skywalker-1/tuning-protocol-analysis.md
Ryan Malloy bbdcb243dc Normalize line endings to LF across entire repository
Apply .gitattributes normalization to convert all CRLF line
endings inherited from Windows-origin source files to Unix LF.
175 files, zero content changes.
2026-02-20 10:55:50 -07:00

32 KiB

Genpix SkyWalker-1 TUNE_8PSK (0x86) Protocol Deep-Dive

Executive Summary

This document presents a complete reverse engineering analysis of the TUNE_8PSK vendor command (0x86) as implemented in three Genpix SkyWalker-1 FX2 firmware versions. The analysis traces the full signal path from USB control transfer through EP0BUF parameter parsing, modulation-specific BCM4500 configuration, I2C register programming, and signal acquisition polling. Supporting commands (LNB voltage, 22 kHz tone, signal lock, signal strength) are fully documented.

Firmware versions analyzed:

  • v2.06 (Ghidra port 8193) -- 61 functions, earliest revision
  • v2.13 FW1 (Ghidra port 8194) -- 88 functions, latest revision
  • Rev.2 v2.10.4 (Ghidra port 8197) -- 107 functions, intermediate revision

Primary analysis target: Rev.2 v2.10.4 (clearest decompilation, most granular function decomposition)


1. TUNE_8PSK Command Handler (0x86)

1.1 Vendor Dispatch Entry Points

All three firmware versions use identical vendor command dispatch logic at CODE:0056. The dispatcher checks bmRequestType bit 6 (vendor request), validates bRequest is in range, and performs an indexed jump via JMP @A+DPTR at the jump table starting at CODE:0076.

Firmware TUNE_8PSK Jump Target Config Status Byte Tune Function
v2.06 0x012E IRAM 0x6D 0x0800 (via FUN_CODE_09a7)
v2.13 0x012E IRAM 0x4F 0x09DF
Rev.2 v2.10.4 0x0118 IRAM 0x4E 0x0800

1.2 Handler Inline Code

The TUNE_8PSK handler at the jump table target performs two operations:

  1. Wait for EP0 data phase -- calls the EP0 flush function to ensure the 10-byte command payload has arrived in EP0BUF
  2. Check device status -- reads the config status byte and verifies bit 0 (bm8pskStarted) is set before proceeding
  3. Call tune function -- if the device is started, calls the main tune function

Rev.2 v2.10.4 (0x0118):

CODE:0118  LCALL 0x2167          ; EP0 flush -- wait for EP0BUF data ready
CODE:011B  MOV   A, 0x4E         ; Read config status byte (IRAM 0x4E)
CODE:011D  JNB   ACC.0, +3       ; Skip tune if device not started
CODE:0120  LCALL 0x0800          ; Call main tune function (FUN_CODE_0800)

v2.06 (0x012E):

CODE:012E  LCALL 0x23E0          ; EP0 flush function
CODE:0131  MOV   A, 0x6D         ; Config status byte (IRAM 0x6D)
CODE:0133  JNB   ACC.0, +3       ; Skip if not started
CODE:0136  LCALL 0x0800          ; Main tune function

v2.13 (0x012E):

CODE:012E  LCALL 0x231E          ; EP0 flush function
CODE:0131  MOV   A, 0x4F         ; Config status byte (IRAM 0x4F)
CODE:0133  JNB   ACC.0, +3       ; Skip if not started
CODE:0136  LCALL 0x09DF          ; Main tune function (different address)

2. EP0BUF Parameter Parsing

2.1 10-Byte Command Format

The host (Windows BDA driver or Linux dvb-usb-gp8psk) sends a 10-byte payload via USB control transfer OUT:

USB Setup Packet:
  bmRequestType = 0x40 (Vendor, Host-to-Device)
  bRequest      = 0x86 (TUNE_8PSK)
  wValue        = 0x0000
  wIndex        = 0x0000
  wLength       = 10

EP0BUF Layout (0xE740-0xE749):
  Offset  XRAM Addr  Content              Encoding
  ------  ---------  -------------------  ------------------
  [0]     0xE740     Symbol Rate byte 0   Little-endian LSB
  [1]     0xE741     Symbol Rate byte 1
  [2]     0xE742     Symbol Rate byte 2
  [3]     0xE743     Symbol Rate byte 3   Little-endian MSB
  [4]     0xE744     Frequency byte 0     Little-endian LSB
  [5]     0xE745     Frequency byte 1
  [6]     0xE746     Frequency byte 2
  [7]     0xE747     Frequency byte 3     Little-endian MSB
  [8]     0xE748     Modulation Type      0-9 (see section 3)
  [9]     0xE749     Inner FEC Rate       See FEC tables

Symbol Rate is in samples per second (sps). The Windows driver converts from ksps: ulTempSymbolRate = pDeviceParameter->ulSymbolRate * 1000

Frequency is in kHz, computed as (CarrierFrequency - LO_Frequency) * FrequencyMultiplier. Valid range: 800,000 - 2,250,000 kHz (after LNB downconversion: 950-2150 MHz IF).

2.2 Firmware EP0BUF Read Sequence (Rev.2 v2.10.4 Disassembly)

The tune function at 0x0800 begins with direct EP0BUF reads:

