| Tuner/LNB | Unknown IC (I2C address 0x10) | Tuner or LNB controller on shared I2C bus |
The FX2 handles USB communication, LNB control, DiSEqC signaling, and orchestrates tuning via I2C commands to the BCM4500. The BCM4500 performs RF demodulation, forward error correction, and outputs an MPEG-2 transport stream on an 8-bit parallel bus. The FX2's GPIF engine transfers the transport stream directly into a USB bulk endpoint with zero firmware intervention in the data path.
The SkyWalker-1 (PID 0x0203) enumerates directly as a "warm" device. The DVB-USB framework skips firmware download when `cold_ids` is NULL. No host-side firmware files are required.
The firmware files `dvb-usb-gp8psk-01.fw` and `dvb-usb-gp8psk-02.fw` were never open-sourced or included in `linux-firmware`.
---
## 3. Vendor Command Reference
All vendor commands use USB control transfers:
- **USB Type**: `USB_TYPE_VENDOR`
- **Timeout**: 2000 ms (kernel driver)
- **Retry**: Up to 3 attempts for IN operations if partial data received
- **Data buffer maximum**: 80 bytes (kernel driver state structure)
### 3.1 Stock Command Table (0x80--0x9D)
The vendor command dispatcher at CODE:0056 validates `bRequest` in the range 0x80--0x9D (30 entries) and dispatches via an indexed jump table at CODE:0076. Rev.2 supports only 0x80--0x9A (27 entries).
| 0x9D | CW3K_INIT / SET_MODE_FLAG | OUT | 0/1 | 0 | 0 | CW3K init or conditional demod reset | OK | N/A | Changed |
**Status key**: OK = implemented. STALL = routes to stall handler. Proto = partial/prototype. N/A = out of range (Rev.2 supports 0x80--0x9A only). Changed = implementation differs between versions.
**Driver usage notes**:
- The Linux driver only sends LOAD_BCM4500 (0x88) for Rev.1 Warm (PID 0x0201). On SkyWalker-1, `bm8pskFW_Loaded` is already set and 0x88 STALLs.
- The Linux driver only sends CW3K_INIT (0x9D) for SkyWalker CW3K (PID 0x0206).
### 3.2 Vendor Command Dispatch Mechanism
The vendor command dispatcher at CODE:0056 (identical code address across v2.06, v2.13, and Rev.2) follows this logic:
```
1. Check bmRequestType bit 6: if not set, not a vendor request -> handle standard
2. Read bRequest from SETUPDAT[1]
3. Subtract 0x80 (command base offset)
4. Compare against maximum: <0x1E(v2.06/v2.13)or<0x1B(Rev.2)
5. If in range: double the index (2 bytes per entry) and JMP @A+DPTR to jump table
6. If out of range: route to STALL handler
```
The jump table at CODE:0076 contains 2-byte AJMP targets. Each entry points to the handler for commands 0x80 through 0x9D (or 0x9A for Rev.2).
**0x87 GET_SIGNAL_STRENGTH**: Returns 6 bytes. Bytes 0--1 are a 16-bit SNR value (little-endian, dBu * 256 units). Bytes 2--5 are reserved/diagnostic. SNR scaling from Windows BDA driver: `if snr_raw <= 0x0F00: strength = snr_raw * 17; else strength = 0xFFFF`. Version differences: v2.06 polls 3 registers (0xA2, 0xA8, 0xA4) up to 6 times; v2.13 consolidates to 1 register.
**0x8D SEND_DISEQC_COMMAND**: When `wLength > 0`, the payload is a standard DiSEqC message (3--6 bytes) with `wValue` = `msg[0]` (framing byte, typically 0xE0 or 0xE1). When `wLength == 0`: `wValue == 0` sends tone burst A; `wValue != 0` sends tone burst B. See [Section 9](#9-lnb-and-diseqc-control).
**0x8F SET_DN_SWITCH**: `wValue` carries a 7-bit Dish Network switch command, bit-banged LSB-first on GPIO P0.4. The 8th bit (0x80) of the original switch command controls LNB voltage and is sent separately via SET_LNB_VOLTAGE (0x8B).
**0x92 GET_FW_VERS**: Returns 6 bytes of hardcoded constants:
```
Byte 0: version minor_minor (e.g., 0x04)
Byte 1: version minor (e.g., 0x06)
Byte 2: version major (e.g., 0x02)
Byte 3: build day (e.g., 0x0D = 13)
Byte 4: build month (e.g., 0x07 = July)
Byte 5: build year - 2000 (e.g., 0x07 = 2007)
```
Full version = `byte[2] << 16 | byte[1] << 8 | byte[0]`. Build date = `(2000 + byte[5]) / byte[4] / byte[3]`.
**0x93 GET_SERIAL_NUMBER**: Returns 4 bytes read from I2C EEPROM at device address 0x51 (7-bit), extracted at 8-bit intervals using a shift/rotate routine.
**0x94 USE_EXTRA_VOLT**: `wValue=1` writes 0x6A to XRAM 0xE0B6; `wValue=0` writes 0x62. The difference is bit 3 (0x08), which controls the voltage boost on the LNB power regulator.
**0x95 GET_FPGA_VERS**: Reads from I2C EEPROM at 0x51. Despite the name, there is no FPGA on the SkyWalker-1 -- this returns a hardware platform ID. v2.06 reads EEPROM offset 0x31 (2 bytes); v2.13/Rev.2 read offset 0x00 (1 byte).
**0xB0 SPECTRUM_SWEEP**: 10-byte EP0 payload: `[start_freq(u32 LE kHz), stop_freq(u32 LE kHz), step_khz(u16 LE)]`. Programs BCM4500 at each frequency step, reads SNR, packs u16 LE results into EP2 bulk FIFO.
**0xB3 BLIND_SCAN**: 16-byte EP0 payload: `[freq_khz(u32 LE), sr_min(u32 LE sps), sr_max(u32 LE sps), sr_step(u32 LE sps)]`. Returns 8 bytes on lock `[freq_khz(4) + sr_locked(4)]` or 1 byte 0x00 if no lock found.
**0xB4 I2C_BUS_SCAN**: Returns a 16-byte bitmap (128 bits for addresses 0x00--0x77). Each bit position corresponds to a 7-bit address; bit set = ACK received. Known devices on the SkyWalker-1 bus:
Returned by GET_8PSK_CONFIG (0x80). Stored in IRAM at a version-dependent address.
```
Bit 7 (0x80): bmArmed - MPEG-2 stream transfer armed / GPIF active
Bit 6 (0x40): bmDCtuned - DC offset tuning complete (set for DCII modes)
Bit 5 (0x20): bmSEL18V - 18V LNB voltage selected (else 13V)
Bit 4 (0x10): bm22kHz - 22 kHz tone active
Bit 3 (0x08): bmDVBmode - DVB mode enabled
Bit 2 (0x04): bmIntersilOn - LNB power supply enabled
Bit 1 (0x02): bm8pskFW_Loaded - BCM4500 firmware loaded (always set on SkyWalker-1)
Bit 0 (0x01): bm8pskStarted - Device booted and running
```
| Firmware | IRAM Address |
|----------|-------------|
| v2.06 | 0x6D |
| Rev.2 v2.10.4 | 0x4E |
| v2.13 | 0x4F |
The kernel driver checks these bits to decide which initialization steps to perform. On the SkyWalker-1 after a successful BOOT_8PSK, `config_status = 0x03` (STARTED + FW_LOADED).
---
## 5. Boot Sequence
### 5.1 Kernel Driver Boot Flow
```
1. GET_8PSK_CONFIG (0x80) -- read config status byte
|-- Check bit 0: bm8pskStarted?
2. If not started:
|-- BOOT_8PSK (0x89, wValue=1)
|-- GET_FW_VERS (0x92) -- read firmware version
3. If bit 1 clear (bm8pskFW_Loaded):
|-- LOAD_BCM4500 (0x88) -- Rev.1 Warm only; STALLs on SkyWalker-1
4. If bit 2 clear (bmIntersilOn):
|-- START_INTERSIL (0x8A, wValue=1) -- enable LNB power supply
5. SET_DVB_MODE (0x8E, wValue=1) -- STALLs on all SkyWalker-1 FW versions
6. ARM_TRANSFER (0x85, wValue=0) -- abort any pending MPEG transfer
7. Device ready for tuning
```
### 5.2 BCM4500 Boot Sequence (BOOT_8PSK, 0x89)
As implemented in `bcm4500_boot()` in custom firmware v3.01.0, reverse-engineered from stock v2.06 `FUN_CODE_1D4F` + `FUN_CODE_0ddd`:
7 Write init block 0 to page 0 I2C write 0xA6/A7/A8 ~2 ms
8 Write init block 1 to page 0 I2C write 0xA6/A7/A8 ~2 ms
9 Write init block 2 to page 0 I2C write 0xA6/A7/A8 ~1 ms
10 Set config_status = 0x03 -- --
```
**Total boot time**: approximately 90 ms (30 ms power + 50 ms POR + ~10 ms I2C).
### 5.3 BCM4500 Initialization Data
Three register initialization blocks are written to BCM4500 indirect registers (page 0x00) via the 0xA6/0xA7/0xA8 protocol. Data extracted from stock v2.06 firmware `FUN_CODE_0ddd`:
Each block is written as: page select (0xA6 = 0x00), data bytes to 0xA7, trailing zero to 0xA7, then commit (0xA8 = 0x03). The firmware polls 0xA8 until the command completes before proceeding to the next block.
### 5.4 FX2 CPUCS Recovery
The FX2's CPUCS register at 0xE600 controls the 8051 run/halt state. The standard vendor request bRequest=0xA0 (RAM read/write) is handled by the FX2 boot ROM in silicon, not by user firmware. This means `fw_load.py` can reload firmware over a completely hung device:
The BCM3440 tuner acts as an I2C bridge/gateway: register accesses in the 0xA0+ range sent to the tuner's address (0x10) are transparently forwarded to the BCM4500 demodulator. The BCM4500's own I2C address (0x08) only exposes a single status byte via simple reads — it does NOT support register-addressed reads at that address.
**Stock firmware evidence (functions at wire address 0x20/0x21):**
-`FUN_CODE_0DDD` (init blocks): writes A6/A7/A8 via `LCALL 0x1A81` with R7=0x10
-`FUN_CODE_10F2` (PLL/firmware download): writes A9/AA/AB via `LCALL 0x1A81` with R7=0x10
-`FUN_CODE_15E9` (config mode): writes A0 via device 0x10
**Stock I2C_READ (0x84) vendor command for BCM4500 (address 0x08):** Simple read only — `[S][0x11][data][P]` — no register address sent. Returns whatever the BCM4500's I2C slave has ready (global status byte). Register address from wIndex is completely ignored for device 0x08 (confirmed by disassembly of `FUN_CODE_2036`).
The v2.13 firmware probes addresses 0x7F and 0x3F at startup (INT0 handler) to detect which demodulator variant is present.
### 6.2 Direct Registers
Accessed via I2C write/read through the BCM3440 gateway (address 0x10):
| Register | Function |
|----------|----------|
| 0xA2 | Status register (polled for readiness during boot) |
| 0xA4 | Lock/ready register; bit 5 (0x20) = signal locked |
| 0xA6 | Indirect page/address select |
| 0xA7 | Indirect data register (read/write) |
| 0xA8 | Indirect command register |
| 0xF9 | Demod status (read by v2.13 GET_DEMOD_STATUS / INT0 polling) |
### 6.3 Indirect Register Protocol
The BCM4500 uses an indirect register access scheme through three directly-addressable registers:
**Indirect Write Sequence:**
```
1. I2C WRITE to 0x08, register 0xA6 <-page_number(typically0x00)
2. I2C WRITE to 0x08, register 0xA7 <-databytes(Nbytes,auto-increment)
3. I2C WRITE to 0x08, register 0xA8 <-0x03(executeindirectwrite)
4. Poll register 0xA8 until bit 0 clear (command complete)
5. Optionally read back register 0xA7 to verify
```
**Indirect Read Sequence:**
```
1. I2C WRITE to 0x08, register 0xA6 <-target_register
2. I2C WRITE to 0x08, register 0xA7 <-0x00(placeholder)
3. I2C WRITE to 0x08, register 0xA8 <-0x01(executeindirectread)
4. Short delay (~1 ms)
5. I2C READ from 0x08, register 0xA7 <-resultbyte
```
### 6.4 Indirect Protocol Auto-Increment
The BCM4500's data register (0xA7) supports auto-increment for multi-byte writes within a single I2C transaction. When writing N data bytes to 0xA7 in one I2C WRITE operation (without issuing STOP between bytes), the BCM4500 internally advances its data buffer pointer after each byte. This allows writing an entire initialization block in a single I2C transaction:
5. Poll: I2C READ [0xA8] until bit 0 clear -- Wait for completion
```
The trailing zero write (step 3) appears in all stock firmware versions. Its purpose is unclear -- it may zero-pad the data buffer or serve as an end-of-data marker within the BCM4500's indirect register engine.
### 6.5 Demodulator Scan
The tune function (stock firmware) tries up to 3 different I2C address configurations per attempt, with 3 outer retries (up to 9 total I2C programming attempts). This supports hardware variants where the BCM4500 may appear at different bus addresses.
v2.13 adds a boot-time probe: INT0 polls addresses 0x7F and 0x3F up to 40 times (0x28), setting flag `_1_4` if neither responds. This prevents tuning attempts on boards with absent demodulators.
The byte reversal converts host little-endian to BCM4500 big-endian so values can be written directly to the demodulator via I2C.
### 7.3 Modulation Dispatch
After parsing, the firmware validates the modulation type (bounds check `< 10`) and dispatches via a 20-byte jump table (10 entries x 2 bytes) at CODE:0873. Each handler:
1. Validates the FEC index against the maximum for that modulation
2. Looks up a preconfigured byte from an XRAM FEC rate table
3. Writes configuration to four XRAM registers (0xE0EB, 0xE0EC, 0xE0F5, 0xE0F6)
**Modulation jump table (from Rev.2 at CODE:0873):**
| Entry | AJMP Target | Modulation |
|-------|------------|-----------|
| 0 | 0x08B7 | DVB-S QPSK |
| 1 | 0x08DF | Turbo QPSK |
| 2 | 0x08FA | Turbo 8PSK |
| 3 | 0x0915 | Turbo 16QAM |
| 4 | 0x0947 | DCII Combo |
| 5 | 0x094F | DCII I-stream |
| 6 | 0x0957 | DCII Q-stream |
| 7 | 0x095F | DCII Offset QPSK |
| 8 | 0x0887 | DSS QPSK |
| 9 | 0x0887 | DVB BPSK (shares DSS handler) |
DSS and DVB BPSK share the same handler. Their FEC lookup uses the same table (0xE0F9) but ORs the result with 0x80 to distinguish them from DVB-S QPSK.
### 7.4 FEC Rate Lookup Tables
Populated from the CODE-space init table at boot:
| XRAM Base | Modulation | Max FEC Index | Code Rates |
=== Phase 5: Signal Acquisition (host polling) ===
14. GET_SIGNAL_LOCK (0x90) -- poll until non-zero
15. GET_SIGNAL_STRENGTH (0x87) -- read SNR
```
### 7.7 Signal Lock and Strength
**GET_SIGNAL_LOCK (0x90)**: Returns 1 byte from BCM4500 register 0xA4. Bit 5 (0x20) indicates signal lock. The kernel driver interprets any non-zero value as locked and reports `FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_SIGNAL | FE_HAS_CARRIER`.
(I2C:0x08) <-----> (Master Read) (AUTOIN) ------------> Bulk IN
8-bit 0xE4xx wfm 4x buffer 7 URBs
parallel 8-bit x 8KB
```
The path is fully hardware-managed. The GPIF engine reads data from the BCM4500's 8-bit parallel transport stream output directly into the EP2 FIFO. The AUTOIN bit causes automatic USB commit when the FIFO buffer is full. The FLOWSTATE engine re-triggers GPIF transactions when buffer space becomes available. No firmware intervention occurs in the data path after initial setup.
### 8.2 Key Register Configuration
All values are identical across the three stock firmware versions:
The USB/GPIF path has approximately 5x headroom even at maximum theoretical DVB-S2 data rates. The bottleneck for supported modes is the satellite link, not the USB data path.
### 8.5 FIFO Reset Sequence
All endpoint FIFOs are reset during initialization using the Cypress-prescribed procedure:
```
FIFORESET = 0x80 ; NAKALL: NAK all host transfers during reset
FIFORESET = 0x02 ; Reset EP2 FIFO
FIFORESET = 0x04 ; Reset EP4 FIFO
FIFORESET = 0x06 ; Reset EP6 FIFO
FIFORESET = 0x08 ; Reset EP8 FIFO
FIFORESET = 0x00 ; Release NAKALL
```
Three NOP instructions (mandatory SYNCDELAY) are inserted between each write per Cypress TRM requirements.
INT4 and INT6 (GPIF/FIFO events) share a common handler that sets a software flag (`_0_1`) and clears EXIF.4. The main loop polls this flag, enters CPU idle mode (PCON.0) between events, and checks EP2CS for buffer availability before re-arming the GPIF.
During early initialization, IFCONFIG is temporarily set to 0xCA before the final 0xEE:
| Value | Decode | Difference from 0xEE |
|-------|--------|---------------------|
| 0xCA | 1100_1010 | GSTATE=0, ASYNC=0 |
| 0xEE | 1110_1110 | GSTATE=1, ASYNC=1 (final) |
The temporary value disables async mode and debug state output during FIFO setup.
---
## 9. LNB and DiSEqC Control
### 9.1 LNB Voltage
LNB voltage is controlled via GPIO P0.4. No I2C is involved.
| wValue | Voltage | GPIO P0.4 | Polarization |
|--------|---------|-----------|-------------|
| 0 | 13V | LOW | Vertical / Circular-Right |
| 1 | 18V | HIGH | Horizontal / Circular-Left |
USE_EXTRA_VOLT (0x94) enables a +1V boost (13V->14V, 18V->19V) for long cable runs by writing to XRAM 0xE0B6 (0x62=normal, 0x6A=boosted; bit 3 is the difference).
### 9.2 22 kHz Tone
Controlled via GPIO P0.3. P0.3 gates an external 22 kHz oscillator on the PCB. The firmware does not generate the 22 kHz carrier directly.
| wValue | State | GPIO P0.3 | Band |
|--------|-------|-----------|------|
| 0 | OFF | LOW | Low band (9.75 GHz LO on universal LNB) |
| 1 | ON | HIGH | High band (10.6 GHz LO on universal LNB) |
### 9.3 DiSEqC Protocol Implementation
All firmware versions implement DiSEqC via Timer2-based GPIO bit-bang. The algorithm is identical across versions; only the data pin differs per PCB revision.
**Timer2 configuration (identical across all versions):**
if (first_byte == 0) TF2 = 0; // Sync timer on first byte
for (char i = 8; i > 0; i--) { // 8 bits, MSB first
if (data & 0x80) {
data_pin = 1; // Set data = '1'
diseqc_bit_symbol();
ones_count++;
} else {
data_pin = 0; // Set data = '0'
diseqc_bit_symbol();
}
data <<= 1; // Next bit
}
data_pin = ~ones_count & 1; // Odd parity
diseqc_bit_symbol(); // Transmit parity bit
}
```
**Timing per byte**: 9 bits x 1.5 ms = 13.5 ms
**Tone burst (mini DiSEqC)**: 25 consecutive Timer2 ticks of carrier (12.5 ms). Tone burst A: `wValue==0` and `wLength==0`. Tone burst B: `wValue!=0` and `wLength==0`.
**Timer tick wait (TF2 polling, identical across all versions):**
The data pin (P0.7 / P0.4 / P0.0 depending on firmware version) is used only internally by the firmware's Manchester encoding logic. It controls whether the carrier gate signal is cut short or held for the full bit period.
### 9.5 Windows BDA Driver DiSEqC Interface
The Windows driver exposes DiSEqC through a BDA extended property:
1. Assert P0.4 HIGH (start pulse), delay ~32 cycles
2. De-assert P0.4, delay ~8 cycles
3. Shift out 7 bits LSB-first via P0.4, ~8 cycle delays between bits
The kernel calls this via `dishnetwork_send_legacy_command`. Bit 7 (0x80) of the original switch command selects LNB voltage and is sent separately via SET_LNB_VOLTAGE.
Rev.2 v2.10 targets PID 0x0202 (different product). The v2.13 sub-variants target different SkyWalker-1 hardware sub-revisions. Custom v3.01.0 is compiled with SDCC + fx2lib and loaded into FX2 RAM (not flashed to EEPROM).
### 11.2 Kernel Version Constants
From `gp8psk-fe.h`:
```
GP8PSK_FW_REV1 = 0x020604 (v2.06.4)
GP8PSK_FW_REV2 = 0x020704 (v2.07.4)
```
If `fw_vers >= GP8PSK_FW_REV2`, the kernel enables Rev.2-specific code paths. The v2.10 and v2.13 firmwares are newer than either kernel constant.
FW1 uses standard I2C master-mode transactions. FW2/FW3 use a parallel data bus with P0 for control signals (chip select, read strobe) and P1 for 8-bit data. FW3 adds dual-phase reading with OR-accumulation, likely for a demodulator chip with different bus timing. The updater program selects the correct sub-variant based on hardware detection.
### 11.5 Binary Comparison Matrix
Byte-level similarity (percentage of matching bytes within shared length):
| | v2.06 | v2.13.1 | v2.13.2 | v2.13.3 | Rev.2 |
|---|---|---|---|---|---|
| **v2.06** | -- | 4.8% | 4.3% | 4.3% | 6.0% |
| **v2.13.1** | | -- | 57.2% | 59.4% | 8.0% |
| **v2.13.2** | | | -- | 83.5% | 5.8% |
| **v2.13.3** | | | | -- | 5.8% |
| **Rev.2** | | | | | -- |
The very low similarity between major versions (4--8%) indicates complete recompilation with different linker configurations. Functions relocate even when logic is identical.
### 11.6 Anti-Tampering (v2.13 Only)
At firmware offset 0x1880, all v2.13 sub-variants contain:
```
"Tampering is detected. Attempt is logged. Warranty is voided ! \n"
```
Followed by I2C register write commands (`01 10 aa 82 02 41 41 83`). This string and mechanism are absent from v2.06 and Rev.2.
### 11.7 Rev.2 as Transitional Firmware
Rev.2 v2.10.4 sits architecturally between v2.06 and v2.13:
- Adopted v2.13's descriptor base (0x0E00) and similar stack pointer
- Retained v2.06's INT0 USB re-enumeration behavior
- Has the most functions (107) but smallest binary (~8.8 KB) due to granular decomposition
no_demod_flag = (counter == 0); // Set if loop exhausted
}
```
Polls two I2C addresses (0x7F, 0x3F) to detect which demodulator variant is present. The `no_demod_flag` prevents tuning attempts on boards with absent or failed demodulators.
In v2.13, the USB re-enumeration code was moved to `FUN_CODE_2031` and called as a normal function before the main loop starts, freeing INT0 for demodulator polling.
### 11.10 v2.13 Integrity Verification
v2.13 performs two integrity checks during initialization, absent from v2.06 and Rev.2:
1. Iterates bytes 6 through 0x29 (36 bytes) of a descriptor block
2. Computes running sum, compares against 0x0706
3. Iterates bytes 0x2C through 0x4F (36 bytes) of same block
4. Computes second sum, compares against 0x0686
5. Up to 20 retry attempts
Both checks call `FUN_CODE_1ac6(100)` (tuning acquisition with 100 ms delay) as a recovery action if verification fails after all attempts.
### 11.11 XRAM Initialization Table
All firmware versions initialize XRAM peripheral registers from a table stored in CODE space. The table is processed at startup before entering the main loop.
**Table format (all versions):**
```
Each entry: [addr_hi] [addr_lo] [data_byte]
Terminator: [0x00] [0x00] (address 0x0000)
```
The parser reads 3 bytes at a time: a 16-bit XRAM address (big-endian) and a data byte. It writes the byte to the address until it encounters address 0x0000.
**Key XRAM registers initialized from the table:**
All firmware versions use the same main loop structure: poll the SUDAV (setup data available) interrupt flag, process vendor commands, then idle the CPU until the next interrupt.
**v2.06 (simplified decompilation):**
```c
void main_loop(void) { // FUN_CODE_09a7
// 1. Process init table from CODE:0B46
// 2. Call FUN_CODE_13c3 (USB/peripheral setup)
// 3. EA = 1 (global interrupts enable)
while (1) {
if (sudav_flag) {
handle_setupdata(); // Process USB SETUP packet
sudav_flag = 0;
}
if (gpif_flag) {
handle_gpif_event();
gpif_flag = 0;
} else {
PCON |= 0x01; // CPU idle until next interrupt
}
}
}
```
The SUDAV ISR simply sets `sudav_flag = 1` and clears the interrupt. All actual USB processing happens in the main loop context.
---
## 12. I2C Bus Architecture
### 12.1 FX2 I2C Controller
The FX2's I2C master controller is a hardware peripheral accessed through SFRs:
The EEPROM at 0x51 stores: device serial number (read by GET_SERIAL_NUMBER 0x93), hardware platform ID (read by GET_FPGA_VERS 0x95), and calibration data.
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 as a write phase before the read phase:
```
Complete I2C transaction for reading register 0xA2 from device 0x08:
+------------------------------------ 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.
**FX2 I2C SFR sequence for combined read (from custom firmware):**
```c
I2CS |= bmSTART; // Generate START
I2DAT = 0x10; // Write: device addr + W
// wait bmDONE, check bmACK
I2DAT = 0xA2; // Write: register address
// wait bmDONE, check bmACK
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
```
### 12.5 I2C STOP Corruption Bug
Sending `I2CS |= bmSTOP` when no I2C transaction is active (no prior START issued, bus idle) corrupts the FX2 I2C controller's internal state machine. The bmSTOP bit may not self-clear, and subsequent START conditions fail to detect ACK from slaves.
This was the root cause of the firmware hang in custom v3.01.0 during boot. The stock firmware's "bus reset" step:
```c
/* BROKEN: */
I2CS |= bmSTOP;
i2c_wait_stop();
```
was removed. The correct approach is to simply proceed with a new START condition. If the bus is idle (after power-on or after the previous transaction completed normally), the START succeeds and the controller enters its normal operating state. The Cypress TRM does not document STOP as a standalone bus-reset mechanism.
### 12.6 Timeout Protection
The fx2lib I2C functions poll `bmDONE` with no timeout:
```c
while (!(I2CS & bmDONE) && !cancel_i2c_trans);
```
Since `cancel_i2c_trans` is never set during normal operation, these loops are effectively infinite. The custom firmware replaces all fx2lib I2C functions with timeout-protected wrappers:
```c
#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.
---
## 13. Custom Firmware v3.01.0
### 13.1 Overview
Custom replacement firmware built with SDCC and fx2lib. Loaded into FX2 RAM for testing via `fw_load.py` (not flashed to EEPROM).
| Property | Value |
|----------|-------|
| Toolchain | SDCC + fx2lib |
| Source | `firmware/skywalker1.c` (1351 lines) |
| Version ID | 0x030100 |
| Build date | 2026-02-12 |
| Load method | RAM upload via `tools/fw_load.py` |
### 13.2 Stock-Compatible Commands
The custom firmware implements all commands needed for the kernel driver: GET_8PSK_CONFIG (0x80), ARM_TRANSFER (0x85), TUNE_8PSK (0x86), GET_SIGNAL_STRENGTH (0x87), BOOT_8PSK (0x89), START_INTERSIL (0x8A), SET_LNB_VOLTAGE (0x8B), SET_22KHZ_TONE (0x8C), SEND_DISEQC (0x8D), GET_SIGNAL_LOCK (0x90), GET_FW_VERS (0x92), USE_EXTRA_VOLT (0x94).
### 13.3 Custom Commands
| Command | Function |
|---------|----------|
| SPECTRUM_SWEEP (0xB0) | Step through frequency range reading signal energy |
| RAW_DEMOD_READ (0xB1) | Read any BCM4500 indirect register |
| RAW_DEMOD_WRITE (0xB2) | Write any BCM4500 indirect register |
| BLIND_SCAN (0xB3) | Try symbol rates at a frequency looking for lock |
| I2C_BUS_SCAN (0xB4) | Probe all 7-bit I2C addresses |
| I2C_RAW_READ (0xB5) | Read from any I2C device address |
The SkyWalker-1's inability to receive DVB-S2 is a fundamental hardware limitation of the BCM4500 demodulator silicon. The BCM4500 was designed before the DVB-S2 standard was ratified (March 2005) and contains no LDPC or BCH decoder hardware. No firmware update can add DVB-S2 support.
1. The firmware modulation dispatch table has exactly 10 entries (0--9), with no DVB-S2-specific modes. The bounds check at CODE:0866 rejects values >= 10.
2. No LDPC/BCH code rate values exist in any FEC lookup table. The XRAM tables at 0xE0B1, 0xE0B7, 0xE0BC, 0xE0BD, and 0xE0F9 contain only Viterbi rates (1/2 through 7/8), turbo rates, and DCII combined codes.
3. No DVB-S2-specific register addresses appear in any I2C traffic. The BCM4500 is programmed exclusively through indirect registers 0xA6/0xA7/0xA8 with page 0x00.
**From Windows BDA driver source:**
4.`SkyWalker1TunerFilter.cpp` (line 1070): `else if(ulNewInnerFecType == BDA_FEC_VITERBI)` -- only Viterbi FEC is accepted; any other type returns `STATUS_INVALID_PARAMETER`.
5.`SkyWalker1Control.cpp` (line 292): `ucCommand[8] = ADV_MOD_DVB_QPSK;` -- the driver hardcodes modulation type 0 (DVB-S QPSK) regardless of application request.
6.`SkyWalker1Control.h` (lines 64--74): modulation constants cap at `ADV_MOD_DVB_BPSK` (9). No value 10+ exists.
**From datasheets:**
7. The BCM4500 datasheet describes exactly two FEC paths: "an advanced modulation turbo decoder" and "a DVB/DIRECTV/DCII-compliant FEC decoder." No third path for LDPC/BCH.
8. BCM4500 specification: 128-pin MQFP, 3.3V I/O, 1.8V digital, symbol rate 256 Ksps to 30 Msps. No mention of LDPC, BCH, or DVB-S2.
Broadcom restricted BCM4501/4505/4506 sales to set-top box manufacturers, preventing Genpix from using them.
### 14.5 What Genpix Did
Released the SkyWalker-3, replacing the BCM4500 with a different demodulator (likely STMicroelectronics STV0903). The trade-off: gained DVB-S2 LDPC/BCH support, lost proprietary turbo-FEC support (turbo codes are Broadcom/EchoStar proprietary).
### 14.6 USB Data Path is Not the Bottleneck
The GPIF/USB 2.0 path has approximately 5x headroom for DVB-S2 rates (~58 Mbps max vs ~280 Mbps USB practical throughput). The 8-bit transport stream interface uses the same MPEG-TS format (188-byte packets). The bottleneck is the demodulator silicon.
---
## 15. Kernel Driver Notes
### 15.1 Module Names
-`dvb_usb_gp8psk` -- USB transport and device management
The kernel module auto-loads via udev when VID:PID `09C0:0203` appears on the USB bus (every FX2 re-enumeration after firmware load). The driver races with test tools and sends its own BOOT_8PSK command.
Symptoms:
- "resource busy" or "entity not found" errors from test scripts
- BCM4500 enters unexpected state from partial kernel initialization
- Kernel driver detaches mid-test
**Fix**: Blacklist the module:
```
# /etc/modprobe.d/blacklist-gp8psk.conf
blacklist dvb_usb_gp8psk
blacklist gp8psk_fe
```
Then unload: `sudo modprobe -r dvb_usb_gp8psk gp8psk_fe`
### 15.3 FPGA Version Failure
```
gp8psk: usb in 149 operation failed.
gp8psk: failed to get FPGA version
```
Command 0x95 (GET_FPGA_VERS, decimal 149) fails on some SkyWalker-1 units. The driver logs the failure but continues normally.
### 16.3 DVB-USB Binary Hexline Format (Kernel FW01)
The format the kernel expects for `dvb-usb-gp8psk-01.fw` (only needed for Rev.1 Cold, PID 0x0200):
```
Record structure:
Offset Size Field
0 1 len - Number of data bytes
1 1 addr_lo - Target address low byte
2 1 addr_hi - Target address high byte
3 1 type - 0x00=data, 0x01=EOF, 0x04=extended addr
4 len data[] - Payload bytes
4+len 1 chk - Checksum byte
```
### 16.4 FW02 Chunk Format (BCM4500 Firmware)
Only needed for Rev.1 Warm (PID 0x0201):
```
Chunk format:
Byte 0: payload_length (N)
Bytes 1-3: header/address bytes
Bytes 4..N+3: payload data
Terminator: single byte 0xFF
Maximum chunk size: 64 bytes (USB control transfer limit)
```
Command 0x88 (LOAD_BCM4500) initiates the transfer. Each chunk is sent via bulk endpoint 0x01. On the SkyWalker-1, 0x88 routes to STALL (BCM4500 firmware is in ROM).
### 16.5 Format Incompatibility
C2 (EEPROM) and hexline (kernel FW01) are structurally different containers. They cannot be used interchangeably, but the payload data is identical. A C2 file can be converted to hexline by stripping the 8-byte header, splitting segments into 16-byte records, and appending an EOF record.
---
## 17. Debugging Reference
### 17.1 I2C STOP Corruption Root Cause
The root cause of the initial firmware hang was traced through incremental debug modes:
| 0x85 | GPIO + power + probe (no bmSTOP) | Works | Confirms bmSTOP is the cause |
| 0x84 | I2C probe only (chip already powered) | Works | BCM4500 is alive; I2C function is correct |
Key finding: mode 0x84 succeeds immediately after 0x82 fails, proving the BCM4500 was alive the whole time. The FX2 I2C controller was in a bad state, not the bus or slave.
### 17.2 Boot Results After Fix
| Metric | Value |
|--------|-------|
| Boot time | ~90 ms total |
| config_status | 0x03 (STARTED + FW_LOADED) |
| boot_stage | 0xFF (COMPLETE) |
| Direct registers 0xA2-0xA8 | All return 0x02 (powered, not locked) |
| Signal lock | 0x00 (no lock -- dish not aimed) |
| USB responsiveness | No hang; fully responsive throughout |