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
This commit is contained in:
parent
97c1000d8b
commit
0d6facb321
95
tools/a8_autoclear_test.py
Normal file
95
tools/a8_autoclear_test.py
Normal file
@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test whether BCM4500 register A8 auto-clears after write commands.
|
||||
|
||||
If A8 auto-clears to 0x00 (or 0x02) after writing 0x03, then
|
||||
bcm_poll_ready() would always return TRUE, masking the fact that
|
||||
the DSP isn't processing commands.
|
||||
|
||||
Uses enhanced 0xB6 diagnostic:
|
||||
wValue = page, wIndex = A8 command (0 = default READ)
|
||||
Returns 8 bytes:
|
||||
[0] write_A6_ok
|
||||
[1] A6 readback
|
||||
[2] write_A8_ok
|
||||
[3] A8 IMMEDIATE readback (no delay)
|
||||
[4] A8 after 2ms delay
|
||||
[5] A7 data result
|
||||
[6] A6 final state
|
||||
[7] echo: command sent
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_I2C_DIAG = 0xB6
|
||||
CMD_I2C_RAW_READ = 0xB5
|
||||
BCM4500_ADDR = 0x08
|
||||
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
print('=== A8 Auto-Clear Test ===')
|
||||
print(f'Firmware: {sw.get_fw_version()}')
|
||||
|
||||
# Boot with full sequence
|
||||
print('\n--- Booting BCM4500 (full boot) ---')
|
||||
result = sw._vendor_in(0x89, value=1, index=0, length=3)
|
||||
cfg, stage = result[0], result[1]
|
||||
print(f' Config: 0x{cfg:02X}, Stage: 0x{stage:02X}')
|
||||
|
||||
# Read A8 default state before any commands
|
||||
print('\n--- A8 default state (after boot, before diagnostic) ---')
|
||||
a8_default = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=0xA8, length=1)
|
||||
print(f' A8 default: 0x{a8_default[0]:02X} (bit0={a8_default[0] & 0x01})')
|
||||
|
||||
# Test 1: READ command (0x01) — this was the previous behavior
|
||||
print('\n=== Test 1: A8 with READ command (0x01) ===')
|
||||
diag1 = sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0x01, length=8)
|
||||
print(f' A6 write ok: {diag1[0]}')
|
||||
print(f' A6 readback: 0x{diag1[1]:02X}')
|
||||
print(f' A8 write ok: {diag1[2]}')
|
||||
print(f' A8 IMMEDIATE: 0x{diag1[3]:02X} (bit0={diag1[3] & 0x01})')
|
||||
print(f' A8 after 2ms: 0x{diag1[4]:02X} (bit0={diag1[4] & 0x01})')
|
||||
print(f' A7 data: 0x{diag1[5]:02X}')
|
||||
print(f' A6 final: 0x{diag1[6]:02X}')
|
||||
print(f' Command sent: 0x{diag1[7]:02X}')
|
||||
|
||||
# Reset A8 back to default by reading
|
||||
time.sleep(0.01)
|
||||
|
||||
# Test 2: WRITE command (0x03) — the one used by init blocks
|
||||
print('\n=== Test 2: A8 with WRITE command (0x03) ===')
|
||||
diag2 = sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0x03, length=8)
|
||||
print(f' A6 write ok: {diag2[0]}')
|
||||
print(f' A6 readback: 0x{diag2[1]:02X}')
|
||||
print(f' A8 write ok: {diag2[2]}')
|
||||
print(f' A8 IMMEDIATE: 0x{diag2[3]:02X} (bit0={diag2[3] & 0x01})')
|
||||
print(f' A8 after 2ms: 0x{diag2[4]:02X} (bit0={diag2[4] & 0x01})')
|
||||
print(f' A7 data: 0x{diag2[5]:02X}')
|
||||
print(f' A6 final: 0x{diag2[6]:02X}')
|
||||
print(f' Command sent: 0x{diag2[7]:02X}')
|
||||
|
||||
# Test 3: Try other A8 values
|
||||
print('\n=== Test 3: Various A8 values ===')
|
||||
for cmd in [0x00, 0x02, 0x04, 0xFF]:
|
||||
time.sleep(0.01)
|
||||
diag = sw._vendor_in(CMD_I2C_DIAG, value=0x00, index=cmd, length=8)
|
||||
print(f' CMD=0x{cmd:02X}: A8_imm=0x{diag[3]:02X} A8_2ms=0x{diag[4]:02X} '
|
||||
f'A7=0x{diag[5]:02X}')
|
||||
|
||||
# Analysis
|
||||
print('\n=== Analysis ===')
|
||||
if diag2[3] != 0x03 or diag2[4] != 0x03:
|
||||
print(f' !! A8 AUTO-CLEARS after WRITE command!')
|
||||
print(f' Wrote 0x03, immediate={diag2[3]:02X}, after_2ms={diag2[4]:02X}')
|
||||
if (diag2[3] & 0x01) == 0 or (diag2[4] & 0x01) == 0:
|
||||
print(f' bcm_poll_ready() sees bit0=0 → always returns TRUE')
|
||||
print(f' Init blocks appear to succeed but DSP never processes them!')
|
||||
else:
|
||||
print(f' A8 retains WRITE command (0x03). No auto-clear.')
|
||||
if (diag1[3] == 0x01) and (diag2[3] == 0x03):
|
||||
print(f' Both READ and WRITE commands stick. DSP is genuinely not processing.')
|
||||
|
||||
sw.close()
|
||||
print('\n=== Done ===')
|
||||
128
tools/addr_gateway_test.py
Normal file
128
tools/addr_gateway_test.py
Normal file
@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""BCM4500 I2C address gateway verification test.
|
||||
|
||||
Compares register reads at address 0x08 (BCM4500 direct) vs 0x10
|
||||
(BCM3440 tuner gateway) to confirm the stock firmware disassembly
|
||||
finding: all BCM4500 register access goes through the tuner at 0x10.
|
||||
|
||||
HYPOTHESIS:
|
||||
- Reads at 0x08 return the same status byte for all registers
|
||||
- Reads at 0x10 return different, register-specific values
|
||||
- Writes at 0x10 actually reach BCM4500 registers
|
||||
- The indirect register protocol (A6/A7/A8) works at 0x10
|
||||
|
||||
Run with custom firmware v3.02+ (needs 0xB5 raw I2C read).
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_I2C_RAW_READ = 0xB5
|
||||
|
||||
ADDR_DIRECT = 0x08 # BCM4500 direct
|
||||
ADDR_GATEWAY = 0x10 # BCM3440 tuner gateway
|
||||
|
||||
REGS = [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB]
|
||||
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
print('=== BCM4500 I2C Address Gateway Test ===')
|
||||
print(f'Firmware: {sw.get_fw_version()}')
|
||||
|
||||
# Boot first
|
||||
print('\n--- Booting BCM4500 ---')
|
||||
result = sw._vendor_in(0x89, value=1, index=0, length=3)
|
||||
cfg, stage = result[0], result[1]
|
||||
print(f' Config: 0x{cfg:02X}, Stage: 0x{stage:02X}')
|
||||
|
||||
# Test 1: Read registers via direct address (0x08)
|
||||
print(f'\n=== Test 1: Registers via direct address 0x{ADDR_DIRECT:02X} (BCM4500) ===')
|
||||
direct_vals = {}
|
||||
for reg in REGS:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=ADDR_DIRECT, index=reg, length=1)
|
||||
direct_vals[reg] = data[0]
|
||||
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
|
||||
except Exception as e:
|
||||
direct_vals[reg] = None
|
||||
print(f' 0x{reg:02X}: FAILED ({e})')
|
||||
|
||||
# Test 2: Read registers via gateway address (0x10)
|
||||
print(f'\n=== Test 2: Registers via gateway address 0x{ADDR_GATEWAY:02X} (BCM3440) ===')
|
||||
gw_vals = {}
|
||||
for reg in REGS:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=ADDR_GATEWAY, index=reg, length=1)
|
||||
gw_vals[reg] = data[0]
|
||||
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
|
||||
except Exception as e:
|
||||
gw_vals[reg] = None
|
||||
print(f' 0x{reg:02X}: FAILED ({e})')
|
||||
|
||||
# Test 3: Read tuner-specific registers (low range) at 0x10
|
||||
print(f'\n=== Test 3: BCM3440 tuner registers (0x00-0x0F) at 0x{ADDR_GATEWAY:02X} ===')
|
||||
for reg in range(0x00, 0x10):
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=ADDR_GATEWAY, index=reg, length=1)
|
||||
print(f' 0x{reg:02X}: 0x{data[0]:02X}', end='')
|
||||
except Exception:
|
||||
print(f' 0x{reg:02X}: FAIL', end='')
|
||||
if (reg + 1) % 8 == 0:
|
||||
print()
|
||||
|
||||
# Test 4: Write to A6 via gateway, then read back
|
||||
print(f'\n=== Test 4: Write A6=0x42 via gateway, read back ===')
|
||||
try:
|
||||
# Write using vendor OUT (need to construct this)
|
||||
# Use bcm_direct_write equivalent through 0xB2 (RAW_DEMOD_WRITE)
|
||||
# Actually, we need a raw I2C write command...
|
||||
# Let's use the B6 diagnostic which writes A6 and reads back
|
||||
diag = sw._vendor_in(0xB6, value=0x42, index=0x01, length=8)
|
||||
print(f' B6 diag (via current FW addr):')
|
||||
print(f' A6 write ok: {diag[0]}')
|
||||
print(f' A6 readback: 0x{diag[1]:02X}')
|
||||
print(f' A8 write ok: {diag[2]}')
|
||||
print(f' A8 immediate: 0x{diag[3]:02X}')
|
||||
print(f' A8 after 2ms: 0x{diag[4]:02X}')
|
||||
print(f' A7 data: 0x{diag[5]:02X}')
|
||||
print(f' A6 final: 0x{diag[6]:02X}')
|
||||
print(f' Cmd sent: 0x{diag[7]:02X}')
|
||||
except Exception as e:
|
||||
print(f' FAILED: {e}')
|
||||
|
||||
# Analysis
|
||||
print('\n' + '=' * 60)
|
||||
print('ANALYSIS')
|
||||
print('=' * 60)
|
||||
|
||||
direct_unique = set(v for v in direct_vals.values() if v is not None)
|
||||
gw_unique = set(v for v in gw_vals.values() if v is not None)
|
||||
|
||||
print(f'\nDirect (0x{ADDR_DIRECT:02X}): {len(direct_unique)} unique values: '
|
||||
f'{", ".join(f"0x{v:02X}" for v in sorted(direct_unique))}')
|
||||
print(f'Gateway (0x{ADDR_GATEWAY:02X}): {len(gw_unique)} unique values: '
|
||||
f'{", ".join(f"0x{v:02X}" for v in sorted(gw_unique))}')
|
||||
|
||||
if len(direct_unique) <= 2 and len(gw_unique) > 2:
|
||||
print('\n >> HYPOTHESIS CONFIRMED!')
|
||||
print(' >> Direct 0x08 returns same status for all regs')
|
||||
print(' >> Gateway 0x10 returns register-specific values')
|
||||
print(' >> FIX: BCM4500_ADDR should be 0x10 (tuner gateway)')
|
||||
elif len(gw_unique) <= 2:
|
||||
print('\n >> Gateway also returns uniform values -- may need different timing')
|
||||
print(' >> or the tuner gateway requires initialization first')
|
||||
else:
|
||||
print('\n >> Unexpected results -- check values above')
|
||||
|
||||
# Show side-by-side comparison
|
||||
print('\n Reg Direct(0x08) Gateway(0x10) Different?')
|
||||
for reg in REGS:
|
||||
d = direct_vals.get(reg)
|
||||
g = gw_vals.get(reg)
|
||||
d_str = f'0x{d:02X}' if d is not None else 'FAIL'
|
||||
g_str = f'0x{g:02X}' if g is not None else 'FAIL'
|
||||
diff = ' <--' if d != g else ''
|
||||
print(f' 0x{reg:02X} {d_str:>12} {g_str:>13} {diff}')
|
||||
|
||||
sw.close()
|
||||
print('\n=== Done ===')
|
||||
194
tools/boot_ab_test.py
Normal file
194
tools/boot_ab_test.py
Normal file
@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
"""A/B test: compare BCM4500 register state with and without firmware download.
|
||||
|
||||
Uses wIndex boot flags on BOOT_8PSK (0x89):
|
||||
bit 0 (0x01): skip EEPROM firmware download
|
||||
bit 1 (0x02): add 200ms DSP startup delay after download
|
||||
bit 2 (0x04): skip register init blocks
|
||||
|
||||
Test matrix:
|
||||
A) wIndex=0x01 — skip FW download, init blocks only
|
||||
B) wIndex=0x00 — full boot (FW download + init blocks)
|
||||
C) wIndex=0x02 — full boot + 200ms DSP delay
|
||||
D) wIndex=0x04 — FW download only, skip init blocks
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_RAW_DEMOD_READ = 0xB1
|
||||
CMD_I2C_RAW_READ = 0xB5
|
||||
CMD_GET_PLL_DIAG = 0xBF
|
||||
BCM4500_ADDR = 0x08
|
||||
|
||||
# Key direct registers
|
||||
KEY_REGS = [
|
||||
(0xA0, 'CFG_MODE'),
|
||||
(0xA2, 'STATUS'),
|
||||
(0xA4, 'LOCK'),
|
||||
(0xA6, 'PAGE'),
|
||||
(0xA7, 'DATA'),
|
||||
(0xA8, 'CMD'),
|
||||
(0xA9, 'PLL_A9'),
|
||||
(0xAA, 'PLL_AA'),
|
||||
(0xAB, 'PLL_AB'),
|
||||
]
|
||||
|
||||
|
||||
def read_key_regs(sw, label):
|
||||
"""Read key BCM4500 registers via raw I2C (ground truth)."""
|
||||
print(f' --- {label} ---')
|
||||
results = {}
|
||||
for reg, name in KEY_REGS:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=reg, length=1)
|
||||
val = data[0]
|
||||
results[reg] = val
|
||||
marker = ''
|
||||
if val == 0x00:
|
||||
marker = ' (zero)'
|
||||
elif val == 0x02:
|
||||
marker = ' << non-zero!'
|
||||
elif val != 0x00:
|
||||
marker = ' << non-zero!'
|
||||
print(f' 0x{reg:02X} ({name:8s}): 0x{val:02X}{marker}')
|
||||
except Exception as e:
|
||||
results[reg] = None
|
||||
print(f' 0x{reg:02X} ({name:8s}): FAILED ({e})')
|
||||
nonzero = sum(1 for v in results.values() if v and v != 0)
|
||||
print(f' Non-zero registers: {nonzero}/{len(results)}')
|
||||
return results
|
||||
|
||||
|
||||
def boot_with_flags(sw, wval=1, flags=0, label=''):
|
||||
"""Send BOOT_8PSK with wValue and wIndex (boot flags)."""
|
||||
print(f'\n{"="*60}')
|
||||
print(f'BOOT: wValue={wval}, wIndex=0x{flags:02X} — {label}')
|
||||
print(f'{"="*60}')
|
||||
|
||||
# Send boot command: wValue=boot mode, wIndex=flags
|
||||
try:
|
||||
result = sw._vendor_in(0x89, value=wval, index=flags, length=3)
|
||||
cfg = result[0]
|
||||
stage = result[1]
|
||||
bits = []
|
||||
if cfg & 0x01: bits.append('Started')
|
||||
if cfg & 0x02: bits.append('FW_Loaded')
|
||||
if cfg & 0x04: bits.append('Intersil')
|
||||
if cfg & 0x08: bits.append('DVBmode')
|
||||
print(f' Config: 0x{cfg:02X} ({" | ".join(bits) if bits else "none"})')
|
||||
print(f' Boot stage: 0x{stage:02X}' +
|
||||
(' (COMPLETE)' if stage == 0xFF else f' (stopped at {stage})'))
|
||||
except Exception as e:
|
||||
print(f' Boot command failed: {e}')
|
||||
return None
|
||||
|
||||
# PLL diagnostics
|
||||
try:
|
||||
pd = sw._vendor_in(CMD_GET_PLL_DIAG, value=0, index=0, length=10)
|
||||
print(f' PLL diag: eeprom={"Y" if pd[0] else "N"} '
|
||||
f'blocks={pd[2]} '
|
||||
f'exit={"OK" if pd[6]==1 else "FAIL" if pd[6]==0 else "skip"} '
|
||||
f'result={"OK" if pd[7]==1 else "FAIL"}')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return read_key_regs(sw, 'Registers immediately after boot')
|
||||
|
||||
|
||||
def main():
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
print('=== BCM4500 Boot A/B Test ===')
|
||||
print(f'Firmware: {sw.get_fw_version()}')
|
||||
|
||||
# Pre-boot register state (BCM4500 may still be in stock-firmware state)
|
||||
print('\n' + '='*60)
|
||||
print('PRE-BOOT: Register state before any boot command')
|
||||
print('='*60)
|
||||
pre = read_key_regs(sw, 'Before boot')
|
||||
|
||||
# Shutdown first to establish baseline
|
||||
print('\n--- Shutting down BCM4500 ---')
|
||||
sw._vendor_in(0x89, value=0, index=0, length=3)
|
||||
time.sleep(0.5)
|
||||
|
||||
# ===== TEST A: Init blocks only (no firmware download) =====
|
||||
regs_a = boot_with_flags(sw, wval=1, flags=0x01,
|
||||
label='Init blocks ONLY (skip FW download)')
|
||||
|
||||
# Read again after 200ms
|
||||
time.sleep(0.2)
|
||||
regs_a2 = read_key_regs(sw, 'After 200ms settle (test A)')
|
||||
|
||||
# Shutdown and wait
|
||||
print('\n--- Shutting down BCM4500 ---')
|
||||
sw._vendor_in(0x89, value=0, index=0, length=3)
|
||||
time.sleep(0.5)
|
||||
|
||||
# ===== TEST B: Full boot (firmware download + init blocks) =====
|
||||
regs_b = boot_with_flags(sw, wval=1, flags=0x00,
|
||||
label='Full boot (FW download + init blocks)')
|
||||
|
||||
# Read again after 200ms
|
||||
time.sleep(0.2)
|
||||
regs_b2 = read_key_regs(sw, 'After 200ms settle (test B)')
|
||||
|
||||
# Shutdown and wait
|
||||
print('\n--- Shutting down BCM4500 ---')
|
||||
sw._vendor_in(0x89, value=0, index=0, length=3)
|
||||
time.sleep(0.5)
|
||||
|
||||
# ===== TEST C: Full boot + 200ms DSP startup delay =====
|
||||
regs_c = boot_with_flags(sw, wval=1, flags=0x02,
|
||||
label='Full boot + 200ms DSP delay')
|
||||
|
||||
regs_c2 = read_key_regs(sw, 'Registers (test C)')
|
||||
|
||||
# Shutdown and wait
|
||||
print('\n--- Shutting down BCM4500 ---')
|
||||
sw._vendor_in(0x89, value=0, index=0, length=3)
|
||||
time.sleep(0.5)
|
||||
|
||||
# ===== TEST D: Firmware download only (no init blocks) =====
|
||||
regs_d = boot_with_flags(sw, wval=1, flags=0x04,
|
||||
label='FW download ONLY (skip init blocks)')
|
||||
|
||||
time.sleep(0.2)
|
||||
regs_d2 = read_key_regs(sw, 'After 200ms settle (test D)')
|
||||
|
||||
# ===== SUMMARY =====
|
||||
print('\n' + '='*60)
|
||||
print('SUMMARY: Register 0xA2 (STATUS) across tests')
|
||||
print('='*60)
|
||||
|
||||
def val_str(regs, reg=0xA2):
|
||||
if regs is None:
|
||||
return 'FAIL'
|
||||
v = regs.get(reg)
|
||||
if v is None:
|
||||
return 'FAIL'
|
||||
return f'0x{v:02X}'
|
||||
|
||||
print(f' Pre-boot (stock FW state): {val_str(pre)}')
|
||||
print(f' A) Init only, immediate: {val_str(regs_a)}')
|
||||
print(f' A) Init only, +200ms: {val_str(regs_a2)}')
|
||||
print(f' B) Full boot, immediate: {val_str(regs_b)}')
|
||||
print(f' B) Full boot, +200ms: {val_str(regs_b2)}')
|
||||
print(f' C) Full boot + DSP delay: {val_str(regs_c)}')
|
||||
print(f' D) FW only, +200ms: {val_str(regs_d2)}')
|
||||
|
||||
print()
|
||||
print('Expected: A should show 0x02 (previous working state)')
|
||||
print(' B should show 0x00 (current broken state)')
|
||||
print(' C tests if DSP just needs more startup time')
|
||||
print(' D isolates firmware download effect')
|
||||
|
||||
sw.close()
|
||||
print('\n=== Done ===')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
154
tools/boot_deep_verify.py
Normal file
154
tools/boot_deep_verify.py
Normal file
@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deep verification: full register dump + indirect register test.
|
||||
|
||||
Checks whether:
|
||||
1. Direct registers (0xA0-0xBF) are truly all 0x02 or vary
|
||||
2. Indirect registers respond (proves DSP core is running)
|
||||
3. Signal monitoring works after boot
|
||||
4. Boot with vs without FW download produces different indirect reg values
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_RAW_DEMOD_READ = 0xB1
|
||||
CMD_I2C_RAW_READ = 0xB5
|
||||
BCM4500_ADDR = 0x08
|
||||
|
||||
|
||||
def full_direct_dump(sw, label):
|
||||
"""Read all direct registers 0xA0-0xBF via raw I2C."""
|
||||
print(f'\n --- {label}: Direct Register Dump 0xA0-0xBF ---')
|
||||
values = {}
|
||||
for reg in range(0xA0, 0xC0):
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=reg, length=1)
|
||||
values[reg] = data[0]
|
||||
except Exception:
|
||||
values[reg] = None
|
||||
|
||||
# Print in 2 rows of 16
|
||||
for base in [0xA0, 0xB0]:
|
||||
row = []
|
||||
for reg in range(base, base + 16):
|
||||
v = values.get(reg)
|
||||
row.append(f'{v:02X}' if v is not None else '??')
|
||||
print(f' {base:02X}: {" ".join(row)}')
|
||||
|
||||
unique = set(v for v in values.values() if v is not None)
|
||||
print(f' Unique values: {sorted(f"0x{v:02X}" for v in unique)}')
|
||||
return values
|
||||
|
||||
|
||||
def indirect_reg_test(sw, label):
|
||||
"""Read indirect registers to test DSP core responsiveness.
|
||||
Uses 0xB1 with wIndex=0 (indirect mode): wValue=page."""
|
||||
print(f'\n --- {label}: Indirect Register Test ---')
|
||||
# Key DSP pages: 0x00 (config), 0x06 (acq), 0x07 (AGC),
|
||||
# 0x0A (Viterbi), 0x0F (transport)
|
||||
pages = [0x00, 0x01, 0x06, 0x07, 0x0A, 0x0F, 0x10, 0x20]
|
||||
results = {}
|
||||
for page in pages:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_RAW_DEMOD_READ, value=page,
|
||||
index=0, length=1)
|
||||
val = data[0]
|
||||
results[page] = val
|
||||
marker = ' << DSP alive!' if val != 0 else ''
|
||||
print(f' Page 0x{page:02X}: 0x{val:02X}{marker}')
|
||||
except Exception as e:
|
||||
results[page] = None
|
||||
print(f' Page 0x{page:02X}: FAILED ({e})')
|
||||
|
||||
nonzero = sum(1 for v in results.values() if v and v != 0)
|
||||
print(f' Non-zero pages: {nonzero}/{len(pages)}')
|
||||
return results
|
||||
|
||||
|
||||
def boot_and_test(sw, flags, label):
|
||||
"""Boot with given flags and run full diagnostics."""
|
||||
print(f'\n{"="*60}')
|
||||
print(f'TEST: {label} (wIndex=0x{flags:02X})')
|
||||
print(f'{"="*60}')
|
||||
|
||||
# Shutdown + wait
|
||||
sw._vendor_in(0x89, value=0, index=0, length=3)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Boot
|
||||
result = sw._vendor_in(0x89, value=1, index=flags, length=3)
|
||||
cfg, stage = result[0], result[1]
|
||||
bits = []
|
||||
if cfg & 0x01: bits.append('Started')
|
||||
if cfg & 0x02: bits.append('FW_Loaded')
|
||||
print(f' Config: 0x{cfg:02X} ({" | ".join(bits) if bits else "none"})')
|
||||
print(f' Stage: 0x{stage:02X}' +
|
||||
(' (COMPLETE)' if stage == 0xFF else f' (at {stage})'))
|
||||
|
||||
# Full register dumps
|
||||
direct = full_direct_dump(sw, label)
|
||||
indirect = indirect_reg_test(sw, label)
|
||||
|
||||
# Wait and re-test indirect (DSP may need startup time)
|
||||
time.sleep(0.5)
|
||||
indirect2 = indirect_reg_test(sw, f'{label} +500ms')
|
||||
|
||||
# Signal monitor
|
||||
print(f'\n --- Signal Monitor ---')
|
||||
try:
|
||||
sig = sw.signal_monitor()
|
||||
print(f' {sig}')
|
||||
except Exception as e:
|
||||
print(f' Failed: {e}')
|
||||
|
||||
return direct, indirect, indirect2
|
||||
|
||||
|
||||
def main():
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
print('=== BCM4500 Deep Boot Verification ===')
|
||||
print(f'Firmware: {sw.get_fw_version()}')
|
||||
|
||||
# Test 1: Init blocks only (no FW download)
|
||||
d1, i1, i1b = boot_and_test(sw, 0x01,
|
||||
'Init blocks only (skip FW download)')
|
||||
|
||||
# Test 2: Full boot (FW download + init blocks)
|
||||
d2, i2, i2b = boot_and_test(sw, 0x00,
|
||||
'Full boot (FW download + init blocks)')
|
||||
|
||||
# Compare indirect registers between the two modes
|
||||
print(f'\n{"="*60}')
|
||||
print('COMPARISON: Indirect Registers (init-only vs full boot)')
|
||||
print('='*60)
|
||||
pages = [0x00, 0x01, 0x06, 0x07, 0x0A, 0x0F, 0x10, 0x20]
|
||||
for page in pages:
|
||||
v1 = i1b.get(page)
|
||||
v2 = i2b.get(page)
|
||||
v1s = f'0x{v1:02X}' if v1 is not None else '??'
|
||||
v2s = f'0x{v2:02X}' if v2 is not None else '??'
|
||||
diff = ' << DIFFERENT!' if v1 != v2 else ''
|
||||
print(f' Page 0x{page:02X}: init-only={v1s} full={v2s}{diff}')
|
||||
|
||||
# Compare direct registers
|
||||
print(f'\nDirect register comparison (0xA0-0xBF):')
|
||||
diffs = []
|
||||
for reg in range(0xA0, 0xC0):
|
||||
v1 = d1.get(reg)
|
||||
v2 = d2.get(reg)
|
||||
if v1 != v2:
|
||||
diffs.append(f' 0x{reg:02X}: init-only=0x{v1:02X} full=0x{v2:02X}')
|
||||
if diffs:
|
||||
print('\n'.join(diffs))
|
||||
else:
|
||||
print(' No differences (all registers identical)')
|
||||
|
||||
sw.close()
|
||||
print('\n=== Done ===')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
189
tools/boot_reg_probe.py
Normal file
189
tools/boot_reg_probe.py
Normal file
@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Probe BCM4500 register behavior after boot.
|
||||
|
||||
1. Multi-byte reads via 0xB5 (do adjacent registers differ?)
|
||||
2. Step-by-step indirect read via 0xB6 diagnostic
|
||||
3. Write a register and read back (is the BCM alive or echoing?)
|
||||
4. Try tuning to see if the signal path works
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_I2C_RAW_READ = 0xB5
|
||||
CMD_I2C_DIAG = 0xB6
|
||||
CMD_RAW_DEMOD_READ = 0xB1
|
||||
CMD_RAW_DEMOD_WRITE = 0xB2
|
||||
BCM4500_ADDR = 0x08
|
||||
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
print('=== BCM4500 Register Probe ===')
|
||||
print(f'Firmware: {sw.get_fw_version()}')
|
||||
|
||||
# Boot with full sequence
|
||||
print('\n--- Booting BCM4500 (full boot) ---')
|
||||
result = sw._vendor_in(0x89, value=1, index=0, length=3)
|
||||
cfg, stage = result[0], result[1]
|
||||
print(f' Config: 0x{cfg:02X}, Stage: 0x{stage:02X}')
|
||||
|
||||
# ============================================================
|
||||
# TEST 1: Multi-byte read — read 16 bytes starting at 0xA0
|
||||
# ============================================================
|
||||
print('\n=== Test 1: Multi-byte read (16 bytes from 0xA0) ===')
|
||||
print('If BCM4500 truly maps different registers, bytes should differ.')
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=0xA0, length=16)
|
||||
hex_str = ' '.join(f'{b:02X}' for b in data)
|
||||
print(f' 0xA0-0xAF: {hex_str}')
|
||||
unique = set(data)
|
||||
print(f' Unique values: {sorted(f"0x{v:02X}" for v in unique)}')
|
||||
if len(unique) == 1:
|
||||
print(f' ALL SAME VALUE: 0x{data[0]:02X} — BCM4500 may be returning')
|
||||
print(f' a fixed status byte, not true register contents.')
|
||||
except Exception as e:
|
||||
print(f' Failed: {e}')
|
||||
|
||||
# Read second block 0xB0-0xBF
|
||||
try:
|
||||
data2 = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=0xB0, length=16)
|
||||
hex_str = ' '.join(f'{b:02X}' for b in data2)
|
||||
print(f' 0xB0-0xBF: {hex_str}')
|
||||
except Exception as e:
|
||||
print(f' Failed: {e}')
|
||||
|
||||
# ============================================================
|
||||
# TEST 2: Write-then-read — does the BCM4500 retain writes?
|
||||
# ============================================================
|
||||
print('\n=== Test 2: Write-then-read (register 0xA6 PAGE) ===')
|
||||
print('Write 0x42 to 0xA6, read back. If we get 0x42, register works.')
|
||||
print('If we get 0x02, the chip may be ignoring writes.')
|
||||
|
||||
# Read current value
|
||||
try:
|
||||
before = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=0xA6, length=1)
|
||||
print(f' Before write: 0xA6 = 0x{before[0]:02X}')
|
||||
except Exception as e:
|
||||
print(f' Read failed: {e}')
|
||||
|
||||
# Write 0x42 to 0xA6 via 0xB1 direct write
|
||||
try:
|
||||
# 0xB2: RAW_DEMOD_WRITE — wValue=register, wIndex=data
|
||||
sw._vendor_in(CMD_RAW_DEMOD_WRITE, value=0xA6, index=0x42, length=0)
|
||||
except Exception:
|
||||
# 0xB2 might not return data
|
||||
pass
|
||||
|
||||
time.sleep(0.01)
|
||||
|
||||
# Read back
|
||||
try:
|
||||
after = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=0xA6, length=1)
|
||||
print(f' After write 0x42: 0xA6 = 0x{after[0]:02X}')
|
||||
if after[0] == 0x42:
|
||||
print(f' >> REGISTER WORKS — BCM4500 is alive and accepting writes!')
|
||||
elif after[0] == 0x02:
|
||||
print(f' >> Still 0x02 — chip may be ignoring writes or returning status')
|
||||
else:
|
||||
print(f' >> Got 0x{after[0]:02X} — unexpected')
|
||||
except Exception as e:
|
||||
print(f' Read failed: {e}')
|
||||
|
||||
# Restore 0xA6 to 0x00
|
||||
try:
|
||||
sw._vendor_in(CMD_RAW_DEMOD_WRITE, value=0xA6, index=0x00, length=0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================
|
||||
# TEST 3: Step-by-step indirect read via 0xB6
|
||||
# ============================================================
|
||||
print('\n=== Test 3: Step-by-step indirect read (0xB6) ===')
|
||||
print('Reading indirect register page 0x06 (acquisition config)')
|
||||
try:
|
||||
diag = sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0, length=8)
|
||||
labels = [
|
||||
'Write 0xA6 ok', # [0]
|
||||
'Readback 0xA6', # [1]
|
||||
'Write 0xA8 ok', # [2]
|
||||
'Readback 0xA8', # [3]
|
||||
'Readback 0xA7', # [4] — the indirect register data
|
||||
'Direct read 0xA6', # [5]
|
||||
'Direct read 0xA7', # [6]
|
||||
'Direct read 0xA8', # [7]
|
||||
]
|
||||
for i, label in enumerate(labels):
|
||||
ok_marker = ''
|
||||
if i in [0, 2]:
|
||||
ok_marker = ' (success)' if diag[i] == 0x01 else ' (FAILED!)' if diag[i] == 0x00 else ''
|
||||
print(f' [{i}] {label:20s}: 0x{diag[i]:02X}{ok_marker}')
|
||||
|
||||
print()
|
||||
if diag[0] == 0x01 and diag[2] == 0x01:
|
||||
print(' Writes to A6/A8 succeeded.')
|
||||
if diag[1] == 0x06:
|
||||
print(f' A6 readback = 0x06 — page register WORKS.')
|
||||
else:
|
||||
print(f' A6 readback = 0x{diag[1]:02X} — expected 0x06!')
|
||||
if diag[4] != 0x00:
|
||||
print(f' A7 (indirect data) = 0x{diag[4]:02X} — DSP responding!')
|
||||
else:
|
||||
print(f' A7 (indirect data) = 0x00 — DSP may not be running.')
|
||||
else:
|
||||
print(' Write step FAILED — I2C issue.')
|
||||
except Exception as e:
|
||||
print(f' Diagnostic failed: {e}')
|
||||
|
||||
# Try a few more pages
|
||||
for page in [0x00, 0x07, 0x0F]:
|
||||
try:
|
||||
diag = sw._vendor_in(CMD_I2C_DIAG, value=page, index=0, length=8)
|
||||
a6_ok = 'ok' if diag[0] == 1 else 'FAIL'
|
||||
a8_ok = 'ok' if diag[2] == 1 else 'FAIL'
|
||||
print(f' Page 0x{page:02X}: A6={a6_ok} A6_rb=0x{diag[1]:02X} '
|
||||
f'A8={a8_ok} A8_rb=0x{diag[3]:02X} '
|
||||
f'A7_data=0x{diag[4]:02X} '
|
||||
f'direct=[A6=0x{diag[5]:02X} A7=0x{diag[6]:02X} A8=0x{diag[7]:02X}]')
|
||||
except Exception as e:
|
||||
print(f' Page 0x{page:02X}: {e}')
|
||||
|
||||
# ============================================================
|
||||
# TEST 4: Read registers 0x00-0x9F (below the A0 range)
|
||||
# ============================================================
|
||||
print('\n=== Test 4: Registers OUTSIDE 0xA0-0xBF range ===')
|
||||
print('If BCM4500 returns 0x02 for everything, it might do so for ALL addresses.')
|
||||
for reg in [0x00, 0x10, 0x50, 0x80, 0x90, 0x9F, 0xC0, 0xD0, 0xFF]:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=reg, length=1)
|
||||
print(f' Reg 0x{reg:02X}: 0x{data[0]:02X}')
|
||||
except Exception as e:
|
||||
print(f' Reg 0x{reg:02X}: FAILED ({e})')
|
||||
|
||||
# ============================================================
|
||||
# TEST 5: Quick tune test (no dish needed — just check AGC)
|
||||
# ============================================================
|
||||
print('\n=== Test 5: Tune to 1200 MHz / 27500 ksps (AGC check) ===')
|
||||
try:
|
||||
# Enable Intersil (LNB controller)
|
||||
sw._vendor_in(0x8A, value=1, index=0, length=1)
|
||||
time.sleep(0.1)
|
||||
print(' Intersil enabled')
|
||||
|
||||
# Tune: 1200 MHz, 27500 ksps, QPSK
|
||||
result = sw.tune_monitor(freq_mhz=1200, sr_ksps=27500, mod_index=0, dwell_ms=200)
|
||||
print(f' Tune result: {result}')
|
||||
if result.get('agc1', 0) > 0 or result.get('agc2', 0) > 0:
|
||||
print(' >> AGC non-zero — tuner + demod signal path is alive!')
|
||||
else:
|
||||
print(' >> AGC=0 — signal path not working (or tuner not configured)')
|
||||
except Exception as e:
|
||||
print(f' Tune failed: {e}')
|
||||
|
||||
sw.close()
|
||||
print('\n=== Done ===')
|
||||
177
tools/boot_test.py
Normal file
177
tools/boot_test.py
Normal file
@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Quick boot test for PLL config firmware."""
|
||||
import sys
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_RAW_DEMOD_READ = 0xB1
|
||||
CMD_I2C_SCAN = 0xB4
|
||||
CMD_GET_PLL_DIAG = 0xBF
|
||||
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
print('=== SkyWalker-1 Custom Firmware Boot Test ===')
|
||||
print()
|
||||
|
||||
# Check firmware version
|
||||
ver = sw.get_fw_version()
|
||||
print(f'Firmware version: {ver}')
|
||||
|
||||
# Check config status
|
||||
cfg = sw.get_config()
|
||||
print(f'Config status: 0x{cfg:02X}')
|
||||
bits = []
|
||||
if cfg & 0x01: bits.append('Started')
|
||||
if cfg & 0x02: bits.append('FW_Loaded')
|
||||
if cfg & 0x04: bits.append('Intersil')
|
||||
if cfg & 0x08: bits.append('DVBmode')
|
||||
if cfg & 0x10: bits.append('22kHz')
|
||||
if cfg & 0x20: bits.append('18V')
|
||||
if cfg & 0x40: bits.append('DCtuned')
|
||||
if cfg & 0x80: bits.append('Armed')
|
||||
print(f' Flags: {" | ".join(bits) if bits else "(none)"}')
|
||||
print()
|
||||
|
||||
# Check last error before boot
|
||||
err = sw.get_last_error()
|
||||
print(f'Last error (pre-boot): 0x{err:02X}')
|
||||
print()
|
||||
|
||||
# Boot the BCM4500
|
||||
print('--- Booting BCM4500 ---')
|
||||
boot_result = sw.boot()
|
||||
print(f'Boot result: {boot_result}')
|
||||
cfg = sw.get_config()
|
||||
print(f'Config after boot: 0x{cfg:02X}')
|
||||
bits = []
|
||||
if cfg & 0x01: bits.append('Started')
|
||||
if cfg & 0x02: bits.append('FW_Loaded')
|
||||
if cfg & 0x04: bits.append('Intersil')
|
||||
if cfg & 0x08: bits.append('DVBmode')
|
||||
if cfg & 0x10: bits.append('22kHz')
|
||||
if cfg & 0x20: bits.append('18V')
|
||||
if cfg & 0x40: bits.append('DCtuned')
|
||||
if cfg & 0x80: bits.append('Armed')
|
||||
print(f' Flags: {" | ".join(bits) if bits else "(none)"}')
|
||||
|
||||
err = sw.get_last_error()
|
||||
ERR_NAMES = {
|
||||
0x00: 'ERR_OK',
|
||||
0x01: 'ERR_I2C_TIMEOUT',
|
||||
0x02: 'ERR_I2C_NAK',
|
||||
0x03: 'ERR_I2C_ARB_LOST',
|
||||
0x04: 'ERR_BCM_NOT_READY (PLL config failed)',
|
||||
0x05: 'ERR_BCM_TIMEOUT',
|
||||
}
|
||||
print(f'Last error after boot: 0x{err:02X} = {ERR_NAMES.get(err, "unknown")}')
|
||||
print()
|
||||
|
||||
# PLL config diagnostics
|
||||
print('--- PLL Config Diagnostics ---')
|
||||
try:
|
||||
pd = sw._vendor_in(CMD_GET_PLL_DIAG, value=0, index=0, length=10)
|
||||
print(f' EEPROM present: {"YES" if pd[0] else "NO"}')
|
||||
print(f' First block count: 0x{pd[1]:02X}' + (' (sentinel=0, no PLL data!)' if pd[1] == 0 else
|
||||
(' (not reached)' if pd[1] == 0xFF else f' ({pd[1]} AB bytes)')))
|
||||
print(f' Blocks written: {pd[2]}')
|
||||
print(f' Last A9 value: 0x{pd[3]:02X}' + (' (none)' if pd[3] == 0xFF else ''))
|
||||
print(f' Last AA value: 0x{pd[4]:02X}' + (' (none)' if pd[4] == 0xFF else ''))
|
||||
print(f' Last AB count: 0x{pd[5]:02X}' + (' (none)' if pd[5] == 0xFF else ''))
|
||||
print(f' Config mode exit: {"OK" if pd[6] == 1 else ("FAIL" if pd[6] == 0 else "not reached")}')
|
||||
print(f' Overall PLL result: {"SUCCESS" if pd[7] == 1 else "FAILED"}')
|
||||
print(f' Boot stage: 0x{pd[8]:02X}' + (' (all complete)' if pd[8] == 0xFF else f' (stopped at stage {pd[8]})'))
|
||||
print(f' Last error: 0x{pd[9]:02X}')
|
||||
except Exception as e:
|
||||
print(f' PLL diag failed: {e}')
|
||||
print()
|
||||
|
||||
# I2C bus scan
|
||||
print('--- I2C Bus Scan ---')
|
||||
try:
|
||||
bitmap = sw._vendor_in(CMD_I2C_SCAN, value=0, index=0, length=16)
|
||||
found = []
|
||||
for byte_idx in range(16):
|
||||
for bit_idx in range(8):
|
||||
if bitmap[byte_idx] & (1 << bit_idx):
|
||||
addr = byte_idx * 8 + bit_idx
|
||||
found.append(addr)
|
||||
labels = {0x08: 'BCM4500', 0x10: 'BCM3440', 0x51: 'EEPROM'}
|
||||
for addr in found:
|
||||
label = labels.get(addr, '')
|
||||
print(f' 0x{addr:02X} {label}')
|
||||
if not found:
|
||||
print(' (no devices found!)')
|
||||
except Exception as e:
|
||||
print(f' Scan failed: {e}')
|
||||
print()
|
||||
|
||||
# Try reading signal
|
||||
print('--- Signal Check ---')
|
||||
try:
|
||||
sig = sw.signal_monitor()
|
||||
print(f'Signal monitor: {sig}')
|
||||
except Exception as e:
|
||||
print(f'Signal monitor failed: {e}')
|
||||
|
||||
# Read BCM4500 direct registers via 0xB1 vendor command
|
||||
# wValue=register address, wIndex=1 for direct read mode
|
||||
print()
|
||||
print('--- BCM4500 Direct Register Reads ---')
|
||||
key_regs = [
|
||||
(0xA0, 'CFG_MODE'),
|
||||
(0xA2, 'STATUS'),
|
||||
(0xA4, 'LOCK'),
|
||||
(0xA9, 'PLL_A9'),
|
||||
(0xAA, 'PLL_AA'),
|
||||
(0xAB, 'PLL_AB'),
|
||||
]
|
||||
for reg, label in key_regs:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_RAW_DEMOD_READ, value=reg, index=1, length=1)
|
||||
val = data[0]
|
||||
print(f' 0x{reg:02X} ({label:8s}): 0x{val:02X}')
|
||||
except Exception as e:
|
||||
print(f' 0x{reg:02X} ({label:8s}): FAILED ({e})')
|
||||
|
||||
# Indirect register reads via 0xB1 with wIndex=0 (indirect mode)
|
||||
# wValue=page, wIndex=0. Only meaningful if the DSP core is running.
|
||||
print()
|
||||
print('--- BCM4500 Indirect Register Reads (DSP core test) ---')
|
||||
for page in [0x06, 0x07, 0x0F]:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_RAW_DEMOD_READ, value=page, index=0, length=1)
|
||||
val = data[0]
|
||||
alive = ' << DSP responding' if val != 0 else ''
|
||||
print(f' Page 0x{page:02X}: 0x{val:02X}{alive}')
|
||||
except Exception as e:
|
||||
print(f' Page 0x{page:02X}: FAILED ({e})')
|
||||
|
||||
# Cross-check: read key registers via 0xB5 (raw I2C, writes into EP0BUF directly)
|
||||
# This bypasses the 0xB1 handler's val variable entirely
|
||||
CMD_I2C_RAW_READ = 0xB5
|
||||
BCM4500_ADDR = 0x08
|
||||
print()
|
||||
print('--- Cross-check via 0xB5 Raw I2C Read (BCM4500 @ 0x08) ---')
|
||||
for reg, label in key_regs:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR, index=reg, length=1)
|
||||
val = data[0]
|
||||
tag = ' ** I2C FAIL (0xFF) **' if val == 0xFF else ''
|
||||
print(f' 0x{reg:02X} ({label:8s}): 0x{val:02X}{tag}')
|
||||
except Exception as e:
|
||||
print(f' 0x{reg:02X} ({label:8s}): FAILED ({e})')
|
||||
|
||||
# Full register dump 0xA0-0xBF via 0xB5 raw I2C (ground truth)
|
||||
print()
|
||||
print('--- Full Direct Register Dump 0xA0-0xBF (via 0xB5 raw I2C) ---')
|
||||
for reg in range(0xA0, 0xC0):
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR, index=reg, length=1)
|
||||
val = data[0]
|
||||
print(f' 0x{reg:02X}: 0x{val:02X}')
|
||||
except Exception as e:
|
||||
print(f' 0x{reg:02X}: FAIL')
|
||||
|
||||
sw.close()
|
||||
print()
|
||||
print('=== Test Complete ===')
|
||||
112
tools/eeprom_deep_scan.py
Normal file
112
tools/eeprom_deep_scan.py
Normal file
@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deep EEPROM scan — find the real PLL data location."""
|
||||
import sys
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_EEPROM_READ = 0xC0
|
||||
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
|
||||
|
||||
def ee(addr, length):
|
||||
return sw._vendor_in(CMD_EEPROM_READ, value=addr, index=length, length=length)
|
||||
|
||||
|
||||
def hex_line(addr, data):
|
||||
hex_str = ' '.join(f'{b:02X}' for b in data)
|
||||
return f'{addr:04X}: {hex_str}'
|
||||
|
||||
|
||||
# Dump 0x3F00-0x4100 (area around the boundary — zero gap between FX2 and BCM firmware?)
|
||||
print('=== Region around FX2 firmware end (0x2530) ===')
|
||||
for addr in range(0x2530, 0x2600, 16):
|
||||
d = ee(addr, 16)
|
||||
print(f' {hex_line(addr, d)}')
|
||||
|
||||
print()
|
||||
print('=== Region 0x3F00-0x4080 (end of first 16KB, start of second) ===')
|
||||
for addr in range(0x3F00, 0x4080, 16):
|
||||
d = ee(addr, 16)
|
||||
print(f' {hex_line(addr, d)}')
|
||||
|
||||
# Check the END of the AT24C256 (0x7F00-0x7FFF)
|
||||
print()
|
||||
print('=== End of EEPROM 0x7F00-0x7FFF ===')
|
||||
for addr in range(0x7F00, 0x8000, 16):
|
||||
d = ee(addr, 16)
|
||||
print(f' {hex_line(addr, d)}')
|
||||
|
||||
# Parse the second image at 0x4000 to find its end
|
||||
print()
|
||||
print('=== Parsing 0x4000 as C2-like records ===')
|
||||
offset = 0x4000
|
||||
# First check if it's a C2 header
|
||||
header = ee(0x4000, 4)
|
||||
print(f' Header bytes: {header.hex(" ")}')
|
||||
# Could be: [len_hi, len_lo, addr_hi, addr_lo]
|
||||
rec_len = (header[0] << 8) | header[1]
|
||||
rec_addr = (header[2] << 8) | header[3]
|
||||
print(f' As record: len={rec_len} (0x{rec_len:04X}), addr=0x{rec_addr:04X}')
|
||||
print()
|
||||
|
||||
# Try parsing as load records (same format as C2 but without the 8-byte header)
|
||||
offset = 0x4000
|
||||
print(' Attempting record parse:')
|
||||
total_size = 0
|
||||
while offset < 0x7000:
|
||||
hdr = ee(offset, 4)
|
||||
rlen = (hdr[0] << 8) | hdr[1]
|
||||
raddr = (hdr[2] << 8) | hdr[3]
|
||||
|
||||
if rlen == 0x8001:
|
||||
print(f' [{total_size}] END MARKER at 0x{offset:04X} → entry=0x{raddr:04X}')
|
||||
offset += 4
|
||||
break
|
||||
elif rlen == 0 or rlen > 0x4000:
|
||||
print(f' [{total_size}] STOP at 0x{offset:04X}: len=0x{rlen:04X} addr=0x{raddr:04X}')
|
||||
# Dump surrounding bytes
|
||||
d = ee(offset, 32)
|
||||
print(f' Bytes: {d.hex(" ")}')
|
||||
offset += 4
|
||||
break
|
||||
|
||||
end = raddr + rlen - 1
|
||||
print(f' [{total_size}] {rlen:5d} bytes at EEPROM 0x{offset:04X} → 0x{raddr:04X}-0x{end:04X}')
|
||||
offset += 4 + rlen
|
||||
total_size += rlen
|
||||
if total_size > 30000:
|
||||
print(' (aborting, too much data)')
|
||||
break
|
||||
|
||||
print(f' Second image ends at: 0x{offset:04X}')
|
||||
print()
|
||||
|
||||
# Check immediately after second image for PLL data
|
||||
print(f'=== After second image: 0x{offset:04X}-0x{offset+256:04X} ===')
|
||||
for addr in range(offset, offset + 256, 16):
|
||||
d = ee(addr, 16)
|
||||
print(f' {hex_line(addr, d)}')
|
||||
|
||||
# Also check for PLL blocks at this offset
|
||||
print()
|
||||
print(f'=== PLL block scan starting at 0x{offset:04X} ===')
|
||||
for addr in range(offset, offset + 400, 20):
|
||||
block = ee(addr, 20)
|
||||
count = block[0]
|
||||
if count == 0:
|
||||
print(f' 0x{addr:04X}: [sentinel count=0]')
|
||||
break
|
||||
elif 1 <= count <= 16:
|
||||
ab = block[4:4 + count]
|
||||
print(f' 0x{addr:04X}: count={count} A9=0x{block[1]:02X} AA=0x{block[2]:02X} '
|
||||
f'unused=0x{block[3]:02X} AB=[{ab.hex(" ")}]')
|
||||
else:
|
||||
print(f' 0x{addr:04X}: NOT PLL (first byte=0x{count:02X})')
|
||||
# But continue scanning
|
||||
pass
|
||||
|
||||
sw.close()
|
||||
print()
|
||||
print('=== Done ===')
|
||||
736
tools/eeprom_flash_a0.py
Normal file
736
tools/eeprom_flash_a0.py
Normal file
@ -0,0 +1,736 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
EEPROM flash via host-side I2C orchestration (boot ROM 0xA0).
|
||||
|
||||
Writes C2 firmware images to the SkyWalker-1 EEPROM by driving the
|
||||
FX2LP's I2C controller from the host while the CPU is halted.
|
||||
|
||||
Background:
|
||||
- Stock firmware I2C proxy (0x83/0x84) returns pipe errors
|
||||
- Custom firmware via RAM: I2CS BERR (0xF6) on CPUCS restart
|
||||
- Pre-halt I2C flush (0x90) + halt → I2CS = 0x01 (clean/idle)
|
||||
- Boot ROM 0xA0 can read/write I2C registers at 0xE678-0xE67A
|
||||
- We drive the I2C controller from the host, one register write at
|
||||
a time, to program the EEPROM
|
||||
|
||||
Strategy:
|
||||
1. Pre-halt flush: GET_SIGNAL_LOCK (0x90) finishes stock firmware I2C
|
||||
2. Halt CPU: CPUCS = 0x01 (I2C controller stays idle)
|
||||
3. Write I2C registers via 0xA0 to orchestrate EEPROM write
|
||||
4. Power-cycle to boot from new EEPROM image
|
||||
"""
|
||||
import usb.core, usb.util, sys, time, os, subprocess, argparse
|
||||
|
||||
# USB IDs
|
||||
SKYWALKER_VID = 0x09C0
|
||||
SKYWALKER_PID = 0x0203
|
||||
|
||||
# FX2LP register addresses (XDATA space)
|
||||
CPUCS_ADDR = 0xE600
|
||||
I2CS_ADDR = 0xE678
|
||||
I2DAT_ADDR = 0xE679
|
||||
I2CTL_ADDR = 0xE67A
|
||||
|
||||
# I2CS bit masks
|
||||
bmSTART = 0x80
|
||||
bmSTOP = 0x40
|
||||
bmLASTRD = 0x20
|
||||
bmID1 = 0x10
|
||||
bmID0 = 0x08
|
||||
bmBERR = 0x04
|
||||
bmACK = 0x02
|
||||
bmDONE = 0x01
|
||||
|
||||
# EEPROM parameters (24C128)
|
||||
EEPROM_I2C_ADDR = 0x51 # 7-bit address
|
||||
EEPROM_PAGE_SIZE = 64 # bytes per page write
|
||||
EEPROM_SIZE = 16384 # 16KB total
|
||||
EEPROM_WRITE_MS = 5 # max internal write cycle time
|
||||
|
||||
# Boot ROM vendor request
|
||||
A0_REQUEST = 0xA0
|
||||
|
||||
|
||||
def i2cs_str(val):
|
||||
"""Human-readable I2CS register decode."""
|
||||
flags = []
|
||||
if val & bmSTART: flags.append('START')
|
||||
if val & bmSTOP: flags.append('STOP')
|
||||
if val & bmLASTRD: flags.append('LASTRD')
|
||||
if val & bmBERR: flags.append('BERR')
|
||||
if val & bmACK: flags.append('ACK')
|
||||
if val & bmDONE: flags.append('DONE')
|
||||
id_val = (val >> 3) & 0x03
|
||||
state = {0: 'idle', 1: 'data', 2: 'addr-wait', 3: 'busy'}[id_val]
|
||||
return f"0x{val:02X} [{' '.join(flags) if flags else 'clear'}] state={state}"
|
||||
|
||||
|
||||
def find_device():
|
||||
"""Find SkyWalker-1 on USB."""
|
||||
dev = usb.core.find(idVendor=SKYWALKER_VID, idProduct=SKYWALKER_PID)
|
||||
if dev is None:
|
||||
print("ERROR: SkyWalker-1 not found on USB")
|
||||
sys.exit(1)
|
||||
print(f" Device: Bus {dev.bus} Addr {dev.address}")
|
||||
return dev
|
||||
|
||||
|
||||
def detach_driver(dev):
|
||||
"""Detach kernel driver if attached."""
|
||||
for cfg in dev:
|
||||
for intf in cfg:
|
||||
if dev.is_kernel_driver_active(intf.bInterfaceNumber):
|
||||
try:
|
||||
dev.detach_kernel_driver(intf.bInterfaceNumber)
|
||||
except usb.core.USBError as e:
|
||||
print(f" Cannot detach driver: {e}")
|
||||
print(" Try: sudo modprobe -r dvb_usb_gp8psk")
|
||||
sys.exit(1)
|
||||
try:
|
||||
dev.set_configuration()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# ── FX2 register access via 0xA0 ─────────────────────────────────
|
||||
|
||||
def a0_read(dev, addr, length=1):
|
||||
"""Read from FX2 XDATA address space via boot ROM 0xA0."""
|
||||
return bytes(dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN,
|
||||
A0_REQUEST, addr, 0, length, 2000))
|
||||
|
||||
|
||||
def a0_write(dev, addr, data):
|
||||
"""Write to FX2 XDATA address space via boot ROM 0xA0."""
|
||||
if isinstance(data, int):
|
||||
data = bytes([data])
|
||||
dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
|
||||
A0_REQUEST, addr, 0, data, 2000)
|
||||
|
||||
|
||||
# ── Host-side I2C primitives ─────────────────────────────────────
|
||||
|
||||
def i2c_read_status(dev):
|
||||
"""Read I2CS register."""
|
||||
return a0_read(dev, I2CS_ADDR, 1)[0]
|
||||
|
||||
|
||||
def i2c_wait_done(dev, timeout_ms=50):
|
||||
"""Poll I2CS for DONE bit. Returns (success, i2cs_value)."""
|
||||
deadline = time.time() + timeout_ms / 1000.0
|
||||
while time.time() < deadline:
|
||||
val = i2c_read_status(dev)
|
||||
if val & bmDONE:
|
||||
return True, val
|
||||
if val & bmBERR:
|
||||
return False, val
|
||||
time.sleep(0.001) # 1ms between polls
|
||||
return False, val
|
||||
|
||||
|
||||
def i2c_start(dev):
|
||||
"""Assert I2C START condition."""
|
||||
a0_write(dev, I2CS_ADDR, bmSTART)
|
||||
|
||||
|
||||
def i2c_stop(dev):
|
||||
"""Assert I2C STOP condition and wait for completion."""
|
||||
a0_write(dev, I2CS_ADDR, bmSTOP)
|
||||
deadline = time.time() + 0.050
|
||||
while time.time() < deadline:
|
||||
val = i2c_read_status(dev)
|
||||
if not (val & bmSTOP):
|
||||
return True, val
|
||||
time.sleep(0.001)
|
||||
return False, val
|
||||
|
||||
|
||||
def i2c_write_byte(dev, byte_val):
|
||||
"""Write a byte to I2DAT, wait for DONE. Returns (ack, i2cs)."""
|
||||
a0_write(dev, I2DAT_ADDR, byte_val)
|
||||
ok, status = i2c_wait_done(dev)
|
||||
if not ok:
|
||||
return False, status
|
||||
return bool(status & bmACK), status
|
||||
|
||||
|
||||
def i2c_read_byte(dev, last=False):
|
||||
"""Read a byte from I2DAT. Set last=True for NACK (last byte)."""
|
||||
if last:
|
||||
a0_write(dev, I2CS_ADDR, bmLASTRD)
|
||||
# Reading I2DAT triggers the next SCL clock cycle
|
||||
val = a0_read(dev, I2DAT_ADDR, 1)[0]
|
||||
ok, status = i2c_wait_done(dev)
|
||||
return val, ok, status
|
||||
|
||||
|
||||
# ── Higher-level I2C operations ──────────────────────────────────
|
||||
|
||||
def i2c_probe(dev, addr_7bit):
|
||||
"""Probe an I2C device. Returns True if it ACKs its address."""
|
||||
i2c_start(dev)
|
||||
ack, status = i2c_write_byte(dev, addr_7bit << 1)
|
||||
i2c_stop(dev)
|
||||
return ack
|
||||
|
||||
|
||||
def eeprom_write_page(dev, mem_addr, data):
|
||||
"""Write up to EEPROM_PAGE_SIZE bytes to EEPROM at mem_addr.
|
||||
|
||||
EEPROM protocol: START + slave_W + addr_H + addr_L + data... + STOP
|
||||
Then wait for internal write cycle (ACK polling or fixed delay).
|
||||
"""
|
||||
if len(data) > EEPROM_PAGE_SIZE:
|
||||
raise ValueError(f"Page write max {EEPROM_PAGE_SIZE} bytes, got {len(data)}")
|
||||
|
||||
addr_h = (mem_addr >> 8) & 0xFF
|
||||
addr_l = mem_addr & 0xFF
|
||||
|
||||
# START
|
||||
i2c_start(dev)
|
||||
|
||||
# Slave address (write)
|
||||
ack, status = i2c_write_byte(dev, EEPROM_I2C_ADDR << 1)
|
||||
if not ack:
|
||||
i2c_stop(dev)
|
||||
return False, "no ACK on slave address"
|
||||
|
||||
# Memory address high byte
|
||||
ack, status = i2c_write_byte(dev, addr_h)
|
||||
if not ack:
|
||||
i2c_stop(dev)
|
||||
return False, "no ACK on addr_H"
|
||||
|
||||
# Memory address low byte
|
||||
ack, status = i2c_write_byte(dev, addr_l)
|
||||
if not ack:
|
||||
i2c_stop(dev)
|
||||
return False, "no ACK on addr_L"
|
||||
|
||||
# Data bytes
|
||||
for i, byte_val in enumerate(data):
|
||||
ack, status = i2c_write_byte(dev, byte_val)
|
||||
if not ack:
|
||||
i2c_stop(dev)
|
||||
return False, f"no ACK on data byte {i} (0x{byte_val:02X})"
|
||||
|
||||
# STOP (initiates EEPROM internal write cycle)
|
||||
i2c_stop(dev)
|
||||
|
||||
# Wait for write cycle via ACK polling
|
||||
# The EEPROM NACKs its address during the write cycle, then ACKs
|
||||
# when the cycle completes. Timeout after 20ms.
|
||||
deadline = time.time() + 0.020
|
||||
while time.time() < deadline:
|
||||
time.sleep(0.001)
|
||||
i2c_start(dev)
|
||||
ack, status = i2c_write_byte(dev, EEPROM_I2C_ADDR << 1)
|
||||
if ack:
|
||||
i2c_stop(dev)
|
||||
return True, "ok"
|
||||
i2c_stop(dev)
|
||||
|
||||
return False, "write cycle timeout (no ACK after 20ms)"
|
||||
|
||||
|
||||
def eeprom_read_bytes(dev, mem_addr, length):
|
||||
"""Read bytes from EEPROM.
|
||||
|
||||
Protocol: START + slave_W + addr_H + addr_L +
|
||||
rSTART + slave_R + data[0] ... data[n-1] + NACK + STOP
|
||||
"""
|
||||
addr_h = (mem_addr >> 8) & 0xFF
|
||||
addr_l = mem_addr & 0xFF
|
||||
|
||||
# Write phase: set address pointer
|
||||
i2c_start(dev)
|
||||
|
||||
ack, status = i2c_write_byte(dev, EEPROM_I2C_ADDR << 1)
|
||||
if not ack:
|
||||
i2c_stop(dev)
|
||||
return None, "no ACK on slave address (write phase)"
|
||||
|
||||
ack, status = i2c_write_byte(dev, addr_h)
|
||||
if not ack:
|
||||
i2c_stop(dev)
|
||||
return None, "no ACK on addr_H"
|
||||
|
||||
ack, status = i2c_write_byte(dev, addr_l)
|
||||
if not ack:
|
||||
i2c_stop(dev)
|
||||
return None, "no ACK on addr_L"
|
||||
|
||||
# Read phase: repeated START + slave_R
|
||||
i2c_start(dev)
|
||||
|
||||
ack, status = i2c_write_byte(dev, (EEPROM_I2C_ADDR << 1) | 1)
|
||||
if not ack:
|
||||
i2c_stop(dev)
|
||||
return None, "no ACK on slave address (read phase)"
|
||||
|
||||
# Read data bytes
|
||||
result = bytearray()
|
||||
for i in range(length):
|
||||
is_last = (i == length - 1)
|
||||
val, ok, status = i2c_read_byte(dev, last=is_last)
|
||||
if not ok:
|
||||
i2c_stop(dev)
|
||||
return bytes(result), f"read failed at byte {i}"
|
||||
result.append(val)
|
||||
|
||||
# Dummy read to complete the last byte cycle
|
||||
_ = a0_read(dev, I2DAT_ADDR, 1)
|
||||
|
||||
# STOP
|
||||
i2c_stop(dev)
|
||||
|
||||
return bytes(result), "ok"
|
||||
|
||||
|
||||
# ── Device preparation ───────────────────────────────────────────
|
||||
|
||||
def prepare_device(dev, verbose=False):
|
||||
"""Pre-halt flush, halt CPU, verify I2C controller is clean.
|
||||
|
||||
Returns True if I2C controller is ready for host-side operations.
|
||||
"""
|
||||
print("\n Phase 1: Prepare I2C controller")
|
||||
print(" " + "─" * 40)
|
||||
|
||||
# Step 1: Pre-halt I2C flush
|
||||
print(" Sending GET_SIGNAL_LOCK (0x90) to flush I2C...")
|
||||
try:
|
||||
lock = dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN,
|
||||
0x90, 0, 0, 1, 2000)
|
||||
print(f" Response: 0x{lock[0]:02X}")
|
||||
except usb.core.USBError as e:
|
||||
print(f" 0x90 failed: {e}")
|
||||
# Try alternative flush commands
|
||||
try:
|
||||
cfg = dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN,
|
||||
0x80, 0, 0, 1, 2000)
|
||||
print(f" Fallback 0x80 response: 0x{cfg[0]:02X}")
|
||||
except usb.core.USBError:
|
||||
print(" No flush commands worked — proceeding anyway")
|
||||
|
||||
# Step 2: Halt CPU
|
||||
print(" Halting CPU (CPUCS = 0x01)...")
|
||||
a0_write(dev, CPUCS_ADDR, 0x01)
|
||||
time.sleep(0.010)
|
||||
|
||||
cpucs = a0_read(dev, CPUCS_ADDR, 1)[0]
|
||||
if not (cpucs & 0x01):
|
||||
print(f" CPUCS readback: 0x{cpucs:02X} — halt may have failed")
|
||||
return False
|
||||
print(f" CPU halted (CPUCS = 0x{cpucs:02X})")
|
||||
|
||||
# Step 3: Read I2C controller state
|
||||
i2cs = i2c_read_status(dev)
|
||||
i2ctl = a0_read(dev, I2CTL_ADDR, 1)[0]
|
||||
print(f" I2CS: {i2cs_str(i2cs)}")
|
||||
print(f" I2CTL: 0x{i2ctl:02X} ({'400kHz' if i2ctl & 1 else '100kHz'})")
|
||||
|
||||
if i2cs & bmBERR:
|
||||
print(" BERR is set — I2C controller is stuck")
|
||||
print(" This usually means the CPU was restarted after halt")
|
||||
return False
|
||||
|
||||
# Step 4: Set I2C speed to 400kHz
|
||||
if not (i2ctl & 0x01):
|
||||
print(" Setting I2CTL = 0x01 (400kHz)...")
|
||||
a0_write(dev, I2CTL_ADDR, 0x01)
|
||||
i2ctl2 = a0_read(dev, I2CTL_ADDR, 1)[0]
|
||||
print(f" I2CTL readback: 0x{i2ctl2:02X}")
|
||||
|
||||
# Step 5: Quick I2C bus probe
|
||||
print("\n Phase 2: I2C bus probe")
|
||||
print(" " + "─" * 40)
|
||||
|
||||
addrs_to_probe = [
|
||||
(0x51, "EEPROM (24C128)"),
|
||||
(0x50, "EEPROM alt addr"),
|
||||
(0x08, "BCM4500 demod"),
|
||||
(0x10, "BCM3440 tuner"),
|
||||
]
|
||||
|
||||
found_eeprom = False
|
||||
for addr, name in addrs_to_probe:
|
||||
ack = i2c_probe(dev, addr)
|
||||
status_after = i2c_read_status(dev)
|
||||
result = "ACK" if ack else "NACK"
|
||||
marker = " <--" if ack and addr in (0x50, 0x51) else ""
|
||||
print(f" 0x{addr:02X} {name:20s}: {result:4s} "
|
||||
f"(I2CS={i2cs_str(status_after)}){marker}")
|
||||
if ack and addr in (0x50, 0x51):
|
||||
found_eeprom = True
|
||||
|
||||
# Check for BERR after each probe
|
||||
if status_after & bmBERR:
|
||||
print(f" BERR after probe — host-side I2C may not work")
|
||||
return False
|
||||
|
||||
if verbose:
|
||||
# Read a few bytes from EEPROM to verify reads work
|
||||
if found_eeprom:
|
||||
print("\n Phase 2b: EEPROM read test")
|
||||
print(" " + "─" * 40)
|
||||
data, msg = eeprom_read_bytes(dev, 0x0000, 8)
|
||||
if data:
|
||||
print(f" EEPROM[0x0000..0x0007]: {data.hex(' ')}")
|
||||
if data[0] == 0xC2:
|
||||
print(f" First byte is 0xC2 — valid C2 boot header!")
|
||||
else:
|
||||
print(f" First byte is 0x{data[0]:02X} — not a C2 header")
|
||||
else:
|
||||
print(f" Read failed: {msg}")
|
||||
return False
|
||||
|
||||
if not found_eeprom:
|
||||
print(" No EEPROM found at 0x50 or 0x51")
|
||||
return False
|
||||
|
||||
print(f"\n I2C controller ready for EEPROM operations")
|
||||
return True
|
||||
|
||||
|
||||
# ── Subcommands ──────────────────────────────────────────────────
|
||||
|
||||
def cmd_probe(args):
|
||||
"""Test host-side I2C by probing the bus."""
|
||||
print("SkyWalker-1 Host-Side I2C Probe (0xA0)")
|
||||
print("=" * 45)
|
||||
|
||||
dev = find_device()
|
||||
detach_driver(dev)
|
||||
|
||||
ok = prepare_device(dev, verbose=True)
|
||||
if not ok:
|
||||
print("\n FAIL: Host-side I2C does not work")
|
||||
print(" The 0xA0 vendor request may not trigger I2C hardware,")
|
||||
print(" or the I2C bus is in a bad state.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\n SUCCESS: Host-side I2C is working!")
|
||||
print(" EEPROM detected and readable via boot ROM 0xA0")
|
||||
|
||||
|
||||
def cmd_read(args):
|
||||
"""Read EEPROM contents via host-side I2C."""
|
||||
size = args.size
|
||||
print("SkyWalker-1 EEPROM Read (host-side I2C)")
|
||||
print("=" * 45)
|
||||
|
||||
dev = find_device()
|
||||
detach_driver(dev)
|
||||
|
||||
ok = prepare_device(dev, verbose=False)
|
||||
if not ok:
|
||||
print("\n FAIL: Cannot prepare I2C")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\n Reading {size} bytes from EEPROM...")
|
||||
chunk_size = 32 # read in small chunks (each byte = 2+ USB transfers)
|
||||
data = bytearray()
|
||||
errors = 0
|
||||
|
||||
for offset in range(0, size, chunk_size):
|
||||
remaining = min(chunk_size, size - offset)
|
||||
chunk, msg = eeprom_read_bytes(dev, offset, remaining)
|
||||
if chunk is None:
|
||||
print(f"\n Read failed at 0x{offset:04X}: {msg}")
|
||||
errors += 1
|
||||
data.extend(b'\xff' * remaining)
|
||||
else:
|
||||
data.extend(chunk)
|
||||
|
||||
if offset % 256 == 0 or offset + remaining >= size:
|
||||
pct = (offset + remaining) * 100 // size
|
||||
print(f"\r Read: 0x{offset + remaining:04X}/{size:04X} [{pct:3d}%]",
|
||||
end="", flush=True)
|
||||
|
||||
print()
|
||||
|
||||
if errors:
|
||||
print(f"\n {errors} read error(s)")
|
||||
|
||||
# Hex dump
|
||||
if args.hex_dump:
|
||||
print(f"\n EEPROM contents (first {min(256, len(data))} bytes):")
|
||||
for i in range(0, min(256, len(data)), 16):
|
||||
row = data[i:i + 16]
|
||||
hex_part = ' '.join(f'{b:02X}' for b in row)
|
||||
ascii_part = ''.join(
|
||||
chr(b) if 0x20 <= b < 0x7F else '.' for b in row)
|
||||
print(f" {i:04X}: {hex_part:<48s} {ascii_part}")
|
||||
|
||||
# Check C2 header
|
||||
if len(data) >= 8 and data[0] == 0xC2:
|
||||
vid = data[2] << 8 | data[1]
|
||||
pid = data[4] << 8 | data[3]
|
||||
print(f"\n C2 header: VID=0x{vid:04X} PID=0x{pid:04X} "
|
||||
f"CONFIG=0x{data[7]:02X}")
|
||||
|
||||
if args.output:
|
||||
with open(args.output, 'wb') as f:
|
||||
f.write(data)
|
||||
print(f"\n Saved to: {args.output}")
|
||||
|
||||
|
||||
def cmd_write(args):
|
||||
"""Write a C2 firmware image to EEPROM via host-side I2C."""
|
||||
image_path = args.file
|
||||
if not os.path.exists(image_path):
|
||||
print(f"File not found: {image_path}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(image_path, 'rb') as f:
|
||||
image = f.read()
|
||||
|
||||
print("SkyWalker-1 EEPROM Flash (host-side I2C)")
|
||||
print("=" * 45)
|
||||
|
||||
# Validate image
|
||||
if len(image) < 8 or image[0] != 0xC2:
|
||||
print(f" Not a C2 image (first byte: 0x{image[0]:02X})")
|
||||
sys.exit(1)
|
||||
if len(image) > EEPROM_SIZE:
|
||||
print(f" Image too large: {len(image)} > {EEPROM_SIZE}")
|
||||
sys.exit(1)
|
||||
|
||||
vid = image[2] << 8 | image[1]
|
||||
pid = image[4] << 8 | image[3]
|
||||
config = image[7]
|
||||
print(f" Image: {image_path}")
|
||||
print(f" Size: {len(image)} bytes ({len(image)*100//EEPROM_SIZE}% of EEPROM)")
|
||||
print(f" VID: 0x{vid:04X} PID: 0x{pid:04X} CONFIG: 0x{config:02X}")
|
||||
|
||||
dev = find_device()
|
||||
detach_driver(dev)
|
||||
|
||||
ok = prepare_device(dev, verbose=True)
|
||||
if not ok:
|
||||
print("\n FAIL: Cannot prepare I2C — aborting")
|
||||
sys.exit(1)
|
||||
|
||||
# Backup current EEPROM first (just the header to be safe)
|
||||
if not args.no_backup:
|
||||
print(f"\n Backing up EEPROM header...")
|
||||
hdr, msg = eeprom_read_bytes(dev, 0x0000, 8)
|
||||
if hdr:
|
||||
print(f" Current header: {hdr.hex(' ')}")
|
||||
if hdr[0] == 0xC2:
|
||||
cur_vid = hdr[2] << 8 | hdr[1]
|
||||
cur_pid = hdr[4] << 8 | hdr[3]
|
||||
print(f" Current: VID=0x{cur_vid:04X} PID=0x{cur_pid:04X}")
|
||||
else:
|
||||
print(f" Header read failed: {msg}")
|
||||
if not args.force:
|
||||
print(" Use --force to proceed without backup verification")
|
||||
sys.exit(1)
|
||||
|
||||
# Dry run?
|
||||
if args.dry_run:
|
||||
pages = (len(image) + EEPROM_PAGE_SIZE - 1) // EEPROM_PAGE_SIZE
|
||||
print(f"\n DRY RUN: would write {len(image)} bytes in {pages} pages")
|
||||
return
|
||||
|
||||
# Write confirmation
|
||||
print(f"\n *** WRITING {len(image)} BYTES TO EEPROM ***")
|
||||
print(f" This replaces the boot firmware. A bad write = bricked device.")
|
||||
print(f" Press Ctrl+C within 3 seconds to abort.")
|
||||
try:
|
||||
for i in range(3, 0, -1):
|
||||
print(f"\r Starting in {i}... ", end="", flush=True)
|
||||
time.sleep(1)
|
||||
print("\r Writing now... ")
|
||||
except KeyboardInterrupt:
|
||||
print("\n Aborted.")
|
||||
return
|
||||
|
||||
# Write image in page-sized chunks
|
||||
total_pages = (len(image) + EEPROM_PAGE_SIZE - 1) // EEPROM_PAGE_SIZE
|
||||
write_errors = 0
|
||||
start_time = time.time()
|
||||
|
||||
for page_num in range(total_pages):
|
||||
offset = page_num * EEPROM_PAGE_SIZE
|
||||
end = min(offset + EEPROM_PAGE_SIZE, len(image))
|
||||
chunk = image[offset:end]
|
||||
|
||||
pct = (page_num + 1) * 100 // total_pages
|
||||
elapsed = time.time() - start_time
|
||||
rate = (offset + len(chunk)) / elapsed if elapsed > 0 else 0
|
||||
eta = (len(image) - offset - len(chunk)) / rate if rate > 0 else 0
|
||||
print(f"\r Write: 0x{offset:04X}/0x{len(image):04X} "
|
||||
f"[{pct:3d}%] {rate:.0f} B/s ETA {eta:.0f}s ",
|
||||
end="", flush=True)
|
||||
|
||||
ok, msg = eeprom_write_page(dev, offset, chunk)
|
||||
if not ok:
|
||||
print(f"\n Write error at 0x{offset:04X}: {msg}")
|
||||
write_errors += 1
|
||||
if write_errors >= 3:
|
||||
print("\n Too many errors — aborting")
|
||||
print(" *** EEPROM STATE UNKNOWN ***")
|
||||
sys.exit(1)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
print(f"\r Write: 0x{len(image):04X}/0x{len(image):04X} "
|
||||
f"[100%] done in {elapsed:.1f}s ")
|
||||
|
||||
if write_errors:
|
||||
print(f"\n WARNING: {write_errors} write error(s)")
|
||||
|
||||
# Verify
|
||||
print(f"\n Verifying ({len(image)} bytes)...")
|
||||
verify_chunk_size = 32
|
||||
mismatches = 0
|
||||
first_mismatch = None
|
||||
|
||||
for offset in range(0, len(image), verify_chunk_size):
|
||||
remaining = min(verify_chunk_size, len(image) - offset)
|
||||
chunk, msg = eeprom_read_bytes(dev, offset, remaining)
|
||||
if chunk is None:
|
||||
print(f"\n Verify read failed at 0x{offset:04X}: {msg}")
|
||||
mismatches += remaining
|
||||
continue
|
||||
|
||||
for i, (expected, got) in enumerate(zip(image[offset:offset+remaining], chunk)):
|
||||
if expected != got:
|
||||
if first_mismatch is None:
|
||||
first_mismatch = offset + i
|
||||
mismatches += 1
|
||||
if mismatches <= 8:
|
||||
print(f"\n Mismatch at 0x{offset+i:04X}: "
|
||||
f"wrote 0x{expected:02X} read 0x{got:02X}")
|
||||
|
||||
if offset % 256 == 0 or offset + remaining >= len(image):
|
||||
pct = (offset + remaining) * 100 // len(image)
|
||||
print(f"\r Verify: 0x{offset+remaining:04X}/0x{len(image):04X} "
|
||||
f"[{pct:3d}%]", end="", flush=True)
|
||||
|
||||
print()
|
||||
|
||||
if mismatches == 0:
|
||||
print(f"\n VERIFIED: all {len(image)} bytes match")
|
||||
print(f" Flash complete in {elapsed:.1f}s")
|
||||
print(f"\n Power-cycle the device to boot the new firmware.")
|
||||
if args.power_cycle:
|
||||
do_power_cycle()
|
||||
else:
|
||||
print(f"\n VERIFY FAILED: {mismatches} byte(s) differ "
|
||||
f"(first at 0x{first_mismatch:04X})")
|
||||
if mismatches > 8:
|
||||
print(f" ({mismatches - 8} additional mismatches not shown)")
|
||||
print(f"\n *** DO NOT POWER CYCLE ***")
|
||||
print(f" Re-run this tool to retry, or use an external programmer.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def do_power_cycle():
|
||||
"""Power-cycle SkyWalker-1 via uhubctl."""
|
||||
print(f"\n Power-cycling via uhubctl...")
|
||||
try:
|
||||
# Off
|
||||
r = subprocess.run(
|
||||
['sudo', 'uhubctl', '-l', '1-5.4.4', '-p', '3', '-a', 'off'],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
if r.returncode != 0:
|
||||
print(f" uhubctl off failed: {r.stderr.strip()}")
|
||||
print(f" Manually unplug and replug the device.")
|
||||
return
|
||||
print(f" Port 3 powered off")
|
||||
time.sleep(2)
|
||||
|
||||
# On
|
||||
r = subprocess.run(
|
||||
['sudo', 'uhubctl', '-l', '1-5.4.4', '-p', '3', '-a', 'on'],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
if r.returncode != 0:
|
||||
print(f" uhubctl on failed: {r.stderr.strip()}")
|
||||
return
|
||||
print(f" Port 3 powered on")
|
||||
print(f" Waiting for device to enumerate...")
|
||||
|
||||
time.sleep(3)
|
||||
dev = usb.core.find(idVendor=SKYWALKER_VID, idProduct=SKYWALKER_PID)
|
||||
if dev:
|
||||
print(f" Device found: Bus {dev.bus} Addr {dev.address}")
|
||||
print(f" New firmware is running!")
|
||||
else:
|
||||
print(f" Device not found yet — may need a few more seconds")
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f" uhubctl not found — manually power-cycle the device")
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f" uhubctl timed out")
|
||||
|
||||
|
||||
def cmd_power_cycle(args):
|
||||
"""Power-cycle the SkyWalker-1 via uhubctl."""
|
||||
print("SkyWalker-1 Power Cycle")
|
||||
print("=" * 45)
|
||||
do_power_cycle()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="SkyWalker-1 EEPROM flash via host-side I2C (boot ROM 0xA0)",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""\
|
||||
This tool drives the FX2LP's I2C controller from the host while the
|
||||
CPU is halted. It works around the stock firmware's broken I2C proxy
|
||||
and the BERR bug triggered by CPUCS restart.
|
||||
|
||||
examples:
|
||||
%(prog)s probe # test if host-side I2C works
|
||||
%(prog)s read -o backup.bin # backup current EEPROM
|
||||
%(prog)s write firmware_eeprom.bin # flash new C2 image
|
||||
%(prog)s write firmware_eeprom.bin -P # flash + auto power-cycle
|
||||
%(prog)s power-cycle # just power-cycle the device
|
||||
""")
|
||||
sub = parser.add_subparsers(dest='command', required=True)
|
||||
|
||||
# probe
|
||||
sub.add_parser('probe', help='Test host-side I2C bus access')
|
||||
|
||||
# read
|
||||
p_read = sub.add_parser('read', help='Read EEPROM contents')
|
||||
p_read.add_argument('-o', '--output', help='Save to file')
|
||||
p_read.add_argument('--size', type=int, default=EEPROM_SIZE,
|
||||
help=f'Bytes to read (default: {EEPROM_SIZE})')
|
||||
p_read.add_argument('--hex', dest='hex_dump', action='store_true',
|
||||
help='Show hex dump')
|
||||
|
||||
# write
|
||||
p_write = sub.add_parser('write', help='Flash C2 image to EEPROM')
|
||||
p_write.add_argument('file', help='C2 firmware image (.bin)')
|
||||
p_write.add_argument('--dry-run', action='store_true',
|
||||
help='Show what would happen without writing')
|
||||
p_write.add_argument('--no-backup', action='store_true',
|
||||
help='Skip header backup check')
|
||||
p_write.add_argument('--force', action='store_true',
|
||||
help='Continue despite warnings')
|
||||
p_write.add_argument('-P', '--power-cycle', action='store_true',
|
||||
help='Auto power-cycle after successful flash')
|
||||
|
||||
# power-cycle
|
||||
sub.add_parser('power-cycle', help='Power-cycle via uhubctl')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
dispatch = {
|
||||
'probe': cmd_probe,
|
||||
'read': cmd_read,
|
||||
'write': cmd_write,
|
||||
'power-cycle': cmd_power_cycle,
|
||||
}
|
||||
dispatch[args.command](args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
108
tools/eeprom_pll_find.py
Normal file
108
tools/eeprom_pll_find.py
Normal file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find the exact EEPROM address where PLL config starts.
|
||||
|
||||
The stock firmware's FUN_CODE_10F2 presumably finds the PLL data by
|
||||
computing an offset from the C2 boot firmware structure. This script
|
||||
parses the C2 records to find where the firmware ends, then checks
|
||||
if PLL data immediately follows.
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_EEPROM_READ = 0xC0
|
||||
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
|
||||
|
||||
def eeprom_read(addr, length):
|
||||
return sw._vendor_in(CMD_EEPROM_READ, value=addr, index=length, length=length)
|
||||
|
||||
|
||||
print('=== C2 Firmware Structure Analysis ===')
|
||||
print()
|
||||
|
||||
# Read C2 header (8 bytes)
|
||||
header = eeprom_read(0x0000, 8)
|
||||
print(f'Header: {header.hex(" ")}')
|
||||
assert header[0] == 0xC2, 'Not a C2 EEPROM!'
|
||||
vid = header[2] << 8 | header[1]
|
||||
pid = header[4] << 8 | header[3]
|
||||
print(f' VID: 0x{vid:04X} PID: 0x{pid:04X}')
|
||||
print()
|
||||
|
||||
# Parse C2 load records
|
||||
offset = 8
|
||||
records = []
|
||||
print('C2 Load Records:')
|
||||
while offset < 0x8000:
|
||||
hdr = eeprom_read(offset, 4)
|
||||
rec_len = (hdr[0] << 8) | hdr[1]
|
||||
rec_addr = (hdr[2] << 8) | hdr[3]
|
||||
|
||||
if rec_len == 0x8001:
|
||||
print(f' [{len(records)}] END MARKER at EEPROM 0x{offset:04X} → entry=0x{rec_addr:04X}')
|
||||
records.append({'type': 'end', 'offset': offset, 'entry': rec_addr})
|
||||
offset += 4
|
||||
break
|
||||
elif rec_len == 0 or rec_len > 0x4000:
|
||||
print(f' [{len(records)}] INVALID at EEPROM 0x{offset:04X}: len=0x{rec_len:04X}')
|
||||
records.append({'type': 'invalid', 'offset': offset})
|
||||
offset += 4
|
||||
break
|
||||
|
||||
end_addr = rec_addr + rec_len - 1
|
||||
print(f' [{len(records)}] {rec_len:5d} bytes at EEPROM 0x{offset:04X} → RAM 0x{rec_addr:04X}-0x{end_addr:04X}')
|
||||
records.append({'type': 'data', 'offset': offset, 'len': rec_len, 'addr': rec_addr})
|
||||
offset += 4 + rec_len
|
||||
|
||||
print(f'\nFirmware ends at EEPROM offset: 0x{offset:04X}')
|
||||
print()
|
||||
|
||||
# Check what's right after the firmware
|
||||
print(f'--- Data immediately after firmware (0x{offset:04X}) ---')
|
||||
for addr in range(offset, offset + 128, 16):
|
||||
data = eeprom_read(addr, 16)
|
||||
hex_str = ' '.join(f'{b:02X}' for b in data)
|
||||
print(f' {addr:04X}: {hex_str}')
|
||||
|
||||
print()
|
||||
|
||||
# Now check: does PLL data start right after the C2 firmware?
|
||||
print(f'--- PLL block check starting at 0x{offset:04X} ---')
|
||||
for addr in range(offset, offset + 200, 20):
|
||||
block = eeprom_read(addr, 20)
|
||||
count = block[0]
|
||||
if count == 0:
|
||||
print(f' 0x{addr:04X}: [sentinel count=0]')
|
||||
break
|
||||
elif 1 <= count <= 16:
|
||||
ab = block[4:4 + count]
|
||||
print(f' 0x{addr:04X}: count={count} A9=0x{block[1]:02X} AA=0x{block[2]:02X} '
|
||||
f'unused=0x{block[3]:02X} AB=[{ab.hex(" ")}]')
|
||||
else:
|
||||
print(f' 0x{addr:04X}: count=0x{count:02X} (invalid, not PLL data)')
|
||||
break
|
||||
|
||||
print()
|
||||
|
||||
# Also check the known PLL location from the scan
|
||||
print('--- Confirmed PLL data at 0x125C (from EEPROM scan) ---')
|
||||
for addr in range(0x125C, 0x12C0, 20):
|
||||
block = eeprom_read(addr, 20)
|
||||
count = block[0]
|
||||
if count == 0:
|
||||
print(f' 0x{addr:04X}: [sentinel count=0]')
|
||||
break
|
||||
elif 1 <= count <= 16:
|
||||
ab = block[4:4 + count]
|
||||
print(f' 0x{addr:04X}: count={count} A9=0x{block[1]:02X} AA=0x{block[2]:02X} '
|
||||
f'unused=0x{block[3]:02X} AB=[{ab.hex(" ")}]')
|
||||
else:
|
||||
print(f' 0x{addr:04X}: NOT PLL (count=0x{count:02X})')
|
||||
break
|
||||
|
||||
sw.close()
|
||||
print()
|
||||
print('=== Done ===')
|
||||
77
tools/eeprom_sentinel_scan.py
Normal file
77
tools/eeprom_sentinel_scan.py
Normal file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find the count=0 sentinel in the BCM4500 firmware data at 0x4000+."""
|
||||
import sys
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_EEPROM_READ = 0xC0
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
|
||||
|
||||
def ee(addr, length):
|
||||
return sw._vendor_in(CMD_EEPROM_READ, value=addr, index=length, length=length)
|
||||
|
||||
|
||||
print('=== Scanning for count=0 sentinel from 0x4000 ===')
|
||||
print()
|
||||
|
||||
blocks = 0
|
||||
total_bytes = 0
|
||||
addr = 0x4000
|
||||
|
||||
while addr < 0x8000:
|
||||
data = ee(addr, 20)
|
||||
count = data[0]
|
||||
|
||||
if count == 0:
|
||||
print(f' SENTINEL FOUND at 0x{addr:04X} after {blocks} blocks ({total_bytes} payload bytes)')
|
||||
break
|
||||
|
||||
if count > 16:
|
||||
print(f' INVALID count=0x{count:02X} at 0x{addr:04X} after {blocks} blocks')
|
||||
# Show context
|
||||
for ctx_addr in range(max(0x4000, addr - 60), addr + 60, 20):
|
||||
d = ee(ctx_addr, 20)
|
||||
marker = ' ← INVALID' if ctx_addr == addr else ''
|
||||
print(f' 0x{ctx_addr:04X}: count={d[0]:3d} A9=0x{d[1]:02X} AA=0x{d[2]:02X}{marker}')
|
||||
break
|
||||
|
||||
# Valid block
|
||||
a9 = data[1]
|
||||
aa = data[2]
|
||||
ab_bytes = count
|
||||
total_bytes += ab_bytes
|
||||
|
||||
if blocks < 5 or blocks % 100 == 0:
|
||||
ab = data[4:4 + min(count, 8)]
|
||||
print(f' Block {blocks:4d} @ 0x{addr:04X}: count={count:2d} A9=0x{a9:02X} AA=0x{aa:02X} '
|
||||
f'AB=[{ab.hex(" ")}{"..." if count > 8 else ""}]')
|
||||
|
||||
blocks += 1
|
||||
addr += 20
|
||||
|
||||
else:
|
||||
print(f' NO SENTINEL found before 0x8000 ({blocks} blocks scanned)')
|
||||
|
||||
print()
|
||||
print(f'Summary: {blocks} blocks, {total_bytes} payload bytes')
|
||||
print(f'Address range: 0x4000 - 0x{addr:04X}')
|
||||
|
||||
# Show the sentinel and what follows
|
||||
if addr < 0x8000:
|
||||
print()
|
||||
print(f'--- Data around sentinel at 0x{addr:04X} ---')
|
||||
for a in range(max(0x4000, addr - 40), addr + 60, 20):
|
||||
d = ee(a, 20)
|
||||
cnt = d[0]
|
||||
if cnt == 0:
|
||||
print(f' 0x{a:04X}: [SENTINEL count=0] rest: {d[1:].hex(" ")}')
|
||||
elif 1 <= cnt <= 16:
|
||||
print(f' 0x{a:04X}: count={cnt} A9=0x{d[1]:02X} AA=0x{d[2]:02X}')
|
||||
else:
|
||||
print(f' 0x{a:04X}: [non-PLL: 0x{cnt:02X}] {d[:16].hex(" ")}')
|
||||
|
||||
sw.close()
|
||||
print()
|
||||
print('=== Done ===')
|
||||
196
tools/i2c_host_test.py
Normal file
196
tools/i2c_host_test.py
Normal file
@ -0,0 +1,196 @@
|
||||
#!/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()
|
||||
259
tools/i2c_recovery_boot.py
Normal file
259
tools/i2c_recovery_boot.py
Normal file
@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Two-stage boot with I2C slave recovery.
|
||||
|
||||
The FX2LP I2C controller enters a stuck state (I2CS=0xF6) when the CPU
|
||||
restarts after being halted mid-I2C-transaction. The stock firmware was
|
||||
interrupted by fw_load.py's CPUCS halt while an I2C transfer was in
|
||||
progress. The slave (BCM4500/BCM3440) is still driving SDA LOW, waiting
|
||||
for clock pulses to finish its byte.
|
||||
|
||||
When CPUCS goes back to 0:
|
||||
1. I2C pull-ups reconnect
|
||||
2. Controller detects SDA LOW (slave still holding)
|
||||
3. Controller enters permanent BERR state (0xF6)
|
||||
4. All subsequent I2C operations fail
|
||||
|
||||
Fix: two-stage boot process:
|
||||
Stage 1: Upload a tiny slave recovery stub, run it briefly to release
|
||||
the stuck slave (32 SCL pulses @ ~10us + STOP), then halt again.
|
||||
Stage 2: Upload the real firmware. The I2C bus is now clean, so when
|
||||
CPUCS goes to 0, the controller initializes correctly.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import usb.core
|
||||
import usb.util
|
||||
|
||||
I2CS_ADDR = 0xE678
|
||||
I2CTL_ADDR = 0xE67A
|
||||
CPUCS_ADDR = 0xE600
|
||||
|
||||
def fx2_read(dev, addr, n=1):
|
||||
return dev.ctrl_transfer(0xC0, 0xA0, addr, 0, n, timeout=1000)
|
||||
|
||||
def fx2_write(dev, addr, data):
|
||||
dev.ctrl_transfer(0x40, 0xA0, addr, 0, data, timeout=1000)
|
||||
|
||||
def i2cs_str(v):
|
||||
flags = []
|
||||
for bit, name in [(7,'START'),(6,'STOP'),(5,'LASTRD'),
|
||||
(4,'ID1'),(3,'ID0'),(2,'BERR'),(1,'ACK'),(0,'DONE')]:
|
||||
if v & (1 << bit): flags.append(name)
|
||||
return f"0x{v:02X} ({' | '.join(flags) if flags else 'idle'})"
|
||||
|
||||
def build_recovery_stub():
|
||||
"""Build an 8051 stub that holds BCM4500 in reset to release the I2C bus.
|
||||
|
||||
The I2C controller (I2CS=0xF6 BERR) has exclusive control of PA0 (SDA)
|
||||
and PA1 (SCL). GPIO operations on those pins are OVERRIDDEN by the I2C
|
||||
engine — bit-bang SCL clocking has NO EFFECT on the actual bus.
|
||||
|
||||
Strategy: assert BCM4500 RESET (PA5 LOW) and KEEP it asserted. The
|
||||
BCM4500's I2C interface goes high-impedance, releasing SDA. Then when
|
||||
the host halts and loads real firmware, CPUCS restart will find SDA=HIGH
|
||||
and the I2C controller will initialize cleanly (no BERR).
|
||||
|
||||
The real firmware handles BCM_RESET de-assertion during its normal init.
|
||||
|
||||
CRITICAL: Do NOT modify OEA bits 0-1 — the I2C controller owns those.
|
||||
|
||||
Diagnostics in XDATA:
|
||||
0x3C00: 0xAA = stub started
|
||||
0x3C01: initial OEA
|
||||
0x3C02: initial IOA
|
||||
0x3C03: I2CS at boot (via XDATA 0xE678)
|
||||
0x3C04: IOA after asserting BCM4500 reset (~50ms hold)
|
||||
0x3C05: 0xDD = stub complete, looping with BCM4500 held in reset
|
||||
"""
|
||||
code = []
|
||||
|
||||
# 0x0000: LJMP main_start (to 0x0010)
|
||||
code += [0x02, 0x00, 0x10] # LJMP 0x0010
|
||||
|
||||
# 0x0003-0x000F: padding
|
||||
while len(code) < 0x10:
|
||||
code += [0x00]
|
||||
|
||||
# ========== 0x0010: Main code ==========
|
||||
|
||||
# --- Marker: stub started (0xAA → 0x3C00) ---
|
||||
code += [0x74, 0xAA] # MOV A, #0xAA
|
||||
code += [0x90, 0x3C, 0x00] # MOV DPTR, #0x3C00
|
||||
code += [0xF0] # MOVX @DPTR, A
|
||||
|
||||
# --- Capture initial OEA → 0x3C01 ---
|
||||
code += [0xE5, 0xB2] # MOV A, OEA
|
||||
code += [0x90, 0x3C, 0x01] # MOV DPTR, #0x3C01
|
||||
code += [0xF0] # MOVX @DPTR, A
|
||||
|
||||
# --- Capture initial IOA → 0x3C02 ---
|
||||
code += [0xE5, 0x80] # MOV A, IOA
|
||||
code += [0x90, 0x3C, 0x02] # MOV DPTR, #0x3C02
|
||||
code += [0xF0] # MOVX @DPTR, A
|
||||
|
||||
# --- Capture initial I2CS → 0x3C03 ---
|
||||
code += [0x90, 0xE6, 0x78] # MOV DPTR, #0xE678
|
||||
code += [0xE0] # MOVX A, @DPTR
|
||||
code += [0x90, 0x3C, 0x03] # MOV DPTR, #0x3C03
|
||||
code += [0xF0] # MOVX @DPTR, A
|
||||
|
||||
# --- Assert BCM4500 RESET: PA5 LOW, make output ---
|
||||
# PA5 = IOA bit 5, bit address 0x85 (IOA base 0x80 + 5)
|
||||
# ORL OEA with only bit 5 — do NOT touch bits 0-1 (I2C engine)
|
||||
code += [0xC2, 0x85] # CLR PA5 (assert reset)
|
||||
code += [0x43, 0xB2, 0x20] # ORL OEA, #0x20 (PA5 = output)
|
||||
|
||||
# Hold reset for ~50ms
|
||||
# R2=250, R1=240: 250*240*3 = 180,000 cycles = ~3.75ms
|
||||
# R0=15: 15 * 3.75ms = ~56ms
|
||||
code += [0x78, 15] # MOV R0, #15
|
||||
reset_outer = len(code)
|
||||
code += [0x7A, 250] # MOV R2, #250
|
||||
reset_mid = len(code)
|
||||
code += [0x79, 240] # MOV R1, #240
|
||||
code += [0xD9, 0xFE] # DJNZ R1, $-2
|
||||
djnz2_pc = len(code) + 2
|
||||
code += [0xDA, (reset_mid - djnz2_pc) & 0xFF] # DJNZ R2, reset_mid
|
||||
djnz0_pc = len(code) + 2
|
||||
code += [0xD8, (reset_outer - djnz0_pc) & 0xFF] # DJNZ R0, reset_outer
|
||||
|
||||
# --- Capture IOA during reset → 0x3C04 ---
|
||||
code += [0xE5, 0x80] # MOV A, IOA
|
||||
code += [0x90, 0x3C, 0x04] # MOV DPTR, #0x3C04
|
||||
code += [0xF0] # MOVX @DPTR, A
|
||||
|
||||
# --- Marker: stub complete (0xDD → 0x3C05) ---
|
||||
# BCM4500 stays in reset! Do NOT de-assert.
|
||||
code += [0x74, 0xDD] # MOV A, #0xDD
|
||||
code += [0x90, 0x3C, 0x05] # MOV DPTR, #0x3C05
|
||||
code += [0xF0] # MOVX @DPTR, A
|
||||
|
||||
# Infinite loop — BCM4500 held in reset
|
||||
code += [0x80, 0xFE] # SJMP $ (loop forever)
|
||||
|
||||
return bytes(code)
|
||||
|
||||
|
||||
def main():
|
||||
dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203)
|
||||
if not dev:
|
||||
print("SkyWalker-1 not found")
|
||||
sys.exit(1)
|
||||
|
||||
print("Two-Stage Boot: I2C Slave Recovery (v2 — with markers)")
|
||||
print("=" * 55)
|
||||
|
||||
# Stage 0: Initial state
|
||||
print(f"\n[0] Device found: Bus {dev.bus} Addr {dev.address}")
|
||||
|
||||
# Stage 1: Halt CPU
|
||||
print("\n[1] Halting CPU...")
|
||||
fx2_write(dev, CPUCS_ADDR, bytes([0x01]))
|
||||
time.sleep(0.05)
|
||||
i2cs = fx2_read(dev, I2CS_ADDR, 1)[0]
|
||||
print(f" I2CS during halt = {i2cs_str(i2cs)}")
|
||||
|
||||
# Clear XDATA diagnostic area first (so stale data doesn't confuse us)
|
||||
print(" Clearing XDATA 0x3C00-0x3C0F...")
|
||||
fx2_write(dev, 0x3C00, bytes([0x00] * 16))
|
||||
|
||||
# Stage 2: Upload stub
|
||||
print("\n[2] Uploading slave recovery stub...")
|
||||
stub = build_recovery_stub()
|
||||
print(f" Stub size: {len(stub)} bytes")
|
||||
fx2_write(dev, 0x0000, stub)
|
||||
|
||||
# Verify upload by reading back first 16 bytes
|
||||
readback = fx2_read(dev, 0x0000, 16)
|
||||
match = all(readback[i] == stub[i] for i in range(16))
|
||||
print(f" Upload verify (first 16): {'MATCH' if match else 'MISMATCH'}")
|
||||
if not match:
|
||||
print(f" Expected: {' '.join(f'{b:02X}' for b in stub[:16])}")
|
||||
print(f" Got: {' '.join(f'{b:02X}' for b in readback)}")
|
||||
|
||||
# Stage 3: Run stub (~50ms reset hold + ~100ms settle + margin)
|
||||
print("\n[3] Running stub (500ms)...")
|
||||
fx2_write(dev, CPUCS_ADDR, bytes([0x00]))
|
||||
time.sleep(0.5)
|
||||
|
||||
# Stage 4: Halt and read diagnostics
|
||||
print("\n[4] Halting CPU after recovery...")
|
||||
fx2_write(dev, CPUCS_ADDR, bytes([0x01]))
|
||||
time.sleep(0.05)
|
||||
|
||||
# Read all diagnostic bytes
|
||||
diag = fx2_read(dev, 0x3C00, 8)
|
||||
i2cs_halt = fx2_read(dev, I2CS_ADDR, 1)[0]
|
||||
|
||||
print(f"\n Raw XDATA 0x3C00-0x3C07:")
|
||||
print(f" {' '.join(f'{b:02X}' for b in diag)}")
|
||||
|
||||
# Decode diagnostics
|
||||
marker_start = diag[0] # 0x3C00: expect 0xAA
|
||||
oea_init = diag[1] # 0x3C01: initial OEA
|
||||
ioa_init = diag[2] # 0x3C02: initial IOA
|
||||
i2cs_init = diag[3] # 0x3C03: initial I2CS
|
||||
ioa_during_rst = diag[4] # 0x3C04: IOA during BCM4500 reset
|
||||
marker_done = diag[5] # 0x3C05: expect 0xDD
|
||||
|
||||
def bus_str(ioa):
|
||||
sda = 'H' if ioa & 1 else 'L'
|
||||
scl = 'H' if ioa & 2 else 'L'
|
||||
rst = 'H' if ioa & 0x20 else 'L'
|
||||
return f"SDA={sda} SCL={scl} RST={rst}"
|
||||
|
||||
print(f"\n Markers:")
|
||||
print(f" Start (0xAA?): 0x{marker_start:02X} {'✓' if marker_start == 0xAA else '✗ STUB DID NOT EXECUTE'}")
|
||||
print(f" Done (0xDD?): 0x{marker_done:02X} {'✓' if marker_done == 0xDD else '✗'}")
|
||||
|
||||
if marker_start != 0xAA:
|
||||
print(f"\n FATAL: Stub never started!")
|
||||
else:
|
||||
print(f"\n Initial state:")
|
||||
print(f" OEA = 0x{oea_init:02X}")
|
||||
print(f" IOA = 0x{ioa_init:02X} {bus_str(ioa_init)}")
|
||||
print(f" I2CS = {i2cs_str(i2cs_init)}")
|
||||
|
||||
print(f"\n BCM4500 held in RESET (~50ms):")
|
||||
print(f" IOA = 0x{ioa_during_rst:02X} {bus_str(ioa_during_rst)}")
|
||||
sda_rst = 'H' if ioa_during_rst & 1 else 'L'
|
||||
if sda_rst == 'H':
|
||||
print(f" ✓ SDA HIGH with BCM4500 in reset")
|
||||
else:
|
||||
print(f" ✗ SDA still LOW — not the BCM4500 holding it")
|
||||
|
||||
print(f"\n I2CS during halt: {i2cs_str(i2cs_halt)}")
|
||||
print(f" BCM4500 is held in RESET (PA5 driven LOW)")
|
||||
|
||||
# Stage 5: Now load real firmware with BCM4500 still in reset
|
||||
# The real firmware's init sequence will de-assert BCM_RESET.
|
||||
# Since SDA is HIGH (slave in reset), the I2C controller should
|
||||
# initialize cleanly when CPUCS restarts for the real firmware.
|
||||
print(f"\n[5] BCM4500 held in reset. Loading real firmware...")
|
||||
print(f" The CPUCS restart for firmware load will re-init I2C controller.")
|
||||
print(f" With BCM4500 in reset (SDA=HIGH), I2CS should come up clean.")
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
fw_path = os.path.join(os.path.dirname(__file__), '..', 'firmware', 'build', 'skywalker1.ihx')
|
||||
fw_path = os.path.abspath(fw_path)
|
||||
fw_load = os.path.join(os.path.dirname(__file__), 'fw_load.py')
|
||||
|
||||
if os.path.exists(fw_path):
|
||||
print(f"\n Running: python3 {fw_load} load {fw_path} --wait 5")
|
||||
result = subprocess.run(
|
||||
['python3', fw_load, 'load', fw_path, '--wait', '5'],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
print(result.stdout)
|
||||
if result.stderr:
|
||||
print(f" stderr: {result.stderr}")
|
||||
else:
|
||||
print(f"\n Firmware not found: {fw_path}")
|
||||
print(f" Build with: cd firmware && make")
|
||||
print(f" Then run: python3 tools/fw_load.py load {fw_path} --wait 5")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
126
tools/i2c_register_test.py
Normal file
126
tools/i2c_register_test.py
Normal file
@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Quick diagnostic: test if 0xA0 writes to I2C registers trigger hardware."""
|
||||
import usb.core, usb.util, time
|
||||
|
||||
VID, PID = 0x09C0, 0x0203
|
||||
A0 = 0xA0
|
||||
I2CS = 0xE678; I2DAT = 0xE679; I2CTL = 0xE67A; CPUCS = 0xE600
|
||||
|
||||
dev = usb.core.find(idVendor=VID, idProduct=PID)
|
||||
for cfg in dev:
|
||||
for intf in cfg:
|
||||
if dev.is_kernel_driver_active(intf.bInterfaceNumber):
|
||||
dev.detach_kernel_driver(intf.bInterfaceNumber)
|
||||
try:
|
||||
dev.set_configuration()
|
||||
except:
|
||||
pass
|
||||
|
||||
def a0r(addr, n=1):
|
||||
return bytes(dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, A0, addr, 0, n, 2000))
|
||||
|
||||
def a0w(addr, val):
|
||||
dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT, A0, addr, 0, bytes([val]), 2000)
|
||||
|
||||
# Pre-halt flush
|
||||
try:
|
||||
lock = dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN, 0x90, 0, 0, 1, 2000)
|
||||
print(f"Pre-halt flush (0x90): 0x{lock[0]:02X}")
|
||||
except Exception as e:
|
||||
print(f"Flush failed: {e}")
|
||||
|
||||
# Halt
|
||||
a0w(CPUCS, 0x01)
|
||||
time.sleep(0.01)
|
||||
print(f"CPUCS after halt: 0x{a0r(CPUCS)[0]:02X}")
|
||||
|
||||
print(f"\n=== Register State After Halt ===")
|
||||
i2cs_orig = a0r(I2CS)[0]
|
||||
i2ctl_orig = a0r(I2CTL)[0]
|
||||
print(f"I2CS: 0x{i2cs_orig:02X}")
|
||||
print(f"I2CTL: 0x{i2ctl_orig:02X}")
|
||||
|
||||
# Test 1: I2CTL writability
|
||||
print(f"\n=== Test 1: I2CTL writability ===")
|
||||
print(f"I2CTL before: 0x{a0r(I2CTL)[0]:02X}")
|
||||
a0w(I2CTL, 0x00)
|
||||
time.sleep(0.001)
|
||||
v = a0r(I2CTL)[0]
|
||||
print(f"I2CTL after 0x00 write: 0x{v:02X} {'CHANGED' if v == 0x00 else 'unchanged'}")
|
||||
a0w(I2CTL, 0x01)
|
||||
time.sleep(0.001)
|
||||
v = a0r(I2CTL)[0]
|
||||
print(f"I2CTL after 0x01 write: 0x{v:02X}")
|
||||
|
||||
# Test 2: Write START to I2CS, read back
|
||||
print(f"\n=== Test 2: I2CS START ===")
|
||||
print(f"I2CS before START: 0x{a0r(I2CS)[0]:02X}")
|
||||
a0w(I2CS, 0x80) # bmSTART
|
||||
for i in range(5):
|
||||
val = a0r(I2CS)[0]
|
||||
print(f" I2CS read {i}: 0x{val:02X}")
|
||||
time.sleep(0.002)
|
||||
|
||||
# Test 3: Write to I2DAT
|
||||
print(f"\n=== Test 3: I2DAT write (EEPROM addr) ===")
|
||||
print(f"I2CS before I2DAT write: 0x{a0r(I2CS)[0]:02X}")
|
||||
a0w(I2DAT, 0xA2) # EEPROM 0x51<<1
|
||||
for i in range(5):
|
||||
val = a0r(I2CS)[0]
|
||||
print(f" I2CS read {i}: 0x{val:02X}")
|
||||
time.sleep(0.002)
|
||||
|
||||
# Test 4: Read I2DAT
|
||||
print(f"\n=== Test 4: I2DAT read ===")
|
||||
dat = a0r(I2DAT)[0]
|
||||
print(f"I2DAT read: 0x{dat:02X}")
|
||||
print(f"I2CS after I2DAT read: 0x{a0r(I2CS)[0]:02X}")
|
||||
|
||||
# Test 5: Write STOP
|
||||
print(f"\n=== Test 5: I2CS STOP ===")
|
||||
a0w(I2CS, 0x40) # bmSTOP
|
||||
for i in range(3):
|
||||
val = a0r(I2CS)[0]
|
||||
print(f" I2CS read {i}: 0x{val:02X}")
|
||||
time.sleep(0.002)
|
||||
|
||||
# Test 6: Write arbitrary values to I2CS
|
||||
print(f"\n=== Test 6: I2CS raw write/read ===")
|
||||
for test_val in [0x00, 0xFF, 0x04, 0x80]:
|
||||
a0w(I2CS, test_val)
|
||||
time.sleep(0.001)
|
||||
rb = a0r(I2CS)[0]
|
||||
changed = "CHANGED" if rb != i2cs_orig else "unchanged"
|
||||
print(f" Wrote 0x{test_val:02X}, read 0x{rb:02X} {changed}")
|
||||
|
||||
# Test 7: XDATA RAM write/read (control test)
|
||||
print(f"\n=== Test 7: XDATA RAM control test ===")
|
||||
TEST_ADDR = 0x3C00
|
||||
orig = a0r(TEST_ADDR)[0]
|
||||
a0w(TEST_ADDR, 0xBE)
|
||||
time.sleep(0.001)
|
||||
readback = a0r(TEST_ADDR)[0]
|
||||
ok = "OK" if readback == 0xBE else "FAIL"
|
||||
print(f"RAM[0x3C00]: wrote 0xBE, read 0x{readback:02X} {ok}")
|
||||
|
||||
# Test 8: Try writing 0x04 to I2CS (BERR clear bit)
|
||||
print(f"\n=== Test 8: BERR clear bit ===")
|
||||
a0w(I2CS, 0x04)
|
||||
time.sleep(0.001)
|
||||
v = a0r(I2CS)[0]
|
||||
print(f"After writing 0x04: I2CS=0x{v:02X}")
|
||||
|
||||
# Summary
|
||||
print(f"\n=== Summary ===")
|
||||
final_i2cs = a0r(I2CS)[0]
|
||||
print(f"Final I2CS: 0x{final_i2cs:02X} (started at 0x{i2cs_orig:02X})")
|
||||
if final_i2cs == i2cs_orig:
|
||||
print("I2CS NEVER CHANGED -- host-side I2C register writes ignored by hardware")
|
||||
print("\nImplication: 0xA0 writes reach XDATA address space but the I2C")
|
||||
print("controller only responds to 8051 MOVX instructions, not USB engine writes.")
|
||||
print("The boot ROM's 0xA0 handler uses a different bus path for register access.")
|
||||
else:
|
||||
print("I2CS DID CHANGE -- host-side I2C register writes may work!")
|
||||
176
tools/indirect_loopback_test.py
Normal file
176
tools/indirect_loopback_test.py
Normal file
@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""BCM4500 indirect register loopback test.
|
||||
|
||||
Write a known value via indirect write, then read it back.
|
||||
Tests multiple approaches:
|
||||
1. Multi-byte A6+A7+A8 in one transaction (0xB2 uses bcm_indirect_write)
|
||||
2. Separate writes via 0xB6 diagnostic
|
||||
3. Read via 0xB6 with various delays
|
||||
4. Read using 0xB1 (bcm_indirect_read wrapper)
|
||||
|
||||
If we can write-then-read a value back, the DSP command processor works.
|
||||
If not, we need to look at the I2C transaction structure more carefully.
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
sys.path.insert(0, 'tools')
|
||||
from skywalker_lib import SkyWalker1
|
||||
|
||||
CMD_RAW_DEMOD_READ = 0xB1
|
||||
CMD_RAW_DEMOD_WRITE = 0xB2
|
||||
CMD_I2C_RAW_READ = 0xB5
|
||||
CMD_I2C_DIAG = 0xB6
|
||||
BCM4500_ADDR = 0x08
|
||||
|
||||
sw = SkyWalker1()
|
||||
sw.open()
|
||||
print('=== BCM4500 Indirect Register Loopback Test ===')
|
||||
print(f'Firmware: {sw.get_fw_version()}')
|
||||
|
||||
# Boot with full sequence
|
||||
print('\n--- Booting BCM4500 (full boot) ---')
|
||||
result = sw._vendor_in(0x89, value=1, index=0, length=3)
|
||||
cfg, stage = result[0], result[1]
|
||||
print(f' Config: 0x{cfg:02X}, Stage: 0x{stage:02X}')
|
||||
time.sleep(0.1)
|
||||
|
||||
# ============================================================
|
||||
# Test 1: Read default indirect register values
|
||||
# ============================================================
|
||||
print('\n=== Test 1: Default indirect register values (0xB1) ===')
|
||||
for page in [0x00, 0x01, 0x06, 0x07, 0x0A, 0x0F]:
|
||||
try:
|
||||
data = sw._vendor_in(CMD_RAW_DEMOD_READ, value=page, index=0, length=1)
|
||||
print(f' Page 0x{page:02X}: 0x{data[0]:02X}')
|
||||
except Exception as e:
|
||||
print(f' Page 0x{page:02X}: FAILED ({e})')
|
||||
|
||||
# ============================================================
|
||||
# Test 2: Write via 0xB2 (multi-byte A6+A7+A8), then read via 0xB1
|
||||
# ============================================================
|
||||
print('\n=== Test 2: Write 0x42 to page 0x00, read back (0xB2 write, 0xB1 read) ===')
|
||||
try:
|
||||
# 0xB2: bcm_indirect_write(page=0x00, val=0x42)
|
||||
# Writes A6=0x00, A7=0x42, A8=0x03 in one 3-byte I2C transaction
|
||||
sw._vendor_in(CMD_RAW_DEMOD_WRITE, value=0x00, index=0x42, length=0)
|
||||
except Exception:
|
||||
pass # May not return data
|
||||
|
||||
time.sleep(0.05)
|
||||
|
||||
# Read back via 0xB1 (bcm_indirect_read with 1ms delay)
|
||||
try:
|
||||
data = sw._vendor_in(CMD_RAW_DEMOD_READ, value=0x00, index=0, length=1)
|
||||
print(f' Read back page 0x00: 0x{data[0]:02X}')
|
||||
if data[0] == 0x42:
|
||||
print(' >> LOOPBACK SUCCESS! DSP is processing commands!')
|
||||
elif data[0] == 0x00:
|
||||
print(' >> Got 0x00 — either DSP not running or write/read protocol broken')
|
||||
else:
|
||||
print(f' >> Unexpected: 0x{data[0]:02X}')
|
||||
except Exception as e:
|
||||
print(f' Read failed: {e}')
|
||||
|
||||
# ============================================================
|
||||
# Test 3: Direct register reads before/after indirect write
|
||||
# ============================================================
|
||||
print('\n=== Test 3: Direct register state before/after indirect commands ===')
|
||||
print(' Before indirect write:')
|
||||
for reg in [0xA6, 0xA7, 0xA8]:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=reg, length=1)
|
||||
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
|
||||
|
||||
# Write via 0xB2
|
||||
try:
|
||||
sw._vendor_in(CMD_RAW_DEMOD_WRITE, value=0x06, index=0xAB, length=0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
time.sleep(0.01)
|
||||
print(' After indirect write (page=0x06, data=0xAB):')
|
||||
for reg in [0xA6, 0xA7, 0xA8]:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=reg, length=1)
|
||||
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
|
||||
|
||||
# ============================================================
|
||||
# Test 4: Manual step-by-step with individual I2C writes
|
||||
# ============================================================
|
||||
print('\n=== Test 4: Manual indirect read using individual raw I2C writes ===')
|
||||
print(' Writing A6=0x06 via direct I2C write...')
|
||||
|
||||
# We don't have a raw I2C write command, but we can use the 0xB6 diagnostic
|
||||
# which does individual writes and reads for us.
|
||||
|
||||
# Test 4a: 0xB6 with READ command (wIndex=0x01)
|
||||
print('\n 4a: 0xB6 diagnostic — READ page 0x06:')
|
||||
diag = sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0x01, length=8)
|
||||
print(f' A6 write ok: {diag[0]}')
|
||||
print(f' A6 readback: 0x{diag[1]:02X}')
|
||||
print(f' A8 write ok: {diag[2]}')
|
||||
print(f' A8 immediate: 0x{diag[3]:02X}')
|
||||
print(f' A8 after 2ms: 0x{diag[4]:02X}')
|
||||
print(f' A7 data: 0x{diag[5]:02X}')
|
||||
print(f' A6 final: 0x{diag[6]:02X}')
|
||||
|
||||
# ============================================================
|
||||
# Test 5: Try reading A7 with longer delays
|
||||
# ============================================================
|
||||
print('\n=== Test 5: Indirect read with longer delays ===')
|
||||
print(' Maybe the DSP needs more time to process the command...')
|
||||
|
||||
# Use 0xB6 to write A6=0x06 and A8=0x01
|
||||
sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0x01, length=8)
|
||||
|
||||
for delay_ms in [10, 50, 100, 500]:
|
||||
time.sleep(delay_ms / 1000.0)
|
||||
# Read A7 via raw I2C
|
||||
try:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=0xA7, length=1)
|
||||
print(f' After {delay_ms:4d}ms: A7=0x{data[0]:02X}')
|
||||
except Exception as e:
|
||||
print(f' After {delay_ms:4d}ms: FAILED ({e})')
|
||||
|
||||
# ============================================================
|
||||
# Test 6: Direct register dump after all tests
|
||||
# ============================================================
|
||||
print('\n=== Test 6: Register state after all tests ===')
|
||||
print(' A0-AF:')
|
||||
for reg in range(0xA0, 0xB0):
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=reg, length=1)
|
||||
print(f' 0x{reg:02X}=0x{data[0]:02X}', end='')
|
||||
if (reg % 8) == 7:
|
||||
print()
|
||||
print()
|
||||
|
||||
# ============================================================
|
||||
# Test 7: Power cycle BCM4500 and check pre-command register state
|
||||
# ============================================================
|
||||
print('\n=== Test 7: Reboot and check BEFORE any indirect commands ===')
|
||||
sw._vendor_in(0x89, value=0, index=0, length=3) # shutdown
|
||||
time.sleep(0.5)
|
||||
sw._vendor_in(0x89, value=1, index=0, length=3) # full boot
|
||||
time.sleep(0.1)
|
||||
print(' Fresh boot — direct reg reads (no indirect commands issued):')
|
||||
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB]:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=reg, length=1)
|
||||
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
|
||||
|
||||
# Now issue ONE indirect read command and check if registers change
|
||||
print('\n After ONE indirect read (page 0x06):')
|
||||
diag = sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0x01, length=8)
|
||||
print(f' A7 data: 0x{diag[5]:02X} (this is the indirect read result)')
|
||||
|
||||
# Check if direct registers changed
|
||||
print(' Direct register check after indirect command:')
|
||||
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8]:
|
||||
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
|
||||
index=reg, length=1)
|
||||
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
|
||||
|
||||
sw.close()
|
||||
print('\n=== Done ===')
|
||||
210
tools/stock_fw_compare.py
Normal file
210
tools/stock_fw_compare.py
Normal file
@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare BCM4500 register behavior: stock EEPROM firmware vs custom firmware.
|
||||
|
||||
USAGE:
|
||||
1. Power cycle the SkyWalker-1 (unplug/replug USB)
|
||||
2. Run this script IMMEDIATELY (before loading custom FW)
|
||||
3. The script tests registers under stock firmware first,
|
||||
then loads custom firmware and tests again.
|
||||
|
||||
If stock firmware shows different register behavior, the issue is
|
||||
in our custom firmware's boot sequence.
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
import usb.core
|
||||
sys.path.insert(0, 'tools')
|
||||
|
||||
VID = 0x09C0
|
||||
PID = 0x0203
|
||||
BCM4500_ADDR = 0x08
|
||||
|
||||
# ============================================================
|
||||
# Raw USB helpers (work with any firmware)
|
||||
# ============================================================
|
||||
def vendor_in(dev, cmd, value=0, index=0, length=1):
|
||||
"""Send a vendor IN request and return the response bytes."""
|
||||
return dev.ctrl_transfer(
|
||||
0xC0, # bmRequestType: vendor, device-to-host
|
||||
cmd, # bRequest
|
||||
value, # wValue
|
||||
index, # wIndex
|
||||
length # wLength
|
||||
)
|
||||
|
||||
def vendor_out(dev, cmd, value=0, index=0, data=None):
|
||||
"""Send a vendor OUT request."""
|
||||
dev.ctrl_transfer(
|
||||
0x40, # bmRequestType: vendor, host-to-device
|
||||
cmd, # bRequest
|
||||
value, # wValue
|
||||
index, # wIndex
|
||||
data if data else b''
|
||||
)
|
||||
|
||||
def read_bcm_reg(dev, reg):
|
||||
"""Read one BCM4500 register via stock-compatible I2C read (0xB5).
|
||||
This might not exist on stock firmware, so we use the
|
||||
stock READ_8PSK_REG (0x81) as a fallback."""
|
||||
try:
|
||||
data = vendor_in(dev, 0xB5, value=BCM4500_ADDR, index=reg, length=1)
|
||||
return data[0]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def read_bcm_reg_stock(dev, reg):
|
||||
"""Read BCM4500 register via stock READ_8PSK_REG (0x81).
|
||||
wValue = register address, returns 1 byte."""
|
||||
try:
|
||||
data = vendor_in(dev, 0x81, value=reg, index=0, length=1)
|
||||
return data[0]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def read_all_key_regs(dev, method='0xB5'):
|
||||
"""Read key BCM4500 registers."""
|
||||
regs = [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB]
|
||||
results = {}
|
||||
for reg in regs:
|
||||
if method == '0xB5':
|
||||
val = read_bcm_reg(dev, reg)
|
||||
else:
|
||||
val = read_bcm_reg_stock(dev, reg)
|
||||
results[reg] = val
|
||||
v_str = f'0x{val:02X}' if val is not None else 'FAIL'
|
||||
print(f' 0x{reg:02X}: {v_str}')
|
||||
return results
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
print('=== Stock vs Custom Firmware BCM4500 Comparison ===')
|
||||
print()
|
||||
|
||||
# Find the device
|
||||
dev = usb.core.find(idVendor=VID, idProduct=PID)
|
||||
if dev is None:
|
||||
print('ERROR: SkyWalker-1 not found!')
|
||||
print('Make sure to power cycle the device first (stock firmware must be running)')
|
||||
sys.exit(1)
|
||||
|
||||
print(f'Found SkyWalker-1: Bus {dev.bus} Addr {dev.address}')
|
||||
print(f' VID=0x{dev.idVendor:04X} PID=0x{dev.idProduct:04X}')
|
||||
|
||||
# Try to get firmware version (our custom command)
|
||||
try:
|
||||
fw = vendor_in(dev, 0x80, value=0, index=0, length=3)
|
||||
print(f' Firmware response (0x80): {list(fw)}')
|
||||
except Exception as e:
|
||||
print(f' Firmware version (0x80): {e}')
|
||||
|
||||
# ============================================================
|
||||
# Phase 1: Test under stock firmware (before boot command)
|
||||
# ============================================================
|
||||
print('\n' + '='*60)
|
||||
print('PHASE 1: Stock firmware — BEFORE boot command')
|
||||
print('='*60)
|
||||
|
||||
print('\n Registers via 0x81 (READ_8PSK_REG):')
|
||||
regs_stock_pre_81 = {}
|
||||
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8]:
|
||||
val = read_bcm_reg_stock(dev, reg)
|
||||
regs_stock_pre_81[reg] = val
|
||||
v_str = f'0x{val:02X}' if val is not None else 'FAIL'
|
||||
print(f' 0x{reg:02X}: {v_str}')
|
||||
|
||||
print('\n Registers via 0xB5 (I2C_RAW_READ) — may fail on stock FW:')
|
||||
regs_stock_pre_b5 = {}
|
||||
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8]:
|
||||
val = read_bcm_reg(dev, reg)
|
||||
regs_stock_pre_b5[reg] = val
|
||||
v_str = f'0x{val:02X}' if val is not None else 'N/A (stock FW lacks 0xB5)'
|
||||
print(f' 0x{reg:02X}: {v_str}')
|
||||
|
||||
# ============================================================
|
||||
# Phase 2: Boot BCM4500 under stock firmware
|
||||
# ============================================================
|
||||
print('\n' + '='*60)
|
||||
print('PHASE 2: Stock firmware — boot BCM4500 (0x89)')
|
||||
print('='*60)
|
||||
|
||||
try:
|
||||
result = vendor_in(dev, 0x89, value=1, index=0, length=3)
|
||||
print(f' Boot result: [{result[0]:02X}, {result[1]:02X}, {result[2]:02X}]')
|
||||
cfg = result[0]
|
||||
bits = []
|
||||
if cfg & 0x01: bits.append('Started')
|
||||
if cfg & 0x02: bits.append('FW_Loaded')
|
||||
if cfg & 0x04: bits.append('Intersil')
|
||||
if cfg & 0x08: bits.append('DVBmode')
|
||||
print(f' Config: 0x{cfg:02X} ({" | ".join(bits) if bits else "none"})')
|
||||
print(f' Stage: 0x{result[1]:02X}')
|
||||
except Exception as e:
|
||||
print(f' Boot failed: {e}')
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
print('\n Registers via 0x81 after boot:')
|
||||
regs_stock_post_81 = {}
|
||||
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB]:
|
||||
val = read_bcm_reg_stock(dev, reg)
|
||||
regs_stock_post_81[reg] = val
|
||||
v_str = f'0x{val:02X}' if val is not None else 'FAIL'
|
||||
marker = ''
|
||||
if val is not None and reg in regs_stock_pre_81:
|
||||
if regs_stock_pre_81.get(reg) != val:
|
||||
marker = f' (was 0x{regs_stock_pre_81[reg]:02X})' if regs_stock_pre_81[reg] is not None else ''
|
||||
print(f' 0x{reg:02X}: {v_str}{marker}')
|
||||
|
||||
# Try indirect read via stock firmware's 0x81 with indirect addressing
|
||||
# (0x81 might support indirect reads differently)
|
||||
print('\n Testing indirect register reads via 0x81:')
|
||||
for page in [0x00, 0x06, 0x07, 0x0F]:
|
||||
# Stock firmware might use a different convention for indirect reads
|
||||
# Try reading page register values
|
||||
val = read_bcm_reg_stock(dev, page)
|
||||
v_str = f'0x{val:02X}' if val is not None else 'FAIL'
|
||||
print(f' Page 0x{page:02X} via 0x81: {v_str}')
|
||||
|
||||
# Try signal monitor
|
||||
print('\n Signal status:')
|
||||
try:
|
||||
sig = vendor_in(dev, 0x82, value=0, index=0, length=4)
|
||||
print(f' GET_8PSK_SIGNAL (0x82): {list(sig)}')
|
||||
except Exception as e:
|
||||
print(f' 0x82: {e}')
|
||||
|
||||
try:
|
||||
lock = vendor_in(dev, 0x83, value=0, index=0, length=1)
|
||||
print(f' GET_8PSK_LOCK (0x83): 0x{lock[0]:02X}')
|
||||
except Exception as e:
|
||||
print(f' 0x83: {e}')
|
||||
|
||||
# ============================================================
|
||||
# Summary
|
||||
# ============================================================
|
||||
print('\n' + '='*60)
|
||||
print('SUMMARY')
|
||||
print('='*60)
|
||||
|
||||
def compare_regs(label, regs):
|
||||
vals = set(v for v in regs.values() if v is not None)
|
||||
if len(vals) == 1:
|
||||
print(f' {label}: ALL = 0x{list(vals)[0]:02X}')
|
||||
elif len(vals) == 0:
|
||||
print(f' {label}: ALL FAILED')
|
||||
else:
|
||||
print(f' {label}: Mixed: {", ".join(f"0x{v:02X}" for v in sorted(vals))}')
|
||||
|
||||
compare_regs('Stock FW before boot (0x81)', regs_stock_pre_81)
|
||||
compare_regs('Stock FW after boot (0x81)', regs_stock_post_81)
|
||||
|
||||
print()
|
||||
print('If stock FW shows DIFFERENT register values (not all 0x02),')
|
||||
print('then the BCM4500 is truly functional under stock FW and our')
|
||||
print('custom boot sequence is missing something.')
|
||||
print()
|
||||
print('If stock FW also shows all 0x02, then the register behavior')
|
||||
print('is normal and the 0x02 IS the legitimate power-on value.')
|
||||
|
||||
print('\n=== Done ===')
|
||||
342
tools/stock_fw_test.py
Normal file
342
tools/stock_fw_test.py
Normal file
@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Stock firmware BCM4500 boot diagnostic.
|
||||
|
||||
Runs the stock v2.06 firmware boot sequence (same as kernel gp8psk driver),
|
||||
then performs comprehensive register dumps to capture the BCM4500 state —
|
||||
including the critical A9/AA/AB PLL registers written by FUN_CODE_10F2
|
||||
(which our custom firmware currently skips).
|
||||
|
||||
Also scans the I2C bus to find device 0x51 (calibration EEPROM) that the
|
||||
stock firmware reads PLL configuration data from.
|
||||
|
||||
Usage:
|
||||
python tools/stock_fw_test.py [--dump-all] [--i2c-scan]
|
||||
|
||||
Requirements:
|
||||
- SkyWalker-1 connected via USB (stock firmware loaded)
|
||||
- pyusb installed
|
||||
"""
|
||||
|
||||
import sys
|
||||
import struct
|
||||
|
||||
try:
|
||||
import usb.core
|
||||
import usb.util
|
||||
except ImportError:
|
||||
print("pyusb required: pip install pyusb")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# USB IDs
|
||||
VID = 0x09C0
|
||||
PID = 0x0203
|
||||
|
||||
# Stock firmware vendor commands (same as kernel driver)
|
||||
GET_8PSK_CONFIG = 0x80
|
||||
I2C_WRITE = 0x83
|
||||
I2C_READ = 0x84
|
||||
ARM_TRANSFER = 0x85
|
||||
TUNE_8PSK = 0x86
|
||||
GET_SIGNAL_STRENGTH = 0x87
|
||||
BOOT_8PSK = 0x89
|
||||
START_INTERSIL = 0x8A
|
||||
SET_LNB_VOLTAGE = 0x8B
|
||||
GET_SIGNAL_LOCK = 0x90
|
||||
GET_FW_VERS = 0x92
|
||||
|
||||
# Config status bits
|
||||
BM_STARTED = 0x01
|
||||
BM_FW_LOADED = 0x02
|
||||
BM_INTERSIL = 0x04
|
||||
|
||||
# I2C addresses (7-bit)
|
||||
BCM4500_ADDR = 0x08
|
||||
BCM3440_ADDR = 0x10
|
||||
EEPROM_ADDR = 0x51 # Calibration EEPROM found in FUN_CODE_10F2 disassembly
|
||||
|
||||
|
||||
def vendor_in(dev, cmd, value=0, index=0, length=1):
|
||||
"""Send a vendor IN control transfer (device -> host)."""
|
||||
return bytes(dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN,
|
||||
cmd, value, index, length, 2000))
|
||||
|
||||
|
||||
def vendor_out(dev, cmd, value=0, index=0, data=None):
|
||||
"""Send a vendor OUT control transfer (host -> device)."""
|
||||
dev.ctrl_transfer(
|
||||
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
|
||||
cmd, value, index, data or b'', 2000)
|
||||
|
||||
|
||||
def decode_config(cfg):
|
||||
"""Decode config status byte to human-readable string."""
|
||||
bits = []
|
||||
names = [
|
||||
(0x01, "Started"), (0x02, "FW_Loaded"), (0x04, "Intersil"),
|
||||
(0x08, "DVBmode"), (0x10, "22kHz"), (0x20, "18V"),
|
||||
(0x40, "DCtuned"), (0x80, "Armed"),
|
||||
]
|
||||
for mask, name in names:
|
||||
if cfg & mask:
|
||||
bits.append(name)
|
||||
return " | ".join(bits) if bits else "(none)"
|
||||
|
||||
|
||||
def i2c_read(dev, addr, reg, length=1):
|
||||
"""Read from I2C device via stock firmware 0x84 command."""
|
||||
return vendor_in(dev, I2C_READ, value=addr, index=reg, length=length)
|
||||
|
||||
|
||||
def i2c_scan(dev, start=0x03, end=0x77):
|
||||
"""Scan I2C bus for responding devices using stock firmware I2C_READ."""
|
||||
found = []
|
||||
for addr in range(start, end + 1):
|
||||
try:
|
||||
val = i2c_read(dev, addr, 0x00, length=1)
|
||||
found.append((addr, val[0]))
|
||||
except usb.core.USBError:
|
||||
pass
|
||||
return found
|
||||
|
||||
|
||||
def dump_bcm4500_direct_regs(dev):
|
||||
"""Read all BCM4500 direct registers 0xA0-0xBF after boot.
|
||||
|
||||
These are the PLL/config registers. FUN_CODE_10F2 writes to A0, A9, AA, AB
|
||||
during boot from calibration EEPROM data. Our custom firmware skips this.
|
||||
"""
|
||||
regs = {}
|
||||
for reg in range(0xA0, 0xC0):
|
||||
try:
|
||||
val = i2c_read(dev, BCM4500_ADDR, reg, length=1)
|
||||
regs[reg] = val[0]
|
||||
except usb.core.USBError:
|
||||
regs[reg] = None
|
||||
return regs
|
||||
|
||||
|
||||
def main():
|
||||
dump_all = "--dump-all" in sys.argv
|
||||
do_scan = "--i2c-scan" in sys.argv or dump_all
|
||||
|
||||
print("=" * 60)
|
||||
print("Stock Firmware BCM4500 Boot Diagnostic")
|
||||
print("=" * 60)
|
||||
|
||||
# --- Step 0: Find device ---
|
||||
dev = usb.core.find(idVendor=VID, idProduct=PID)
|
||||
if dev is None:
|
||||
print("\nERROR: SkyWalker-1 not found on USB")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\nFound SkyWalker-1: Bus {dev.bus} Addr {dev.address}")
|
||||
product = usb.util.get_string(dev, dev.iProduct) if dev.iProduct else "?"
|
||||
serial = usb.util.get_string(dev, dev.iSerialNumber) if dev.iSerialNumber else "?"
|
||||
print(f" Product: {product}")
|
||||
print(f" Serial: {serial}")
|
||||
|
||||
# Detach kernel driver if attached
|
||||
for cfg in dev:
|
||||
for intf in cfg:
|
||||
if dev.is_kernel_driver_active(intf.bInterfaceNumber):
|
||||
dev.detach_kernel_driver(intf.bInterfaceNumber)
|
||||
try:
|
||||
dev.set_configuration()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# --- Step 1: Pre-boot state ---
|
||||
print("\n--- Step 1: Pre-boot state ---")
|
||||
cfg = vendor_in(dev, GET_8PSK_CONFIG, length=1)
|
||||
print(f" Config: 0x{cfg[0]:02X} = {decode_config(cfg[0])}")
|
||||
|
||||
try:
|
||||
ver = vendor_in(dev, GET_FW_VERS, length=6)
|
||||
print(f" FW Version: {ver[2]}.{ver[0]:02d}.{ver[1]}")
|
||||
except Exception as e:
|
||||
print(f" FW Version: read failed ({e})")
|
||||
|
||||
# --- Step 2: Boot BCM4500 (kernel driver sequence) ---
|
||||
print("\n--- Step 2: BOOT_8PSK (0x89, wValue=1) ---")
|
||||
try:
|
||||
result = vendor_in(dev, BOOT_8PSK, value=1, length=1)
|
||||
print(f" Boot response: 0x{result[0]:02X} = {decode_config(result[0])}")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Boot FAILED: {e}")
|
||||
try:
|
||||
result = vendor_in(dev, BOOT_8PSK, value=1, length=3)
|
||||
print(f" Boot response (3 bytes): {result.hex(' ')}")
|
||||
except usb.core.USBError as e2:
|
||||
print(f" Boot also failed with 3 bytes: {e2}")
|
||||
sys.exit(1)
|
||||
|
||||
cfg = vendor_in(dev, GET_8PSK_CONFIG, length=1)
|
||||
started = bool(cfg[0] & BM_STARTED)
|
||||
fw_loaded = bool(cfg[0] & BM_FW_LOADED)
|
||||
print(f" Config after boot: 0x{cfg[0]:02X} = {decode_config(cfg[0])}")
|
||||
print(f" Started: {started}, FW Loaded: {fw_loaded}")
|
||||
|
||||
# --- Step 3: Enable LNB power supply (Intersil) ---
|
||||
print("\n--- Step 3: START_INTERSIL (0x8A, wValue=1) ---")
|
||||
try:
|
||||
result = vendor_in(dev, START_INTERSIL, value=1, length=1)
|
||||
print(f" Intersil response: 0x{result[0]:02X} = {decode_config(result[0])}")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Intersil FAILED: {e}")
|
||||
|
||||
# --- Step 4: Cancel pending MPEG transfers ---
|
||||
print("\n--- Step 4: ARM_TRANSFER (0x85, wValue=0) ---")
|
||||
try:
|
||||
vendor_out(dev, ARM_TRANSFER, value=0)
|
||||
print(" OK")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Failed: {e}")
|
||||
|
||||
cfg = vendor_in(dev, GET_8PSK_CONFIG, length=1)
|
||||
print(f" Final config: 0x{cfg[0]:02X} = {decode_config(cfg[0])}")
|
||||
|
||||
# --- Step 5: Signal strength (indirect register reads) ---
|
||||
print("\n--- Step 5: Signal reads ---")
|
||||
sig_all_zero = True
|
||||
try:
|
||||
sig = vendor_in(dev, GET_SIGNAL_STRENGTH, length=6)
|
||||
snr_raw = struct.unpack_from('<H', sig, 0)[0]
|
||||
sig_all_zero = all(b == 0 for b in sig)
|
||||
print(f" Signal strength (0x87): {sig.hex(' ')}")
|
||||
print(f" SNR raw: 0x{snr_raw:04X} ({snr_raw}), all_zero: {sig_all_zero}")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Signal strength read failed: {e}")
|
||||
|
||||
try:
|
||||
lock = vendor_in(dev, GET_SIGNAL_LOCK, length=1)
|
||||
print(f" Lock status (0x90): 0x{lock[0]:02X} {'LOCKED' if lock[0] else 'no lock'}")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Lock status read failed: {e}")
|
||||
|
||||
# --- Step 6: FULL BCM4500 direct register dump (0xA0-0xBF) ---
|
||||
# This captures the PLL/clock values written by FUN_CODE_10F2
|
||||
print("\n--- Step 6: BCM4500 direct registers 0xA0-0xBF (post-boot) ---")
|
||||
print(" (A0=config_mode, A9/AA/AB=PLL from EEPROM, A6/A7/A8=indirect)")
|
||||
regs = dump_bcm4500_direct_regs(dev)
|
||||
for reg in sorted(regs.keys()):
|
||||
val = regs[reg]
|
||||
if val is None:
|
||||
print(f" 0x{reg:02X}: FAILED")
|
||||
continue
|
||||
# Check for echo pattern (dead core symptom)
|
||||
echo_expected = min(reg & 0xFE, 0x7E) if reg > 0x7E else (reg & 0xFE)
|
||||
markers = []
|
||||
if val == echo_expected:
|
||||
markers.append("ECHO")
|
||||
if reg in (0xA9, 0xAA, 0xAB):
|
||||
markers.append("★ PLL")
|
||||
if reg == 0xA0:
|
||||
markers.append("CONFIG_MODE")
|
||||
tag = f" ({', '.join(markers)})" if markers else ""
|
||||
print(f" 0x{reg:02X}: 0x{val:02X}{tag}")
|
||||
|
||||
# Highlight the critical PLL values
|
||||
a9 = regs.get(0xA9)
|
||||
aa = regs.get(0xAA)
|
||||
ab = regs.get(0xAB)
|
||||
a0 = regs.get(0xA0)
|
||||
print(f"\n *** Critical PLL registers ***")
|
||||
print(f" A0 (config mode): 0x{a0:02X}" if a0 is not None else " A0: FAILED")
|
||||
print(f" A9 (PLL div?): 0x{a9:02X}" if a9 is not None else " A9: FAILED")
|
||||
print(f" AA (PLL div?): 0x{aa:02X}" if aa is not None else " AA: FAILED")
|
||||
print(f" AB (PLL cfg?): 0x{ab:02X}" if ab is not None else " AB: FAILED")
|
||||
|
||||
# Try multi-byte read of AB (FUN_CODE_10F2 writes variable-length data here)
|
||||
print("\n AB multi-byte read (up to 8 bytes):")
|
||||
try:
|
||||
ab_multi = i2c_read(dev, BCM4500_ADDR, 0xAB, length=8)
|
||||
print(f" {ab_multi.hex(' ')}")
|
||||
except usb.core.USBError as e:
|
||||
print(f" FAILED: {e}")
|
||||
|
||||
# --- Step 7: Other known registers ---
|
||||
print("\n--- Step 7: Other BCM4500 registers ---")
|
||||
other_regs = [(0xF0, "F0"), (0xF8, "F8"), (0xF9, "F9")]
|
||||
for reg, name in other_regs:
|
||||
try:
|
||||
val = i2c_read(dev, BCM4500_ADDR, reg, length=1)
|
||||
echo = min(reg & 0xFE, 0x7E)
|
||||
marker = " (ECHO)" if val[0] == echo else ""
|
||||
print(f" Reg 0x{name}: 0x{val[0]:02X}{marker}")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Reg 0x{name}: FAILED ({e})")
|
||||
|
||||
# --- Step 8: BCM3440 tuner control read ---
|
||||
print("\n--- Step 8: BCM3440 tuner control read (@ 0x10) ---")
|
||||
tuner_ok = False
|
||||
try:
|
||||
val = i2c_read(dev, BCM3440_ADDR, 0x00, length=4)
|
||||
tuner_ok = not all(b == 0 for b in val)
|
||||
print(f" Tuner regs 0x00-0x03: {val.hex(' ')} {'(OK)' if tuner_ok else '(all zero!)'}")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Tuner read failed: {e}")
|
||||
|
||||
# --- Step 9: I2C bus scan ---
|
||||
if do_scan:
|
||||
print("\n--- Step 9: I2C bus scan (0x03-0x77) ---")
|
||||
print(" Scanning for all responding devices...")
|
||||
devices = i2c_scan(dev)
|
||||
if devices:
|
||||
for addr, first_byte in devices:
|
||||
label = ""
|
||||
if addr == BCM4500_ADDR:
|
||||
label = " ← BCM4500 demod"
|
||||
elif addr == BCM3440_ADDR:
|
||||
label = " ← BCM3440 tuner"
|
||||
elif addr == 0x28:
|
||||
label = " ← EEPROM? (0x51 wire >> 1)"
|
||||
elif 0x50 <= addr <= 0x57:
|
||||
label = " ← EEPROM range"
|
||||
elif addr == EEPROM_ADDR:
|
||||
label = " ← device 0x51 from FUN_CODE_10F2!"
|
||||
print(f" 0x{addr:02X} (7-bit) = 0x{addr << 1:02X}/{addr << 1 | 1:02X} (wire) "
|
||||
f"first byte: 0x{first_byte:02X}{label}")
|
||||
else:
|
||||
print(" No devices found!")
|
||||
|
||||
# Try reading from device 0x51 specifically (calibration EEPROM)
|
||||
print("\n --- Device 0x51 probe (calibration EEPROM) ---")
|
||||
for try_addr in [EEPROM_ADDR, 0x50, 0x28, 0x29]:
|
||||
try:
|
||||
val = i2c_read(dev, try_addr, 0x00, length=16)
|
||||
print(f" Addr 0x{try_addr:02X} reg 0x00 (16 bytes): {val.hex(' ')}")
|
||||
# If we get data, try reading more
|
||||
if not all(b == 0xFF for b in val):
|
||||
val2 = i2c_read(dev, try_addr, 0x00, length=64)
|
||||
print(f" Addr 0x{try_addr:02X} reg 0x00 (64 bytes):")
|
||||
for row in range(0, len(val2), 16):
|
||||
chunk = val2[row:row+16]
|
||||
print(f" +{row:02X}: {chunk.hex(' ')}")
|
||||
except usb.core.USBError:
|
||||
print(f" Addr 0x{try_addr:02X}: no response")
|
||||
else:
|
||||
print("\n (use --i2c-scan or --dump-all for I2C bus scan)")
|
||||
|
||||
# --- Summary ---
|
||||
print("\n" + "=" * 60)
|
||||
# Check for echo pattern on critical registers
|
||||
pll_echo = (a9 is not None and a9 == 0xA8) or (aa is not None and aa == 0xAA)
|
||||
if not sig_all_zero:
|
||||
print("BCM4500 CORE IS ALIVE under stock firmware!")
|
||||
print(" → Problem is in our custom firmware boot sequence")
|
||||
if a9 is not None and aa is not None:
|
||||
print(f" → PLL values to replicate: A9=0x{a9:02X} AA=0x{aa:02X} AB=0x{ab:02X}")
|
||||
elif pll_echo:
|
||||
print("BCM4500 CORE IS DEAD (echo pattern on PLL registers)")
|
||||
print(" → FUN_CODE_10F2 may not have run, or EEPROM missing")
|
||||
else:
|
||||
print("BCM4500 CORE STATUS UNCERTAIN")
|
||||
print(" → Check register values above for non-echo data")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user