; Read modulation type and FEC rate
CODE:0802  MOV   DPTR, #0xE748   ; EP0BUF[8] = modulation type
CODE:0805  MOVX  A, @DPTR
CODE:0806  MOV   0x4D, A         ; Store to IRAM 0x4D (DAT_INTMEM_4d)
CODE:0808  INC   DPTR            ; DPTR = 0xE749
CODE:0809  MOVX  A, @DPTR        ; EP0BUF[9] = FEC rate
CODE:080A  MOV   0x4F, A         ; Store to IRAM 0x4F (DAT_INTMEM_4f)

Frequency bytes (EP0BUF[4-7]) are byte-reversed from little-endian to big-endian for BCM4500:

; Loop: i = 0..3
; EP0BUF[4+i] -> XRAM[0xE0DE - i]  (frequency, LE -> BE)
CODE:0816  MOV   A, #0x44        ; 0x44 = EP0BUF offset for freq[0] (0xE740+4=0xE744)
CODE:0818  ADD   A, 0x3B         ; + loop index i
CODE:081A  MOV   DPL, A
CODE:081C  CLR   A
CODE:081D  ADDC  A, #0xE7        ; DPH = 0xE7
CODE:0821  MOVX  A, @DPTR        ; Read EP0BUF[4+i]
CODE:0822  MOV   R7, A           ; Save byte

; Compute destination: 0xE0D8 + (6 - i) = 0xE0DE - i
CODE:0827  MOV   A, #0x06
CODE:0829  SUBB  A, R5           ; 6 - i
CODE:082F  MOV   A, #0xD8        ; Base = 0xE0D8
CODE:0831  ADD   A, R5
CODE:0832  MOV   DPL, A
CODE:0834  MOV   A, #0xE0
CODE:0837  MOV   DPH, A
CODE:0839  MOV   A, R7
CODE:083A  MOVX  @DPTR, A        ; Store byte-reversed

Symbol rate bytes (EP0BUF[0-3]) follow the same pattern:

; EP0BUF[i] -> XRAM[0xE0C7 + (7 - i)] = XRAM[0xE0CE - i]  (symbol rate, LE -> BE)
CODE:083B  MOV   A, #0x40        ; 0x40 = EP0BUF offset for SR[0] (0xE740+0=0xE740)
CODE:083D  ADD   A, 0x3B         ; + loop index i
CODE:0846  MOVX  A, @DPTR        ; Read EP0BUF[i]

; Destination: 0xE0C7 + (7 - i) = 0xE0CE - i
CODE:084C  MOV   A, #0x07
CODE:084E  SUBB  A, R5           ; 7 - i
CODE:0854  MOV   A, #0xC7        ; Base = 0xE0C7
CODE:0856  ADD   A, R5
CODE:085F  MOVX  @DPTR, A        ; Store byte-reversed

2.3 XRAM Storage Map After Parsing

IRAM Variables:
  0x4D  Modulation type (0-9)                [from EP0BUF[8]]
  0x4F  Inner FEC rate index                  [from EP0BUF[9]]

XRAM Frequency (big-endian, 4 bytes):
  0xE0DB  Frequency byte 3 (MSB)             [from EP0BUF[7]]
  0xE0DC  Frequency byte 2                   [from EP0BUF[6]]
  0xE0DD  Frequency byte 1                   [from EP0BUF[5]]
  0xE0DE  Frequency byte 0 (LSB)             [from EP0BUF[4]]

XRAM Symbol Rate (big-endian, 4 bytes):
  0xE0CB  Symbol Rate byte 3 (MSB)           [from EP0BUF[3]]
  0xE0CC  Symbol Rate byte 2                 [from EP0BUF[2]]
  0xE0CD  Symbol Rate byte 1                 [from EP0BUF[1]]
  0xE0CE  Symbol Rate byte 0 (LSB)           [from EP0BUF[0]]

The byte reversal converts the host's little-endian format to the BCM4500's big-endian register format, so the values can be written directly to the demodulator via I2C without further conversion.


3. Modulation Mode Dispatch

3.1 Dispatch Table (Rev.2 at CODE:0873)

After parsing EP0BUF, the firmware validates the modulation type (IRAM 0x4D) is < 10, then dispatches via a jump table:

CODE:0864  MOV   A, 0x4D         ; Load modulation type
CODE:0866  CJNE  A, #0x0A, +0    ; Compare with 10
CODE:0869  JC    0x086D          ; If < 10, proceed to dispatch
CODE:086B  AJMP  0x098E          ; If >= 10, skip to post-tune (invalid mod)
CODE:086D  MOV   DPTR, #0x0873   ; Jump table base
CODE:0870  ADD   A, A            ; Double index (2 bytes per entry)
CODE:0872  JMP   @A+DPTR         ; Dispatch

Jump Table Memory (CODE:0873, 20 bytes):

Offset  Bytes   AJMP Target   Modulation Type
------  ------  -----------   ---------------
+0x00   01 B7   0x08B7        0 = ADV_MOD_DVB_QPSK
+0x02   01 DF   0x08DF        1 = ADV_MOD_TURBO_QPSK
+0x04   01 FA   0x08FA        2 = ADV_MOD_TURBO_8PSK
+0x06   21 15   0x0915        3 = ADV_MOD_TURBO_16QAM
+0x08   21 47   0x0947        4 = ADV_MOD_DCII_C_QPSK (Combo)
+0x0A   21 4F   0x094F        5 = ADV_MOD_DCII_I_QPSK (I-stream)
+0x0C   21 57   0x0957        6 = ADV_MOD_DCII_Q_QPSK (Q-stream)
+0x0E   21 5F   0x095F        7 = ADV_MOD_DCII_C_OQPSK (Offset)
+0x10   01 87   0x0887        8 = ADV_MOD_DSS_QPSK
+0x12   01 87   0x0887        9 = ADV_MOD_DVB_BPSK

