Skip to content

I2C Bus Architecture

The SkyWalker-1 uses a single I2C bus connecting the FX2 microcontroller (master) to three slave devices. The FX2’s hardware I2C controller handles all bus transactions through XRAM-mapped registers.

SFRAddressFunction
I2CS0xE678 (XRAM)I2C control/status register
I2DAT0xE679 (XRAM)I2C data register
I2CTL0xE67A (XRAM)I2C control (speed selection)
BitNameFunction
bmSTARTbit 7Write: initiate START condition
bmSTOPbit 6Write: initiate STOP condition
bmLASTRDbit 5Write: signal last read byte (NACK after next read)
bmDONEbit 2Read: byte transfer complete
bmACKbit 1Read: ACK received from slave
bmBERRbit 0Read: bus error detected

The I2C bus operates at 400 kHz. The speed is set through two mechanisms:

  1. C2 EEPROM header — Config byte at offset 7 = 0x40, which the FX2 boot ROM uses to configure the I2C speed during initial EEPROM read.
  2. Firmware/software — I2CTL = bm400KHZ (written by the custom firmware and init table).
7-bit AddressWire WriteWire ReadIdentity
0x080x100x11BCM4500 demodulator
0x100x200x21Tuner or LNB controller
0x510xA20xA3Configuration EEPROM (24Cxx-family)

These addresses were confirmed via the I2C_BUS_SCAN command (0xB4) in custom firmware v3.01.0, which probes all 7-bit addresses from 0x01 to 0x77.

The primary device for all demodulation operations. Register access uses the indirect register protocol through registers 0xA6, 0xA7, and 0xA8. Direct registers 0xA2, 0xA4, and 0xF9 provide status information.

The v2.13 firmware also probes alternate addresses 0x7F and 0x3F at startup to detect which demodulator variant is present.

Likely a tuner IC or LNB controller. In normal operation, the BCM4500 accesses this device internally for tuning. It is also directly addressable on the shared I2C bus. The kernel driver does not directly communicate with this device.

A 24Cxx-family serial EEPROM that stores:

  • Device serial number (read by GET_SERIAL_NUMBER, 0x93)
  • Hardware platform ID (read by GET_FPGA_VERS, 0x95)
  • Calibration data
FX2 I2C Master
(I2CS/I2DAT/I2CTL)
|
+----------+-----------+
| | |
BCM4500 Tuner/LNB EEPROM
(0x08) (0x10) (0x51)

All three devices share the same SCL/SDA lines. The FX2’s hardware I2C controller manages bus arbitration and clock stretching detection.

All BCM4500 register reads use the I2C combined write-read protocol with a repeated START condition. This is required because the BCM4500 uses a register-addressed protocol where the register number must be sent in a write phase before the read phase.

Complete I2C transaction for reading register 0xA2 from device 0x08:
Phase 1 (Write):
[S] [0x10] [ACK] [0xA2] [ACK]
| | | | |
| | | | +-- BCM4500 ACKs register address
| | | +--------- Register address byte
| | +---------------- BCM4500 ACKs its device address
| +----------------------- Device address (0x08 << 1) = 0x10 (write)
+---------------------------- START condition
Phase 2 (Read with Repeated START):
[Sr] [0x11] [ACK] [DATA] [NACK] [P]
| | | | | |
| | | | | +-- STOP condition
| | | | +--------- Master NACKs (last byte)
| | | +---------------- Register data
| | +----------------------- BCM4500 ACKs its device address
| +------------------------------ Device address (0x08 << 1 | 1) = 0x11 (read)
+------------------------------------ REPEATED START (no STOP between phases)

The repeated START (Sr) is essential. A STOP between phases would release the bus, and the BCM4500 would lose the register address context.

Combined Write-Read Implementation
// Phase 1: Write register address
I2CS |= bmSTART; // Generate START
I2DAT = 0x10; // Write: device addr + W
// wait bmDONE, check bmACK
I2DAT = 0xA2; // Write: register address
// wait bmDONE, check bmACK
// Phase 2: Read with repeated START
I2CS |= bmSTART; // Generate REPEATED START (no STOP first!)
I2DAT = 0x11; // Write: device addr + R
// wait bmDONE, check bmACK
I2CS |= bmLASTRD; // Signal this is the last read byte
tmp = I2DAT; // Dummy read (triggers first clock burst)
// wait bmDONE
I2CS |= bmSTOP; // Generate STOP after reading
data = I2DAT; // Read actual data byte
// wait bmSTOP to clear

The fx2lib I2C functions poll bmDONE with no timeout:

fx2lib Original Code (Vulnerable)
while (!(I2CS & bmDONE) && !cancel_i2c_trans);

Since cancel_i2c_trans is never set during normal operation, these loops are effectively infinite. If a slave holds SCL low or a bus error prevents bmDONE from asserting, the firmware hangs.

The custom firmware replaces all fx2lib I2C functions with timeout-protected wrappers:

Timeout-Protected I2C Wait
#define I2C_TIMEOUT 6000
static BOOL i2c_wait_done(void) {
WORD timeout = I2C_TIMEOUT;
while (!(I2CS & bmDONE)) {
if (--timeout == 0) return FALSE;
}
return TRUE;
}

A WORD counter of 6000 decremented in a tight SDCC-compiled loop at 48 MHz gives approximately 5—10 ms per wait. At 400 kHz I2C, a single byte transfer takes 22.5 us, so the timeout provides over 200x margin for normal operations while bounding the worst case.