Note: Modulations 8 (DSS) and 9 (DVB BPSK) share the same handler at 0x0887.

3.2 Modulation Handler Details

Each handler reads the FEC rate from IRAM 0x4F, validates it against a maximum count, looks up a preconfigured value from an XRAM table, and writes it to XRAM 0xE0EB (the BCM4500 FEC configuration register). The handlers then set additional XRAM configuration registers.

Modulation 0: DVB-S QPSK (0x08B7)

CODE:08B7  MOV   A, 0x4F         ; FEC index
CODE:08BA  SUBB  A, #0x07        ; Max 7 FEC rates
CODE:08BC  JNC   0x08D0          ; Out of range -> return error
CODE:08BE  MOV   A, #0xF9        ; XRAM table base = 0xE0F9
CODE:08C0  ADD   A, 0x4F         ; + FEC index
CODE:08C2  MOV   DPL, A          ; -> DPTR = 0xE0F9 + i
CODE:08C9  MOVX  A, @DPTR        ; Read FEC lookup value
CODE:08CA  MOV   DPTR, #0xE0EB   ; BCM4500 FEC register
CODE:08CD  MOVX  @DPTR, A        ; Write FEC value

; Set remaining config:
CODE:08D2  XRAM[0xE0EC] = 0x09   ; Modulation type register
CODE:08D9  XRAM[0xE0F6] = 0x00   ; Turbo mode flag = OFF
; Falls through to 0x093C:
CODE:093C  XRAM[0xE0F5] = 0x10   ; Demod mode = standard
CODE:0942  IRAM[0x4E] &= 0xBF   ; Clear bmDCtuned flag

Modulations 8/9: DSS QPSK / DVB BPSK (0x0887)

CODE:0887  CLR   0x04            ; Clear _0_4 flag (non-turbo indicator)
CODE:0889  MOV   A, 0x4F         ; FEC index
CODE:088C  SUBB  A, #0x07        ; Max 7 FEC rates
CODE:088E  JNC   0x08A4          ; Out of range -> use default
; In range:
CODE:0890  XRAM[0xE0EB] = XRAM[0xE0F9 + FEC_index] | 0x80  ; FEC with bit 7 set
; Out of range:
CODE:08A4  XRAM[0xE0EB] = 0x8C   ; Default FEC value
; Common:
CODE:08AA  XRAM[0xE0EC] = 0x09   ; Mod type register
CODE:08B1  XRAM[0xE0F6] = 0x00   ; Turbo flag = OFF
; Falls through to 0x093C (same as DVB-S QPSK)

Modulation 1: Turbo QPSK (0x08DF)

CODE:08DF  MOV   A, 0x4F         ; FEC index
CODE:08E2  SUBB  A, #0x05        ; Max 5 Turbo QPSK FEC rates
CODE:08E4  JNC   0x08F8          ; Out of range -> error
CODE:08E6  XRAM[0xE0EB] = XRAM[0xE0B7 + FEC_index]  ; Turbo QPSK FEC table
; Falls through to 0x0930:
CODE:0930  XRAM[0xE0EC] = 0x09   ; Mod type
CODE:0936  XRAM[0xE0F6] = 0x01   ; Turbo flag = ON
; Falls through to 0x093C:
CODE:093C  XRAM[0xE0F5] = 0x10   ; Demod mode = Turbo
CODE:0942  IRAM[0x4E] &= 0xBF   ; Clear bmDCtuned

Modulation 2: Turbo 8PSK (0x08FA)

CODE:08FA  MOV   A, 0x4F         ; FEC index
CODE:08FD  SUBB  A, #0x05        ; Max 5 Turbo 8PSK FEC rates
CODE:08FF  JNC   0x0913          ; Out of range -> error
CODE:0901  XRAM[0xE0EB] = XRAM[0xE0B1 + FEC_index]  ; Turbo 8PSK FEC table
; Falls through to 0x0930 (same as Turbo QPSK)

Modulation 3: Turbo 16QAM (0x0915)

CODE:0915  MOV   A, 0x4F         ; FEC index
CODE:0918  SUBB  A, #0x01        ; Max 1 Turbo 16QAM FEC rate
CODE:091A  JNC   0x092E          ; Out of range -> error
CODE:091C  XRAM[0xE0EB] = XRAM[0xE0BC + FEC_index]  ; Turbo 16QAM FEC table
; Falls through to 0x0930 (same as Turbo QPSK/8PSK)

Modulations 4-7: Digicipher II Variants (0x0947-0x095F)

All four DCII handlers share common post-processing but set different demod modes:

; Mod 4: DCII Combo (0x0947)
CODE:0947  XRAM[0xE0F5] = 0x10   ; Demod mode = DCII Combo
CODE:094D  SJMP  0x0965          ; -> common DCII handler

; Mod 5: DCII I-stream (0x094F)
CODE:094F  XRAM[0xE0F5] = 0x12   ; Demod mode = DCII I-stream (split)
CODE:0955  SJMP  0x0965

; Mod 6: DCII Q-stream (0x0957)
CODE:0957  XRAM[0xE0F5] = 0x16   ; Demod mode = DCII Q-stream (split)
CODE:095D  SJMP  0x0965

; Mod 7: DCII Offset QPSK (0x095F)
CODE:095F  XRAM[0xE0F5] = 0x11   ; Demod mode = DCII Offset QPSK
CODE:0964  ; Falls through to 0x0965

; Common DCII handler (0x0965):
CODE:0965  MOV   A, 0x4F         ; FEC index
CODE:0968  SUBB  A, #0x09        ; Max 9 DCII FEC rates
CODE:096A  JNC   0x097E          ; Out of range -> error
CODE:096C  XRAM[0xE0EC] = XRAM[0xE0BD + FEC_index]  ; DCII FEC/mod table
CODE:0980  XRAM[0xE0EB] = 0xFC   ; Fixed DCII FEC code
CODE:0987  XRAM[0xE0F6] = 0x00   ; Turbo flag = OFF
CODE:098B  IRAM[0x4E] |= 0x40   ; SET bmDCtuned flag

3.3 BCM4500 XRAM Configuration Summary

After modulation dispatch completes, four XRAM registers hold the BCM4500 configuration:

XRAM Address Register Name DVB-S Turbo QPSK/8PSK/16QAM DCII DSS/BPSK
0xE0EB FEC Code Rate Lookup from 0xE0F9+i Lookup from tables 0xFC (fixed) Lookup
0xE0EC Modulation Type 0x09 0x09 From 0xE0BD+i table 0x09
0xE0F5 Demod Mode 0x10 0x10 0x10/0x11/0x12/0x16 0x10
0xE0F6 Turbo Flag 0x00 0x01 0x00 0x00

3.4 FEC Rate Lookup Tables

The FEC lookup tables are populated at boot from the init data table in CODE space, copied to XRAM during startup:

Table Base Modulation Max Index FEC Rates
XRAM 0xE0F9 DVB-S QPSK / DSS / BPSK 7 Standard Viterbi rates (1/2, 2/3, 3/4, 5/6, 7/8, auto, none)
XRAM 0xE0B7 Turbo QPSK 5 Turbo QPSK code rates
XRAM 0xE0B1 Turbo 8PSK 5 Turbo 8PSK code rates
XRAM 0xE0BC Turbo 16QAM 1 Single 16QAM code rate
XRAM 0xE0BD DCII (all variants) 9 DCII code rate + modulation combined

4. BCM4500 I2C Write Sequence

4.1 Post-Dispatch: DVB Mode and I2C Programming

After the modulation dispatch sets XRAM configuration registers, the tune function enters the common post-processing path at CODE:098E:

; Copy _0_4 flag to _0_5 (DVB/non-DVB indicator)
CODE:098E  MOV   CY, 0x04        ; Read _0_4 flag
CODE:0990  MOV   0x05, CY        ; Copy to _0_5

; Configure DVB mode pin
CODE:0992  LCALL 0x21D3          ; FUN_CODE_21d3: SET_DVB_MODE
                                 ; Sets P3.6 based on _0_4

; Attempt I2C programming (up to 3 tries)
CODE:0995  MOV   0x3B, #0x03     ; Retry counter = 3
CODE:0998  MOV   A, 0x3B
CODE:099A  JZ    0x09A7          ; If 0 retries left, return failure
CODE:099C  LCALL 0x1DD0          ; FUN_CODE_1dd0: Demod I2C write sequence
CODE:099F  JC    0x09A3          ; If carry set (success), return
CODE:09A1  SETB  CY              ; Set carry = success
CODE:09A2  RET
CODE:09A3  DEC   0x3B            ; Decrement retry counter
CODE:09A5  SJMP  0x0998          ; Try again
CODE:09A7  CLR   CY              ; Clear carry = failure
CODE:09A8  RET

4.2 FUN_CODE_1DD0: Demod Scan (3 I2C Address Attempts)

The demod scan function tries to program the BCM4500 at up to three different I2C device addresses. This supports hardware variants where the BCM4500 may appear at different I2C addresses:

// Pseudocode for FUN_CODE_1dd0 (Rev.2 v2.10.4)
char demod_scan(void) {
    for (DAT_INTMEM_3c = 0; DAT_INTMEM_3c < 3; DAT_INTMEM_3c++) {
        // Compute I2C parameters from iteration index
        // The multiplication by 0x11 and offset calculations produce:
        //   Iteration 0: device addr derived, register set A
        //   Iteration 1: device addr derived, register set B
        //   Iteration 2: device addr derived, register set C
        result = FUN_CODE_1670(buf_addr, device_param, data_len);
        if (result == success) {
            return success;  // Carry set
        }
    }
    return failure;  // Carry clear
}

4.3 FUN_CODE_1670: BCM4500 Indirect Register Write

This is the core I2C programming function. The BCM4500 uses an indirect register access protocol through three I2C-accessible registers:

BCM4500 I2C Registers (accessed at device address 0x10):
  0xA6 = Page/Address register (indirect address high byte)
  0xA7 = Data register (indirect data)
  0xA8 = Command register (indirect command: 0x03 = write)

FUN_CODE_1670 decompiled (Rev.2 v2.10.4):

void bcm4500_indirect_write(byte buf_hi, byte device_param, byte data_count) {
    // Step 1: Wait for BCM4500 ready (poll regs 0xA2, 0xA8, 0xA4)
    FUN_CODE_1e73();  // 3-register bus wait
    if (!carry) return;  // BCM4500 not ready

    // Step 2: Write page address (0x00) to register 0xA6
    XRAM[0xE111] = 0x00;           // Page = 0
    IRAM[0x45] = 0xE1;             // Buffer pointer high
    IRAM[0x46] = 0x11;             // Buffer pointer low -> XRAM 0xE111
    FUN_CODE_136c(1, 0, 0xA6, 0x10);  // I2C write 1 byte to device 0x10, reg 0xA6

    // Step 3: Write data to register 0xA7
    IRAM[0x45] = buf_hi;           // Source buffer high byte
    IRAM[0x46] = device_param;     // Source buffer low byte
    FUN_CODE_136c(data_count, 0, 0xA7, 0x10);  // I2C write N bytes to reg 0xA7

    // Step 4: Write command (0x03 = indirect write) to register 0xA8
    XRAM[0xE111] = 0x03;           // Command = write
    IRAM[0x45] = 0xE1;
    IRAM[0x46] = 0x11;
    FUN_CODE_136c(1, 0, 0xA8, 0x10);  // I2C write 1 byte to reg 0xA8

    // Step 5: Wait for write completion (poll regs 0xA8, 0xA2)
    FUN_CODE_1ea9();

    // Step 6: Verify -- read back from 0xA7 and compare
    XRAM[0xE111] = 0x00;
    FUN_CODE_136c(1, 0, 0xA6, 0x10);  // Re-select page 0
    FUN_CODE_20cb();                    // Read reg 0xA7
    // Compare read-back with expected value
}

4.4 FUN_CODE_136C: I2C Multi-Byte Write Primitive

This is the lowest-level I2C write function that talks to the FX2's I2C controller:

// Pseudocode for FUN_CODE_136c (Rev.2 v2.10.4)
void i2c_write(byte byte_count, byte flags, byte register_addr, byte device_addr) {
    // Setup I2C controller at XRAM 0xE678 (FX2 I2C register)
    FUN_CODE_2000();                     // Wait for I2C bus ready
    FUN_CODE_2206(device_addr << 1);     // Send I2C start + device address (write)
    FUN_CODE_2224(register_addr);        // Send register address

    // Transfer data bytes from buffer at IRAM[0x45:0x46]
    for (i = 0; i < byte_count; i++) {
        byte data = XRAM[IRAM[0x45]:IRAM[0x46] + i];
        FUN_CODE_2224(data);             // Send each data byte
    }

    // Send I2C stop condition
    FUN_CODE_1aa3();                     // I2C stop + cleanup
}

4.5 Complete I2C Register Write Sequence for a Tune Operation

Combining all the above, a complete tune operation produces the following I2C bus transactions:

=== Phase 1: Pre-Tune LNB/Tone Configuration ===
(These happen before TUNE_8PSK, via separate vendor commands)

1. SET_LNB_VOLTAGE (0x8B): GPIO P0.4 high/low  (no I2C)
2. SET_22KHZ_TONE (0x8C):  GPIO P0.3 high/low   (no I2C)

=== Phase 2: Tune Parameter Setup ===
(EP0BUF parsing -- no I2C, just XRAM writes)

3. EP0BUF[8] -> IRAM[0x4D]          (modulation)
4. EP0BUF[9] -> IRAM[0x4F]          (FEC rate)
5. EP0BUF[4-7] -> XRAM[0xE0DB-0xE0DE] (frequency, byte-reversed)
6. EP0BUF[0-3] -> XRAM[0xE0CB-0xE0CE] (symbol rate, byte-reversed)

=== Phase 3: Modulation Dispatch ===
(XRAM configuration writes -- no I2C)

7. XRAM[0xE0EB] = FEC lookup value   (from modulation-specific table)
8. XRAM[0xE0EC] = modulation type     (usually 0x09)
9. XRAM[0xE0F5] = demod mode          (0x10/0x11/0x12/0x16)
10. XRAM[0xE0F6] = turbo flag         (0x00 or 0x01)

=== Phase 4: DVB Mode GPIO ===

11. P3.6 set/clear based on _0_4 flag  (DVB vs non-DVB mode)

=== Phase 5: BCM4500 I2C Programming (x3 retry, x3 addresses) ===

For each attempt (up to 3 retries, each trying up to 3 I2C addresses):

  --- Wait for BCM4500 ready ---
  12. I2C READ  device 0x10, reg 0xA2   (poll status register)
  13. I2C READ  device 0x10, reg 0xA8   (poll command register)
  14. I2C READ  device 0x10, reg 0xA4   (poll lock/ready register)

  --- Write page address ---
  15. I2C WRITE device 0x10, reg 0xA6 <- 0x00  (select page 0)

  --- Write configuration data ---
  16. I2C WRITE device 0x10, reg 0xA7 <- [N config bytes from XRAM buffer]
      Data includes: frequency, symbol rate, FEC, modulation, demod mode,
      turbo flag -- assembled from XRAM 0xE0EB/EC/F5/F6 and 0xE0CB-CE/DB-DE

  --- Issue indirect write command ---
  17. I2C WRITE device 0x10, reg 0xA8 <- 0x03  (execute indirect write)

  --- Wait for completion ---
  18. I2C READ  device 0x10, reg 0xA8   (poll for command done)
  19. I2C READ  device 0x10, reg 0xA2   (verify status)

  --- Verify write ---
  20. I2C WRITE device 0x10, reg 0xA6 <- 0x00  (re-select page)
  21. I2C READ  device 0x10, reg 0xA7   (read back data)
  22. Compare read-back with expected value

4.6 I2C Device Address Note

The BCM4500 demodulator is accessed at I2C device address 0x10 (7-bit address, shifted to 0x20 for write, 0x21 for read on the wire). In some firmware versions, the device also responds at alternate addresses:

  • 0x3F (used by v2.13 for status polling in INT0)
  • 0x7F (used by v2.13 for status polling in INT0)

The use of 0x3F and 0x7F in v2.13's INT0 handler (demod availability probing) suggests these may be alternate I2C addresses on different hardware revisions, or they may access different internal register banks of the BCM4500.


5. Signal Acquisition

5.1 GET_SIGNAL_LOCK (Vendor Command 0x90)

Jump table targets:

  • v2.06: 0x020B
  • v2.13: 0x022D
  • Rev.2: 0x0217

The signal lock handler reads BCM4500 register 0xA4 and returns the result to the host via EP0BUF.

Rev.2 implementation chain:

; Vendor command handler at 0x0217:
CODE:0217  MOV   A, 0x4E         ; Config status byte
CODE:0219  JNB   ACC.0, ...      ; Check device started
CODE:021C  LCALL 0x2236          ; FUN_CODE_2236: Read lock register

; FUN_CODE_2236:
CODE:2236  MOV   R7, #0xA4       ; Register = 0xA4
CODE:2238  LCALL 0x20CB          ; FUN_CODE_20cb: I2C read

; FUN_CODE_20cb: I2C single register read
;   Sets up buffer at XRAM 0xE114
;   Calls FUN_CODE_0f00(device=0x0F, offset=0, reg=param, addr=0x10)
;   Returns byte from XRAM[0xE114]

I2C transaction:

I2C READ device 0x10, register 0xA4, 1 byte -> XRAM[0xE114]

Register 0xA4 bit fields:

Bit 5 (0x20): Signal locked (demod has achieved lock)

The firmware returns the raw register byte to EP0BUF. The host driver (both Linux and Windows) interprets any non-zero value as "locked":

// Linux kernel driver (gp8psk-fe.c):
gp8psk_usb_in_op(d, GET_SIGNAL_LOCK, 0, 0, &lock, 1);
if (lock)
    *status = FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI |
              FE_HAS_SIGNAL | FE_HAS_CARRIER;

// Windows BDA driver (SkyWalker1Control.cpp):
ControlUsbDevice(pKSDeviceObject, GET_SIGNAL_LOCK, 0, 0, &ucSignalStatus, 1, true);
if (ucSignalStatus)
    *pbSignalLockStatus = TRUE;

5.2 GET_SIGNAL_STRENGTH (Vendor Command 0x87)

Jump table targets:

  • v2.06: 0x0140
  • v2.13: 0x0162
  • Rev.2: 0x014C

This command returns 6 bytes of signal quality data. The first two bytes contain the SNR value.

Rev.2 implementation (FUN_CODE_15eb at 0x15EB):

The signal strength reader performs a more complex sequence than simple lock detection:

  1. Writes configuration to XRAM buffer via FUN_CODE_067e (multi-mode data access)
  2. Performs BCM4500 indirect register write via FUN_CODE_1670 (same protocol as tuning)
  3. Reads back BCM4500 register 0xA7 via FUN_CODE_0f00
  4. Compares write value with read-back for validity check
  5. If valid, copies 6 bytes of signal data to EP0BUF

I2C transactions during signal strength read:

1. I2C WRITE device 0x10, reg 0xA6 <- 0x00     (page select)
2. I2C WRITE device 0x10, reg 0xA7 <- [config]  (request signal data)
3. I2C WRITE device 0x10, reg 0xA8 <- 0x03     (execute)
4. I2C READ  device 0x10, reg 0xA7, 1 byte     (verify/read)
5. Copy 6-byte result buffer to EP0BUF

EP0BUF response format (6 bytes):

Byte 0: SNR low byte (LSB)
Byte 1: SNR high byte (MSB)
Bytes 2-5: Reserved/diagnostic (BCM4500 internal registers)

SNR scaling (from Windows BDA driver):

ulSignalStrength = (int)(ucBuffer[1]) << 8 | ucBuffer[0];
// SNR is in dBu * 256 units
// SNR * 17 maps to 0-65535 range (100% at SNR >= 0x0F00)
if (ulSignalStrength <= 0x0F00)
    *pulSigStrength = (ulSignalStrength << 4) + ulSignalStrength;  // * 17
else
    *pulSigStrength = 0xFFFF;

5.3 Differences Between Firmware Versions

v2.06 GET_SIGNAL_STRENGTH (0x0140):

  • Checks IRAM[0x6D] bit 0 (demod active)
  • Reads three I2C status registers (0xA2, 0xA8, 0xA4) to compute quality
  • Loops up to 6 iterations polling for demod readiness
  • Uses FUN_CODE_0c97 for I2C reads

v2.13 GET_SIGNAL_STRENGTH (0x0162):

  • Checks IRAM[0x4F] bit 0 (demod active)
  • Uses different function call chain (FUN_CODE_1278 vs v2.06's FUN_CODE_0c97)
  • Same overall logic with relocated internal variables
  • Simplified polling (uses consolidated BCM4500 status register)

Rev.2 GET_SIGNAL_STRENGTH (0x014C -> FUN_CODE_15eb):

  • Checks IRAM[0x4E] bit 0
  • Uses FUN_CODE_067e for multi-mode data access
  • Validates read-back against written configuration
  • Most granular implementation with explicit verify step

6. LNB and Tone Control

6.1 SET_LNB_VOLTAGE (Vendor Command 0x8B)

Jump table targets:

  • v2.06: 0x01CB
  • v2.13: 0x01ED
  • Rev.2: 0x01D7

LNB voltage selection is pure GPIO -- no I2C transactions. The FX2 pin P0.4 directly controls a voltage regulator on the PCB.

Rev.2 FUN_CODE_21b1 decompiled:

void set_lnb_voltage(void) {
    if (_0_4 != 0) {
        // wValue = 1: Set 18V (horizontal / circular-left polarization)
        P0 |= 0x10;              // P0.4 = HIGH -> 18V
        DAT_INTMEM_4e |= 0x20;   // Set bmSEL18V in config status
    } else {
        // wValue = 0: Set 13V (vertical / circular-right polarization)
        P0 &= 0xEF;              // P0.4 = LOW -> 13V
        DAT_INTMEM_4e &= 0xDF;   // Clear bmSEL18V in config status
    }
}

Pin mapping across versions:

Version 18V Pin Config Status Bit
v2.06 P0.4 IRAM[0x6D] bit 5
v2.13 P0.4 IRAM[0x4F] bit 5
Rev.2 P0.4 IRAM[0x4E] bit 5

The wValue parameter from the USB SETUP packet is passed via the _0_4 bit flag (IRAM bit-addressable area). The Windows driver sends:

ControlUsbDevice(device, SET_LNB_VOLTAGE, (ucVoltage == SEC_VOLTAGE_18), 0, NULL, 0);

Polarization mapping:

  • Horizontal / Circular-Left: 18V (wValue=1)
  • Vertical / Circular-Right: 13V (wValue=0)

6.2 SET_22KHZ_TONE (Vendor Command 0x8C)

Jump table targets:

  • v2.06: 0x01DD
  • v2.13: 0x01FF
  • Rev.2: 0x01E9

Like LNB voltage, the 22 kHz tone is pure GPIO. P0.3 enables/disables an external 22 kHz oscillator on the PCB.

Rev.2 FUN_CODE_21c2 decompiled:

void set_22khz_tone(void) {
    if (_0_4 != 0) {
        // wValue = 1: Tone ON (high band)
        P0 |= 0x08;              // P0.3 = HIGH -> 22 kHz oscillator enabled
        DAT_INTMEM_4e |= 0x10;   // Set bm22kHz in config status
    } else {
        // wValue = 0: Tone OFF (low band)
        P0 &= 0xF7;              // P0.3 = LOW -> 22 kHz oscillator disabled
        DAT_INTMEM_4e &= 0xEF;   // Clear bm22kHz in config status
    }
}

Pin mapping across versions:

Version Tone Pin Config Status Bit
v2.06 P0.3 IRAM[0x6D] bit 4
v2.13 P0.3 IRAM[0x4F] bit 4
Rev.2 P0.3 IRAM[0x4E] bit 4

The 22 kHz tone is used for satellite band selection:

  • Tone ON: Select high-band LNB oscillator (universal LNB: 10.6 GHz LO)
  • Tone OFF: Select low-band LNB oscillator (universal LNB: 9.75 GHz LO)

Windows driver tone logic:

// SEC_TONE_ON = 0, SEC_TONE_OFF = 1
ControlUsbDevice(device, SET_22KHZ_TONE, (ucTone == 0), 0, NULL, 0);

Note the inverted logic: SEC_TONE_ON = 0 results in wValue = 1 (tone active).

6.3 ConfigureTuner: Full Tuning Sequence (Windows BDA Driver)

The Windows driver's ConfigureTuner() function shows the complete host-side tuning sequence:

NTSTATUS ConfigureTuner(PKSDEVICE device, PBDATUNER_DEVICE_PARAMETER config) {
    // Step 1: Set LNB voltage based on polarization
    if (config->Polarity == BDA_POLARISATION_LINEAR_H ||
        config->Polarity == BDA_POLARISATION_CIRCULAR_L) {
        SetLnbVoltage(device, SEC_VOLTAGE_18);   // 0x8B, wValue=1
    } else {
        SetLnbVoltage(device, SEC_VOLTAGE_13);   // 0x8B, wValue=0
    }

    // Step 2: Disable tone during tune
    SetTunerTone(device, SEC_TONE_OFF);           // 0x8C, wValue=0

    // Step 3: Send tune command
    TuneDevice(device, config);                   // 0x86, 10 bytes
}

7. GPIO Pin Summary

All LNB, tone, and DVB mode control is performed through direct GPIO manipulation with no I2C involvement:

FX2 Port 0 Pin Assignments (Rev.2 v2.10.4):
  P0.0  -- (unused in Rev.2; DiSEqC data in v2.13)
  P0.1  -- (unused)
  P0.2  -- (set during init, purpose TBD)
  P0.3  -- 22 kHz tone oscillator enable (all versions)
  P0.4  -- LNB 13V/18V voltage select (all versions)
           Also DiSEqC data pin in Rev.2
  P0.5  -- (unused)
  P0.6  -- GPIO control (FUN_CODE_1fcf)
  P0.7  -- DiSEqC data pin (v2.06 only)

FX2 Port 3 Pin Assignments (Rev.2 v2.10.4):
  P3.4  -- GPIO control (FUN_CODE_1fcf)
  P3.6  -- DVB mode select (SET_DVB_MODE, FUN_CODE_21d3)

8. Cross-Version Comparison

8.1 Tune Function Correspondence

Component v2.06 v2.13 Rev.2 v2.10.4
Vendor dispatch 0x0056 0x0056 0x0056
TUNE_8PSK handler 0x012E 0x012E 0x0118
EP0 flush 0x23E0 0x231E 0x2167
Main tune function 0x0800 0x09DF 0x0800
Config status byte IRAM 0x6D IRAM 0x4F IRAM 0x4E
Modulation storage IRAM 0x4D* IRAM 0x4D IRAM 0x4D
FEC storage IRAM 0x4F* IRAM 0x4F IRAM 0x4F
Mod dispatch table 0x0873* embedded in 0x09DF 0x0873
BCM4500 indirect write similar FUN_CODE_15b8 chain FUN_CODE_1670
I2C write primitive FUN_CODE_1556 FUN_CODE_15b8 FUN_CODE_136c
Signal lock read 0x020B 0x022D 0x0217 -> FUN_CODE_2236
Signal strength 0x0140 0x0162 0x014C -> FUN_CODE_15eb
LNB voltage 0x01CB 0x01ED 0x01D7 -> FUN_CODE_21b1
22 kHz tone 0x01DD 0x01FF 0x01E9 -> FUN_CODE_21c2

*v2.06 shares the same IRAM layout as Rev.2 for modulation/FEC but uses different XRAM offsets.

8.2 Key Architectural Differences

v2.06:

  • Simplest implementation, single code path
  • No demod verification or retry logic during tune
  • BCM4500 status polling reads 3 registers (0xA2, 0xA8, 0xA4)
  • Signal strength handler loops up to 6 times

Rev.2 v2.10.4:

  • 3 I2C address attempts per tune (FUN_CODE_1dd0)
  • 3 outer retries for the whole demod scan
  • BCM4500 indirect write with read-back verification (FUN_CODE_1670)
  • Most granular function decomposition
  • Smallest binary despite highest function count

v2.13:

  • Adds host-controlled DELAY_COMMAND (0x9C) for tuning timing
  • Adds INIT_DEMOD (0x9A) for host-triggered re-initialization
  • Adds GET_DEMOD_STATUS (0x99) for diagnostic register reads
  • INT0 repurposed for demod availability polling
  • Most robust error recovery (20-attempt retry loops for init)

9. Data Flow Diagram

Host Application (DVB app / szap / w_scan)
    |
    v
Linux dvb-usb-gp8psk / Windows BDA Driver
    |
    | USB Control Transfer (EP0)
    |   bmRequestType = 0x40 (Vendor OUT)
    |   bRequest = 0x86 (TUNE_8PSK)
    |   wValue = 0, wIndex = 0, wLength = 10
    |   Data: [SR0 SR1 SR2 SR3 F0 F1 F2 F3 MOD FEC]
    |
    v
FX2 Microcontroller (Cypress CY7C68013A)
    |
    | 1. EP0BUF (XRAM 0xE740-0xE749) receives 10 bytes
    | 2. Parse: mod->IRAM[0x4D], fec->IRAM[0x4F]
    | 3. Byte-reverse freq->XRAM[0xE0DB-DE], sr->XRAM[0xE0CB-CE]
    | 4. Dispatch on mod type (jump table at CODE:0873)
    | 5. Set XRAM[0xE0EB/EC/F5/F6] per modulation
    | 6. GPIO: P3.6 for DVB mode
    |
    | I2C Bus (FX2 I2C controller at XRAM 0xE678)
    |   Device address: 0x10 (BCM4500)
    |
    v
BCM4500 Demodulator (Broadcom DVB-S/8PSK)
    |
    | Indirect register protocol:
    |   reg 0xA6 <- page (0x00)
    |   reg 0xA7 <- config data (freq, SR, FEC, mod params)
    |   reg 0xA8 <- command (0x03 = write)
    |
    | After programming:
    |   reg 0xA4 bit 5 = signal lock status
    |   reg 0xA7 = SNR / signal quality readback
    |
    v
RF Front End
    |
    | LNB Control (GPIO, no I2C):
    |   P0.4 = voltage (13V/18V)
    |   P0.3 = 22 kHz tone (band select)
    |
    v
Satellite LNB -> Dish -> Satellite

Sources

  • Ghidra firmware disassembly: v2.06 (port 8193), v2.13 FW1 (port 8194), Rev.2 v2.10.4 (port 8197)
  • Windows BDA driver source: SkyWalker1_Final_Release/Source/SkyWalker1Control.cpp
  • Windows BDA driver headers: SkyWalker1_Final_Release/Include/SkyWalker1Control.h, SkyWalker1CommonDef.h
  • Linux kernel driver: drivers/media/usb/dvb-usb/gp8psk.c, gp8psk.h, gp8psk-fe.c
  • Companion documents: gp8psk-driver-analysis.md, firmware-analysis-v206-vs-v213.md, rev2-deep-analysis.md