skywalker-1/firmware/skywalker1.c
Ryan Malloy 29df688f28 Fix BCM4500 full boot: strip init block length prefixes, handle gateway poll
Two root causes prevented BCM4500 init block writes from completing:

1. Init block data arrays included length prefix bytes from the stock
   firmware's XDATA format (17-byte blocks at code:0x0B4F). The stock
   firmware reads byte 0 as length and writes bytes 1..N to A7.
   Blocks 0 and 1 had the length prefix (0x06, 0x07) as the first
   data byte, corrupting the DSP's indirect register FIFO.

2. The BCM3440 gateway's A8 register does not clear bit 0 after
   indirect write commands (0x03), even though the BCM4500 processes
   them successfully (confirmed via direct address 0x08 where A8
   transitions from 0x03 → 0x02). bcm_poll_ready() now treats
   gateway timeout as success with a settling delay.

Boot now completes reliably in ~0.96s through all stages:
GPIO → power → reset → PLL/DSP load → init blocks 0,1,2 → 0xFF.
2026-02-19 22:08:44 -07:00

2665 lines
84 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Genpix SkyWalker-1 Custom Firmware
* For Cypress CY7C68013A (FX2LP) + Broadcom BCM4500 demodulator
*
* Stock-compatible vendor commands (0x80-0x94) plus custom
* spectrum sweep, raw demod access, blind scan (0xB0-0xB3),
* hardware diagnostics (0xB4-0xB6), signal monitoring (0xB7-0xB9),
* advanced commands: parameterized sweep (0xBA), adaptive blind scan
* (0xBB), error codes (0xBC), DiSEqC messaging (0x8D), streaming
* diagnostics (0xBD), and I2C hot-plug detection (0xBE).
*
* SDCC + fx2lib toolchain. Loaded into FX2 RAM for testing.
*/
#include <fx2regs.h>
#include <fx2macros.h>
#include <delay.h>
#include <autovector.h>
#include <setupdat.h>
#include <eputils.h>
#include <i2c.h>
#define SYNCDELAY SYNCDELAY4
/* I2C device addresses (7-bit)
*
* BCM4500 register access goes THROUGH the BCM3440 tuner's I2C gateway.
* The BCM3440 at 0x10 (wire 0x20/0x21) transparently forwards register
* reads/writes in the 0xA0+ range to the BCM4500 demodulator.
*
* The BCM4500's own I2C address (0x08, wire 0x10/0x11) only exposes a
* single status byte via simple reads -- it does NOT support register-
* addressed reads at that address. Stock firmware v2.06 disassembly
* confirms: FUN_CODE_0DDD, FUN_CODE_10F2, and all internal register
* access use device address 0x10 (BCM3440), never 0x08 directly. */
#define BCM4500_ADDR 0x10 /* BCM4500 via BCM3440 tuner gateway */
#define BCM4500_DIRECT 0x08 /* BCM4500 direct (status byte only, no reg addressing) */
#define EEPROM_ADDR 0x51 /* Calibration EEPROM; 16-bit addressed, AT24C-series */
/* BCM4500 indirect register protocol registers */
#define BCM_REG_PAGE 0xA6
#define BCM_REG_DATA 0xA7
#define BCM_REG_CMD 0xA8
/* BCM4500 PLL/config direct registers (written from EEPROM during boot) */
#define BCM_REG_CFG_MODE 0xA0 /* 0x01=enter PLL config, 0x00=exit */
#define BCM_REG_PLL_A9 0xA9
#define BCM_REG_PLL_AA 0xAA
#define BCM_REG_PLL_AB 0xAB
/* BCM4500 status registers */
#define BCM_REG_STATUS 0xA2
#define BCM_REG_LOCK 0xA4
#define BCM_LOCK_BIT 0x20 /* BCM4500 lock detect bit in register 0xA4 */
/* BCM commands */
#define BCM_CMD_READ 0x01
#define BCM_CMD_WRITE 0x03
/* vendor command IDs */
#define GET_8PSK_CONFIG 0x80
#define TUNE_8PSK 0x86
#define GET_SIGNAL_STRENGTH 0x87
#define BOOT_8PSK 0x89
#define START_INTERSIL 0x8A
#define SET_LNB_VOLTAGE 0x8B
#define SET_22KHZ_TONE 0x8C
#define SEND_DISEQC 0x8D
#define ARM_TRANSFER 0x85
#define GET_SIGNAL_LOCK 0x90
#define GET_FW_VERS 0x92
#define USE_EXTRA_VOLT 0x94
/* custom vendor commands */
#define SPECTRUM_SWEEP 0xB0
#define RAW_DEMOD_READ 0xB1
#define RAW_DEMOD_WRITE 0xB2
#define BLIND_SCAN 0xB3
#define SIGNAL_MONITOR 0xB7
#define TUNE_MONITOR 0xB8
#define MULTI_REG_READ 0xB9
#define PARAM_SWEEP 0xBA
#define ADAPTIVE_BLIND_SCAN 0xBB
#define GET_LAST_ERROR 0xBC
#define GET_STREAM_DIAG 0xBD
#define GET_HOTPLUG_STATUS 0xBE
#define GET_PLL_DIAG 0xBF
#define EEPROM_READ 0xC0
/* error codes (set by I2C helpers, read via 0xBC) */
#define ERR_OK 0x00
#define ERR_I2C_TIMEOUT 0x01
#define ERR_I2C_NAK 0x02
#define ERR_I2C_ARB_LOST 0x03
#define ERR_BCM_NOT_READY 0x04
#define ERR_BCM_TIMEOUT 0x05
#define ERR_TUNE_FAIL 0x06
#define ERR_EP0_TIMEOUT 0x07
#define ERR_GPIF_TIMEOUT 0x08
#define ERR_EP2_TIMEOUT 0x09
#define ERR_NOT_SUPPORTED 0x0A
#define ERR_DISEQC_LEN 0x0B
#define ERR_DISEQC_TIMER 0x0C
#define ERR_WDT_FIRED 0x0D
/* configuration status byte bits */
#define BM_STARTED 0x01
#define BM_FW_LOADED 0x02
#define BM_INTERSIL 0x04
#define BM_DVB_MODE 0x08
#define BM_22KHZ 0x10
#define BM_SEL18V 0x20
#define BM_DC_TUNED 0x40
#define BM_ARMED 0x80
/* GPIO pin definitions for v2.06 hardware */
#define PIN_PWR_EN 0x02 /* P0.1 -- power supply enable */
#define PIN_PWR_DIS 0x04 /* P0.2 -- power supply disable */
#define PIN_22KHZ 0x08 /* P0.3 */
#define PIN_LNB_VOLT 0x10 /* P0.4 */
#define PIN_BCM_RESET 0x20 /* P0.5 -- BCM4500 hardware reset (active LOW) */
#define PIN_DISEQC 0x80 /* P0.7 */
/* configuration status byte -- stored in ordinary variable */
static volatile BYTE config_status;
/* boot progress tracker for diagnostics (0=not started, 1-6=step, 0xFF=done) */
static volatile BYTE boot_stage;
/* ISR flag */
volatile __bit got_sud;
/* I2C scratch buffers in xdata (24 bytes: fits 20-byte EEPROM blocks) */
static __xdata BYTE i2c_buf[24];
static __xdata BYTE i2c_rd[8];
/* TUNE_MONITOR result buffer: filled by OUT phase, returned by IN phase */
static __xdata BYTE tm_result[10];
/* DiSEqC message buffer (3-6 bytes) for full message transmission */
static __xdata BYTE diseqc_msg[6];
/* PLL config diagnostic: captures EEPROM read results during boot.
* [0] = eeprom_check_present result (1=ok, 0=fail)
* [1] = first block count byte from EEPROM
* [2] = number of blocks successfully written
* [3] = last A9 value written (or 0xFF if none)
* [4] = last AA value written (or 0xFF if none)
* [5] = last AB count (or 0xFF if none)
* [6] = config_mode_exit result (1=ok, 0=fail)
* [7] = overall PLL result (1=ok, 0=fail) */
static __xdata BYTE pll_diag[8];
/* last error code for diagnostic reads via 0xBC */
static __xdata BYTE last_error;
/* Shared scratch buffer for vendor command case blocks (saves DSEG) */
static __xdata BYTE vc_diag[8];
/* I2C hot-plug detection: previous and current bus scan bitmaps (16 bytes each) */
static __xdata BYTE hp_prev[16]; /* last completed scan */
static __xdata BYTE hp_curr[16]; /* working scan buffer */
static __xdata WORD hp_changes; /* cumulative device change events */
static __xdata BYTE hp_added; /* devices added in last scan */
static __xdata BYTE hp_removed; /* devices removed in last scan */
static __xdata BYTE hp_scan_ok; /* 1 after first scan completes */
/* Streaming diagnostics counters */
static __xdata DWORD sd_poll_count; /* main-loop poll cycles while armed */
static __xdata WORD sd_overflow_count; /* EP2 FULL events detected */
static __xdata WORD sd_sync_loss; /* BCM4500 transport sync losses */
static __xdata BYTE sd_last_status; /* last BCM4500 status register */
static __xdata BYTE sd_last_lock; /* last BCM4500 lock register */
static __xdata BYTE sd_had_sync; /* had sync in previous poll */
/* Main loop timing: USB frame counter for periodic tasks */
static __xdata WORD hp_last_frame; /* frame counter at last I2C scan */
/* Software watchdog: Timer0 ISR decrements; on zero, LNB power is cut.
* volatile: shared between ISR (interrupt 1) and main loop. */
static volatile __xdata BYTE wdt_counter;
static volatile __xdata BYTE wdt_armed;
/*
* BCM4500 register initialization data extracted from stock v2.06 firmware.
* FUN_CODE_0ddd writes these 3 blocks to BCM4500 indirect registers (page 0)
* via the A6/A7/A8 control interface during BOOT_8PSK.
*
* Stock stores these at code:0x0B4F in 17-byte blocks: [length, data[0..15]].
* Only the data bytes (past the length prefix) go to A7.
*/
static const __code BYTE bcm_init_block0[] = {
0x0b, 0x17, 0x38, 0x9f, 0xd9, 0x80
};
static const __code BYTE bcm_init_block1[] = {
0x09, 0x39, 0x4f, 0x00, 0x65, 0xb7, 0x10
};
static const __code BYTE bcm_init_block2[] = {
0x0f, 0x0c, 0x09
};
#define BCM_INIT_BLOCK0_LEN 6
#define BCM_INIT_BLOCK1_LEN 7
#define BCM_INIT_BLOCK2_LEN 3
/* ---------- BCM4500 I2C helpers ---------- */
/*
* I2C timeout: ~5ms at 48MHz CPU clock (4 clocks/cycle, ~12 MIPS).
* At 400kHz I2C, one byte = 22.5us; 5ms gives >200x margin.
* The FX2 I2C controller has no hardware timeout -- if a slave holds
* SCL low (clock stretching), the master waits forever without this.
*/
#define I2C_TIMEOUT 6000
#define GPIF_TIMEOUT 60000 /* GPIF idle wait (~15ms at 4MHz tick) */
#define EP2_TIMEOUT 60000 /* EP2 drain wait */
static BOOL i2c_wait_done(void) {
WORD timeout = I2C_TIMEOUT;
while (!(I2CS & bmDONE)) {
if (--timeout == 0) {
last_error = ERR_I2C_TIMEOUT;
return FALSE;
}
}
return TRUE;
}
static BOOL i2c_wait_stop(void) {
WORD timeout = I2C_TIMEOUT;
while (I2CS & bmSTOP) {
if (--timeout == 0) {
last_error = ERR_I2C_TIMEOUT;
return FALSE;
}
}
return TRUE;
}
/*
* Wait for EP0 data phase to complete (host -> device transfer).
* Replaces bare `while (EP0CS & bmEPBUSY)` spin loops with timeout.
*/
static BOOL ep0_wait_data(void) {
WORD timeout = I2C_TIMEOUT;
while (EP0CS & bmEPBUSY) {
if (--timeout == 0) {
last_error = ERR_EP0_TIMEOUT;
return FALSE;
}
}
return TRUE;
}
/*
* Combined I2C write-read with repeated START (no STOP between
* write and read phases). The BCM3440 tuner gateway expects this
* protocol for register-addressed reads (confirmed by stock firmware
* disassembly of function 0x1556).
*
* Sequence: START -> addr+W -> reg -> RESTART -> addr+R -> data -> STOP
*/
static BOOL i2c_combined_read(BYTE addr, BYTE reg, BYTE len, BYTE *buf) {
BYTE i;
BYTE tmp;
/* START + write address */
I2CS |= bmSTART;
I2DAT = addr << 1;
if (!i2c_wait_done())
goto fail;
if (!(I2CS & bmACK)) {
last_error = ERR_I2C_NAK;
goto fail;
}
/* Write register address */
I2DAT = reg;
if (!i2c_wait_done())
goto fail;
if (!(I2CS & bmACK)) {
last_error = ERR_I2C_NAK;
goto fail;
}
/* REPEATED START + read address */
I2CS |= bmSTART;
I2DAT = (addr << 1) | 1;
if (!i2c_wait_done())
goto fail;
if (!(I2CS & bmACK)) {
last_error = ERR_I2C_NAK;
goto fail;
}
/* For single byte, set LASTRD before dummy read */
if (len == 1)
I2CS |= bmLASTRD;
/* Dummy read to trigger first clock burst */
tmp = I2DAT;
for (i = 0; i < len; i++) {
if (!i2c_wait_done())
goto fail;
if (i == len - 2)
I2CS |= bmLASTRD;
if (i == len - 1)
I2CS |= bmSTOP;
buf[i] = I2DAT;
}
i2c_wait_stop();
return TRUE;
fail:
I2CS |= bmSTOP;
i2c_wait_stop();
return FALSE;
}
/*
* I2C write with timeout -- writes addr+reg+data without using fx2lib,
* so we have full control over timeout behavior.
* Sends: START -> (addr<<1) -> reg -> data -> STOP
*/
static BOOL i2c_write_timeout(BYTE addr, BYTE reg, BYTE val) {
/* START + write address */
I2CS |= bmSTART;
I2DAT = addr << 1;
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
/* Register address */
I2DAT = reg;
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
/* Data byte */
I2DAT = val;
if (!i2c_wait_done()) goto fail;
/* STOP */
I2CS |= bmSTOP;
i2c_wait_stop();
return TRUE;
fail:
I2CS |= bmSTOP;
i2c_wait_stop();
return FALSE;
}
/*
* Multi-byte I2C write with timeout.
* Sends: START -> (addr<<1) -> reg -> data[0..len-1] -> STOP
*/
static BOOL i2c_write_multi_timeout(BYTE addr, BYTE reg, BYTE len,
__xdata BYTE *data) {
BYTE i;
I2CS |= bmSTART;
I2DAT = addr << 1;
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
I2DAT = reg;
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
for (i = 0; i < len; i++) {
I2DAT = data[i];
if (!i2c_wait_done()) goto fail;
}
I2CS |= bmSTOP;
i2c_wait_stop();
return TRUE;
fail:
I2CS |= bmSTOP;
i2c_wait_stop();
return FALSE;
}
/* ---------- EEPROM (calibration data) ---------- */
/*
* Check EEPROM presence by reading 1 byte from address 0x3FFF.
* Uses 16-bit addressing: START -> 0xA2 -> 0x3F -> 0xFF -> RESTART -> 0xA3 -> data -> STOP
* Result stored in i2c_buf[0].
*
* Zero parameters to minimize DSEG usage (8051 has only 14 bytes free).
* Hardcodes EEPROM_ADDR and address 0x3FFF -- matches FUN_CODE_1556.
*
* ASSUMES: AT24C128 or AT24C256 (16KB/32KB). After reading 0x3FFF, the
* internal pointer advances to 0x4000 where PLL data blocks begin.
* Smaller devices (AT24C64) NAK during the address phase.
*/
static BOOL eeprom_check_present(void) {
BYTE i;
I2CS |= bmSTART;
I2DAT = 0xA2; /* EEPROM_ADDR << 1 = write */
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
I2DAT = 0x3F; /* address high byte */
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
I2DAT = 0xFF; /* address low byte */
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
I2CS |= bmSTART;
I2DAT = 0xA3; /* (EEPROM_ADDR << 1) | 1 = read */
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
I2CS |= bmLASTRD; /* single byte read */
i = I2DAT; /* dummy read to trigger clock */
if (!i2c_wait_done()) goto fail;
I2CS |= bmSTOP;
i2c_buf[0] = I2DAT;
i2c_wait_stop();
return TRUE;
fail:
I2CS |= bmSTOP;
i2c_wait_stop();
return FALSE;
}
/*
* Read 20 bytes sequentially from EEPROM into i2c_buf[].
* START -> 0xA3 -> data[0..19] -> STOP
*
* The EEPROM auto-increments its address pointer after each byte.
* After eeprom_check_present() sets addr to 0x3FFF+1 = 0x4000,
* successive calls read consecutive 20-byte PLL config blocks.
*
* Zero parameters to minimize DSEG (hardcodes EEPROM_ADDR and len=20).
*/
static BOOL eeprom_read_block(void) {
BYTE i;
I2CS |= bmSTART;
I2DAT = 0xA3; /* (EEPROM_ADDR << 1) | 1 = read */
if (!i2c_wait_done()) goto fail;
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
i = I2DAT; /* dummy read (value discarded; i reused as loop counter) */
for (i = 0; i < 20; i++) {
if (!i2c_wait_done()) goto fail;
if (i == 18) I2CS |= bmLASTRD;
if (i == 19) I2CS |= bmSTOP;
i2c_buf[i] = I2DAT;
}
i2c_wait_stop();
return TRUE;
fail:
I2CS |= bmSTOP;
i2c_wait_stop();
return FALSE;
}
/* ---------- Software watchdog ---------- */
/*
* Kick the watchdog timer — resets the countdown to ~2 seconds.
* Must be called from the main loop at least once per period.
*/
static void wdt_kick(void) {
if (wdt_armed == 1) wdt_counter = 122;
}
/*
* Initialize Timer0 as a ~16ms periodic interrupt for the software
* watchdog. At 48MHz/12 = 4MHz timer clock with 16-bit overflow,
* period = 65536 / 4MHz = 16.384ms. 122 decrements × 16.384ms ≈ 2s.
*
* Mode 1 (16-bit) does NOT auto-reload. After overflow the counter
* wraps to 0x0000 and keeps counting — which is exactly the reload
* value we want, so no manual reload is needed in the ISR. If the
* start value is ever changed to non-zero, add a reload in the ISR.
*
* CKCON bit 3 (T0M) controls Timer0 clock; bit 5 (T2M) is Timer2.
* DiSEqC uses Timer2 — do not touch bit 3 from DiSEqC code.
*/
static void wdt_init(void) {
TMOD = (TMOD & 0xF0) | 0x01; /* Timer0 Mode 1 (16-bit) */
CKCON &= ~0x08; /* Timer0 clk = 48MHz/12 = 4MHz (bit 3 only) */
TH0 = 0x00; TL0 = 0x00; /* full-count: 0x0000 to 0xFFFF = 16.384ms */
wdt_armed = 1;
wdt_kick();
ET0 = 1; /* Enable Timer0 interrupt */
TR0 = 1; /* Start Timer0 */
}
/*
* Write one byte to a BCM4500 register via BCM3440 tuner gateway.
*/
static BOOL bcm_direct_write(BYTE reg, BYTE val) {
return i2c_write_timeout(BCM4500_ADDR, reg, val);
}
/*
* Read one byte from a BCM4500 register via BCM3440 tuner gateway,
* using combined write-read with repeated START.
*/
static BOOL bcm_direct_read(BYTE reg, BYTE *val) {
return i2c_combined_read(BCM4500_ADDR, reg, 1, val);
}
/*
* Write a value to a BCM4500 indirect register.
* Single multi-byte I2C write to 0xA6 with auto-increment:
* [0xA6] = page, [0xA7] = data, [0xA8] = 0x03 (write cmd)
*/
static BOOL bcm_indirect_write(BYTE reg, BYTE val) {
i2c_rd[0] = reg;
i2c_rd[1] = val;
i2c_rd[2] = BCM_CMD_WRITE;
return i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_PAGE, 3, i2c_rd);
}
/*
* Read a value from a BCM4500 indirect register.
* Protocol: single multi-byte I2C write to 0xA6 with auto-increment:
* [0xA6] = page, [0xA7] = 0x00, [0xA8] = 0x01 (read cmd)
* Then read the result from 0xA7.
*/
static BOOL bcm_indirect_read(BYTE reg, BYTE *val) {
/* page, placeholder data, read command -- written to A6,A7,A8 in one shot */
i2c_rd[0] = reg;
i2c_rd[1] = 0x00;
i2c_rd[2] = BCM_CMD_READ;
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_PAGE, 3, i2c_rd))
return FALSE;
delay(1);
return i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, val);
}
/*
* Write a multi-byte block to BCM4500 via indirect protocol.
* Page select, then N data bytes to 0xA7, then commit with 0x03.
*/
static BOOL bcm_indirect_write_block(BYTE page, __xdata BYTE *data, BYTE len) {
if (!bcm_direct_write(BCM_REG_PAGE, page))
return FALSE;
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, len, data))
return FALSE;
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE))
return FALSE;
return TRUE;
}
/*
* Poll BCM4500 for command completion via gateway register A8.
*
* The BCM3440 gateway's A8 register does not always clear bit 0 after
* the BCM4500 DSP processes an indirect write command, even though the
* write succeeds internally (confirmed by reading the direct BCM4500
* address 0x08 where A8 bit 0 does clear and bit 1 becomes set).
*
* The stock firmware's 0x20C5 polls the same gateway A8 via 0x2258
* and expects bit 0=0, bit 1=1. On our device, bit 1 never becomes 1
* through the gateway after writes, but the init data IS applied.
*
* Strategy: poll for early completion, but treat timeout as success
* with a settling delay rather than hard failure.
*/
static BOOL bcm_poll_ready(void) {
BYTE i, val;
for (i = 0; i < 10; i++) {
if (bcm_direct_read(BCM_REG_CMD, &val)) {
if (!(val & 0x01))
return TRUE;
} else {
return FALSE; /* I2C error */
}
delay(2);
}
delay(5); /* settling time for gateway A8 timeout path */
return TRUE;
}
/*
* Write one block of initialization data to BCM4500 indirect registers.
* Replicates FUN_CODE_0ddd's per-iteration I2C sequence from stock firmware:
* 0. Wait for BCM4500 readiness (stock calls 0x2000 before each block)
* 1. Write 0x00 to reg 0xA6 (page select = page 0)
* 2. Write data[0..len-1] to reg 0xA7 (multi-byte I2C write; the BCM3440
* gateway does NOT auto-increment for A7 — it uses FIFO mode)
* 3. Write 0x00 to reg 0xA7 (trailing zero -- stock firmware sends this)
* 4. Write 0x03 to reg 0xA8 (commit indirect write)
* 5. Wait for BCM4500 to finish processing
*/
static BOOL bcm_write_init_block(const __code BYTE *data, BYTE len) {
BYTE i;
/* Page select = 0 */
if (!bcm_direct_write(BCM_REG_PAGE, 0x00))
return FALSE;
/* Copy block data from code space to xdata scratch buffer */
for (i = 0; i < len; i++)
i2c_buf[i] = data[i];
/* Multi-byte write to A7 — the BCM3440 gateway buffers these in a FIFO
* (confirmed by stock firmware disassembly at 0x0E29: same multi-byte
* write pattern). The gateway does NOT auto-increment register address
* for writes targeting A7. */
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, len, i2c_buf))
return FALSE;
/* Trailing zero to 0xA7 (stock firmware does this as separate write) */
if (!bcm_direct_write(BCM_REG_DATA, 0x00))
return FALSE;
/* Commit: write command 0x03 to 0xA8 */
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE))
return FALSE;
/* Wait for BCM4500 to process the write */
return bcm_poll_ready();
}
/*
* Load PLL configuration from calibration EEPROM into BCM4500.
*
* Reverse-engineered from stock firmware FUN_CODE_10F2 (0x10F2-0x11FF).
* The AT24C256 EEPROM at I2C addr 0x51 stores BCM4500 DSP microcode at
* address 0x4000 (~8.5 KB in 536 blocks). A9/AA are address registers,
* AB+ is the data port with auto-increment. Without loading this
* firmware, the BCM4500 DSP core never starts (I2C responds but all
* registers read 0x00).
*
* EEPROM data format (20-byte blocks, sequential from addr 0x4000):
* buf[0] = count of data bytes (0 = end sentinel, max 16)
* buf[1] = register 0xA9 value (BCM4500 address high / page)
* buf[2] = register 0xAA value (BCM4500 address low / offset)
* buf[3] = (unused)
* buf[4..4+count-1] = data written to 0xAB with auto-increment
*
* Protocol:
* 1. Read 1 byte from EEPROM addr 0x3FFF (presence check, sets ptr to 0x4000)
* 2. Write 0x01 to BCM4500 reg 0xA0 (enter config mode)
* 3. Loop: read 20-byte block -> write [A9,AA,data] in single txn -> repeat until count=0
* 4. Write 0x00 to BCM4500 reg 0xA0 (exit config mode)
*/
static BOOL bcm4500_load_pll_config(void) {
BYTE count;
WORD blocks_left;
/* Initialize diagnostics to "not reached" defaults */
pll_diag[0] = 0; /* eeprom present */
pll_diag[1] = 0xFF; /* first count */
pll_diag[2] = 0; /* blocks written (saturates at 255) */
pll_diag[3] = 0xFF; /* last A9 */
pll_diag[4] = 0xFF; /* last AA */
pll_diag[5] = 0xFF; /* last AB count */
pll_diag[6] = 0xFF; /* config exit */
pll_diag[7] = 0; /* overall result */
/* Step 1: Check EEPROM presence (reads 1 byte from 0x3FFF, sets
* pointer to 0x4000 where BCM4500 firmware blocks begin) */
if (!eeprom_check_present())
return FALSE;
pll_diag[0] = 1;
/* Step 2: Enter BCM4500 config mode */
if (!bcm_direct_write(BCM_REG_CFG_MODE, 0x01))
return FALSE;
delay(5); /* let BCM4500 enter config mode */
/* Step 3: Load BCM4500 firmware from EEPROM.
*
* EEPROM 0x4000+ contains BCM4500 microcode in 20-byte blocks:
* [count, A9_val, AA_val, unused, data[count]]
* A9/AA serve as address registers; data goes to AB+ with auto-increment.
* Sentinel: count=0.
*
* Typical firmware: ~536 blocks, ~8.5 KB payload.
* Loop bounded to 820 (max possible in 16KB EEPROM half). */
for (blocks_left = 820; blocks_left > 0; blocks_left--) {
if (!eeprom_read_block())
goto fail_exit;
count = i2c_buf[0];
/* Capture first block's count for diagnostics */
if (pll_diag[2] == 0 && pll_diag[1] == 0xFF)
pll_diag[1] = count;
if (count == 0)
break; /* end sentinel -- firmware loaded */
/* Bounds check: 20-byte block has 4 header bytes, so max
* data count is 16. Reject corrupted EEPROM blocks. */
if (count > 16)
goto fail_exit;
/* Capture last-written values for diagnostics */
pll_diag[3] = i2c_buf[1]; /* A9 (address high) */
pll_diag[4] = i2c_buf[2]; /* AA (address low) */
pll_diag[5] = count; /* data byte count */
/* Single I2C transaction: A9 + AA + data with auto-increment.
* Stock firmware writes these together -- the BCM4500 requires the
* address (A9/AA) and data (AB+) in one transaction to latch.
* Shift data down by 1 to eliminate unused byte3 padding:
* Before: buf[1]=A9, buf[2]=AA, buf[3]=0x00, buf[4..]=data
* After: buf[1]=A9, buf[2]=AA, buf[3..]=data */
{ BYTE si;
for (si = 0; si < i2c_buf[0]; si++)
i2c_buf[3 + si] = i2c_buf[4 + si];
}
for (count = 5; count > 0; count--) {
if (i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_PLL_A9,
2 + i2c_buf[0], &i2c_buf[1]))
break;
}
if (count == 0)
goto fail_exit;
/* Saturating block counter for diagnostics (BYTE, caps at 255) */
if (pll_diag[2] < 255)
pll_diag[2]++;
}
if (blocks_left == 0)
goto fail_exit; /* no sentinel found -- EEPROM corrupt */
/* Step 4: Exit config mode */
if (!bcm_direct_write(BCM_REG_CFG_MODE, 0x00)) {
pll_diag[6] = 0;
return FALSE;
}
pll_diag[6] = 1;
delay(2);
pll_diag[7] = 1;
return TRUE;
fail_exit:
bcm_direct_write(BCM_REG_CFG_MODE, 0x00); /* best-effort cleanup */
return FALSE;
}
/*
* BCM4500 full boot sequence, reverse-engineered from stock firmware
* FUN_CODE_1D4F (reset/power) + FUN_CODE_10F2 (PLL config from EEPROM)
* + FUN_CODE_0ddd (register init blocks).
*
* GPIO sequence from disassembly:
* P3 |= 0xE0 -- P3.7, P3.6, P3.5 HIGH (control lines idle)
* P0 &= ~0x20 -- P0.5 LOW = assert BCM4500 hardware RESET
* I2C bus reset
* P0.1 set, P0.2 clr -- power supply enable
* delay(30) -- wait for power settle
* P0 |= 0x20 -- P0.5 HIGH = release BCM4500 from RESET
* Load PLL config from EEPROM into BCM4500 (A9/AA/AB registers)
* Write 3 register initialization blocks (A6/A7/A8 indirect protocol)
*/
static BOOL bcm4500_boot(void) {
boot_stage = 1; /* Stage 1: GPIO setup */
/* Ensure fx2lib I2C functions won't spin forever */
cancel_i2c_trans = FALSE;
/* P3.7, P3.6, P3.5 HIGH (idle state for control lines) */
IOD |= 0xE0;
/* Assert BCM4500 hardware RESET (P0.5 LOW) */
OEA |= PIN_BCM_RESET;
IOA &= ~PIN_BCM_RESET;
/* NOTE: Do NOT send I2CS bmSTOP here. Sending STOP when no transaction
* is active corrupts the FX2 I2C controller state, causing subsequent
* START+ACK detection to fail. The I2C bus will be in a clean state
* when we reach the probe step -- any prior transaction ended with STOP. */
/* Power on: P0.1 HIGH (enable), P0.2 LOW (disable off) */
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
boot_stage = 2; /* Stage 2: power settled, releasing reset */
/* Wait for power supply to settle (stock firmware uses 30 iterations) */
delay(30);
/* Release BCM4500 from RESET (P0.5 HIGH) */
IOA |= PIN_BCM_RESET;
/* Wait for BCM4500 internal POR and mask ROM boot to complete */
delay(50);
boot_stage = 3; /* Stage 3: I2C probe */
/* Verify tuner+demod are alive on I2C before attempting register init.
* Reads via BCM3440 gateway (0x10) -- if the tuner or demod is
* unpowered or stuck in reset, this combined read will NAK. */
if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0]))
return FALSE;
/* Stage 4: BCM4500 firmware load from EEPROM.
* vc_diag[0] bit 0 = skip this step (set via wIndex boot flags). */
boot_stage = 4;
if (!(vc_diag[0] & 0x01)) {
/* Load BCM4500 DSP firmware from calibration EEPROM (AT24C256 @ 0x51).
*
* The stock firmware's FUN_CODE_10F2 does this:
* reads ~8.5 KB of BCM4500 microcode from EEPROM 0x4000+ and writes
* to BCM4500 via registers A0 (config mode), A9/AA (address), and
* AB (data, multi-byte with auto-increment). ~536 blocks of 20 bytes.
*
* If EEPROM is absent, we continue — init blocks may still
* partially configure the chip for diagnostics. */
bcm4500_load_pll_config();
/* vc_diag[0] bit 1: extended DSP startup delay after download */
if (vc_diag[0] & 0x02)
delay(200);
}
/* Stages 5-7: register init blocks.
* vc_diag[0] bit 2 = skip this step. */
if (!(vc_diag[0] & 0x04)) {
boot_stage = 5;
if (!bcm_write_init_block(bcm_init_block0, BCM_INIT_BLOCK0_LEN))
return FALSE;
boot_stage = 6;
if (!bcm_write_init_block(bcm_init_block1, BCM_INIT_BLOCK1_LEN))
return FALSE;
boot_stage = 7;
if (!bcm_write_init_block(bcm_init_block2, BCM_INIT_BLOCK2_LEN))
return FALSE;
}
boot_stage = 0xFF; /* Success */
return TRUE;
}
/*
* BCM4500 shutdown -- reverse of boot.
* From stock firmware FUN_CODE_1D4F shutdown path at 0x1D93.
*/
static void bcm4500_shutdown(void) {
/* Power off: P0.1 LOW (enable off), P0.2 HIGH (disable) */
IOA = (IOA & ~PIN_PWR_EN) | PIN_PWR_DIS;
}
/* ---------- I2C hot-plug detection ---------- */
/*
* Scan all I2C addresses 0x01-0x77, storing result as 16-byte bitmap
* in hp_curr[]. Then compare with hp_prev[] to detect changes.
* Must NOT be called while streaming (I2C bus contention with GPIF).
*/
static void i2c_hotplug_scan(void) {
static __xdata BYTE hp_a, hp_byte, hp_bit, hp_diff;
/* Clear current scan buffer (hp_prev retains last SUCCESSFUL scan
* so aborted scans don't corrupt the comparison baseline) */
for (hp_a = 0; hp_a < 16; hp_a++)
hp_curr[hp_a] = 0;
/* Probe each 7-bit address using timeout-protected I2C ops */
for (hp_a = 1; hp_a < 0x78; hp_a++) {
I2CS |= bmSTART;
I2DAT = hp_a << 1; /* write direction */
if (!i2c_wait_done())
goto hp_abort; /* I2C hung — abandon scan */
if (I2CS & bmACK) {
hp_byte = hp_a >> 3;
hp_bit = hp_a & 0x07;
hp_curr[hp_byte] |= (1 << hp_bit);
}
I2CS |= bmSTOP;
if (!i2c_wait_stop())
goto hp_abort;
}
/* Compare with previous scan (only after first successful scan) */
if (hp_scan_ok) {
hp_added = 0;
hp_removed = 0;
for (hp_a = 0; hp_a < 16; hp_a++) {
hp_diff = hp_curr[hp_a] ^ hp_prev[hp_a];
if (hp_diff) {
/* Count new devices (in curr but not prev) */
hp_byte = hp_diff & hp_curr[hp_a];
while (hp_byte) {
hp_added++;
hp_byte &= (hp_byte - 1); /* Kernighan clear-lowest */
}
/* Count removed devices (in prev but not curr) */
hp_byte = hp_diff & hp_prev[hp_a];
while (hp_byte) {
hp_removed++;
hp_byte &= (hp_byte - 1);
}
/* Count per-device changes (not per-byte) */
hp_byte = hp_diff;
while (hp_byte) {
if (hp_changes < 0xFFFF) hp_changes++;
hp_byte &= (hp_byte - 1);
}
}
}
}
/* Snapshot successful scan as baseline for next comparison.
* Done after comparison so hp_prev always reflects the last
* fully-completed scan, not a partial abort. */
for (hp_a = 0; hp_a < 16; hp_a++)
hp_prev[hp_a] = hp_curr[hp_a];
hp_scan_ok = 1;
return;
hp_abort:
/* I2C timeout during scan — issue STOP and bail out.
* hp_curr is partial so we don't update hp_prev. */
I2CS |= bmSTOP;
last_error = ERR_I2C_TIMEOUT;
}
/* ---------- Streaming diagnostics poll ---------- */
/*
* Called from main loop while streaming (BM_ARMED set).
* Checks EP2 FIFO overflow and BCM4500 transport sync state.
*/
/* Rate-limit I2C reads: only poll BCM4500 every SD_I2C_INTERVAL polls.
* At ~100K main-loop iterations/sec, 4096 ≈ every 40ms — fast enough
* for meaningful sync-loss detection without saturating the I2C bus. */
#define SD_I2C_INTERVAL 4096
static void stream_diag_poll(void) {
static __xdata BYTE sd_rd[2]; /* dedicated I2C buffer for diag reads */
/* Saturate at max instead of wrapping (I1 fix) */
if (sd_poll_count < 0xFFFFFFFF)
sd_poll_count++;
/* EP2 FIFO full check is a pure SFR read — no I2C, always safe */
if (EP2CS & bmEPFULL) {
if (sd_overflow_count < 0xFFFF)
sd_overflow_count++;
}
/* Rate-limited BCM4500 I2C reads for sync tracking.
* Only attempt if BCM is booted and interval elapsed. */
/* (WORD) cast: SDCC optimization — avoids 32-bit AND. The 12-bit
* mask (0x0FFF) only needs the low 16 bits, so the cast is safe. */
if ((config_status & BM_FW_LOADED) &&
((WORD)sd_poll_count & (SD_I2C_INTERVAL - 1)) == 0) {
sd_rd[0] = 0;
sd_rd[1] = 0;
if (i2c_combined_read(BCM4500_ADDR, BCM_REG_STATUS, 1, &sd_rd[0]))
sd_last_status = sd_rd[0];
if (i2c_combined_read(BCM4500_ADDR, BCM_REG_LOCK, 1, &sd_rd[1]))
sd_last_lock = sd_rd[1];
/* Detect sync loss: had lock (bit 5) previously, lost it now */
if (sd_had_sync && !(sd_last_lock & BCM_LOCK_BIT)) {
if (sd_sync_loss < 0xFFFF)
sd_sync_loss++;
}
sd_had_sync = (sd_last_lock & BCM_LOCK_BIT) ? 1 : 0;
}
}
/* ---------- GPIF streaming ---------- */
static void gpif_start(void) {
if (config_status & BM_ARMED)
return;
config_status |= BM_ARMED;
/* IFCONFIG: internal 48MHz, GPIF master, async, clock output */
IFCONFIG = 0xEE;
SYNCDELAY;
/* EP2FIFOCFG: AUTOIN, ZEROLENIN, 8-bit */
EP2FIFOCFG = 0x0C;
SYNCDELAY;
/* FLOWSTATE: enable flow state + FS[3] */
FLOWSTATE |= 0x09;
/* Set transaction count large (effectively infinite) */
GPIFTCB3 = 0x80;
SYNCDELAY;
GPIFTCB2 = 0x00;
SYNCDELAY;
GPIFTCB1 = 0x00;
SYNCDELAY;
GPIFTCB0 = 0x00;
SYNCDELAY;
/* Assert P3.5 low (BCM4500 TS enable) briefly */
IOD &= ~0x20;
/* Wait for GPIF idle with timeout */
{
WORD gp_timeout = GPIF_TIMEOUT;
while (!(GPIFTRIG & 0x80)) {
if (--gp_timeout == 0) {
last_error = ERR_GPIF_TIMEOUT;
IOD |= 0x20;
config_status &= ~BM_ARMED;
return;
}
}
}
IOD |= 0x20;
/* Trigger continuous GPIF read into EP2 */
GPIFTRIG = 0x04;
/* P0.7 low = streaming indicator */
IOA &= ~0x80;
}
static void gpif_stop(void) {
if (!(config_status & BM_ARMED))
return;
/* P0.7 high = streaming stopped */
IOA |= 0x80;
/* Force-flush current FIFO buffer */
EP2FIFOBCH = 0xFF;
SYNCDELAY;
/* Wait for GPIF idle with timeout */
{
WORD gp_timeout = GPIF_TIMEOUT;
while (!(GPIFTRIG & 0x80)) {
if (--gp_timeout == 0) {
last_error = ERR_GPIF_TIMEOUT;
break; /* proceed with cleanup regardless */
}
}
}
/* Skip/discard partial EP2 packet */
OUTPKTEND = 0x82;
SYNCDELAY;
config_status &= ~BM_ARMED;
/* De-assert all BCM4500 control lines on P3 */
IOD |= 0xE0;
}
/* Forward declaration: diseqc_wait_ticks() is defined in the Manchester
* encoder section but used by diseqc_tone_burst() above it. */
static void diseqc_wait_ticks(BYTE count);
/* ---------- DiSEqC tone burst ---------- */
/*
* Send a tone burst (mini DiSEqC). This is the simpler variant.
* Tone burst A: unmodulated 22kHz for 12.5ms
* Tone burst B: modulated (not implemented yet)
*
* Uses Timer2 for timing as the stock firmware does.
*/
static void diseqc_tone_burst(BYTE sat_b) {
if (sat_b) { last_error = ERR_NOT_SUPPORTED; return; }
/* Configure Timer2 auto-reload.
* CKCON bit 5 (T2M) only — do not touch bit 3 (Timer0/watchdog). */
CKCON &= ~0x20;
T2CON = 0x04; /* auto-reload, running */
RCAP2H = 0xF8;
RCAP2L = 0x2F; /* reload = 63535 -> ~500us tick */
TL2 = 0xFF;
TH2 = 0xFF; /* force immediate overflow */
TF2 = 0;
/* Pre-burst settling: 15 ticks (~7.5ms) with carrier off */
IOA &= ~PIN_22KHZ;
diseqc_wait_ticks(15);
/* Burst: 25 ticks (~12.5ms) with carrier on */
IOA |= PIN_22KHZ;
diseqc_wait_ticks(25);
/* Carrier off */
IOA &= ~PIN_22KHZ;
/* Post-burst settling: 5 ticks (~2.5ms) */
diseqc_wait_ticks(5);
/* Stop Timer2 */
TR2 = 0;
}
/* ---------- DiSEqC Manchester encoder ---------- */
/*
* DiSEqC uses Manchester encoding over a 22 kHz carrier.
* The external oscillator generates 22 kHz continuously when P0.3 is HIGH;
* gating P0.3 LOW silences the carrier. Timer2 provides ~500.25 us ticks.
*
* Timing (EN 50494 / DiSEqC bus spec):
* Bit '1': 1 tick tone + 2 ticks silence = ~1.5 ms
* Bit '0': 2 ticks tone + 1 tick silence = ~1.5 ms
* Preamble: 30 ticks continuous tone (~15 ms)
* Start gap: 3 ticks silence (~1.5 ms)
* Inter-byte gap: 12 ticks silence (~6 ms)
* Post-message: 12 ticks silence (~6 ms)
*/
static void diseqc_wait_ticks(BYTE count) {
static __xdata BYTE dt_i;
WORD dt_timeout;
for (dt_i = 0; dt_i < count; dt_i++) {
dt_timeout = I2C_TIMEOUT;
while (!TF2) {
if (--dt_timeout == 0) {
last_error = ERR_DISEQC_TIMER;
return;
}
}
TF2 = 0;
}
}
static BYTE diseqc_parity(BYTE val) {
/* Compute odd parity: returns 1 if even number of set bits */
BYTE p = val;
p ^= (p >> 4);
p ^= (p >> 2);
p ^= (p >> 1);
return (~p) & 0x01;
}
static void diseqc_send_bit(BYTE bit) {
if (bit) {
/* '1': 1 tick tone ON, 2 ticks silence */
IOA |= PIN_22KHZ;
diseqc_wait_ticks(1);
IOA &= ~PIN_22KHZ;
diseqc_wait_ticks(2);
} else {
/* '0': 2 ticks tone ON, 1 tick silence */
IOA |= PIN_22KHZ;
diseqc_wait_ticks(2);
IOA &= ~PIN_22KHZ;
diseqc_wait_ticks(1);
}
}
static void diseqc_send_byte(BYTE val) {
static __xdata BYTE db_i, db_parity;
/* 8 data bits, MSB first */
for (db_i = 0; db_i < 8; db_i++) {
diseqc_send_bit((val >> (7 - db_i)) & 0x01);
}
/* Odd parity bit */
db_parity = diseqc_parity(val);
diseqc_send_bit(db_parity);
}
static void diseqc_send_message(BYTE len) {
static __xdata BYTE dm_i, dm_saved_tone;
if (len < 3 || len > 6) {
last_error = ERR_DISEQC_LEN;
return;
}
/* Save current 22 kHz tone state */
dm_saved_tone = IOA & PIN_22KHZ;
/* Configure Timer2 for ~500 us ticks (same as tone burst) */
CKCON &= ~0x20; /* T2M=0: Timer2 clk = 48MHz/12 = 4MHz */
T2CON = 0x04; /* auto-reload, running */
RCAP2H = 0xF8;
RCAP2L = 0x2F; /* reload = 63535 -> ~500 us tick */
TL2 = 0xFF;
TH2 = 0xFF; /* force immediate overflow */
TF2 = 0;
/* Pre-message gap: 6 ticks silence (~3 ms) */
IOA &= ~PIN_22KHZ;
diseqc_wait_ticks(6);
/* Preamble: 30 ticks continuous tone (~15 ms) */
IOA |= PIN_22KHZ;
diseqc_wait_ticks(30);
/* Start gap: 3 ticks silence (~1.5 ms) */
IOA &= ~PIN_22KHZ;
diseqc_wait_ticks(3);
/* Transmit bytes */
for (dm_i = 0; dm_i < len; dm_i++) {
diseqc_send_byte(diseqc_msg[dm_i]);
/* Inter-byte gap after each byte except the last */
if (dm_i < len - 1) {
IOA &= ~PIN_22KHZ;
diseqc_wait_ticks(12);
}
}
/* Post-message gap: 12 ticks silence (~6 ms) */
IOA &= ~PIN_22KHZ;
diseqc_wait_ticks(12);
/* Stop Timer2 */
TR2 = 0;
/* Restore 22 kHz tone state */
if (dm_saved_tone)
IOA |= PIN_22KHZ;
else
IOA &= ~PIN_22KHZ;
}
/* Forward declaration: do_tune() is defined after the sweep functions
* but called from do_param_sweep(). */
static void do_tune(void);
/* ---------- Parameterized sweep (0xBA) ---------- */
/*
* Like SPECTRUM_SWEEP (0xB0) but host controls SR, modulation, and FEC.
* 16-byte EP0 payload:
* [0..3] start_freq_khz (u32 LE)
* [4..7] stop_freq_khz (u32 LE)
* [8..9] step_khz (u16 LE)
* [10..13] symbol_rate_sps (u32 LE)
* [14] mod_index
* [15] fec_index
*
* At each step: tune (program SR/mod/FEC via do_tune), dwell for AGC
* settling, read SNR registers, output u16 LE power to EP2.
*/
static void do_param_sweep(void) {
static __xdata DWORD ps_start, ps_stop, ps_cur, ps_sr;
static __xdata WORD ps_step, ps_buf_idx;
static __xdata BYTE ps_snr_lo, ps_snr_hi;
static __xdata BYTE ps_mod, ps_fec;
ps_start = (DWORD)EP0BUF[0] |
((DWORD)EP0BUF[1] << 8) |
((DWORD)EP0BUF[2] << 16) |
((DWORD)EP0BUF[3] << 24);
ps_stop = (DWORD)EP0BUF[4] |
((DWORD)EP0BUF[5] << 8) |
((DWORD)EP0BUF[6] << 16) |
((DWORD)EP0BUF[7] << 24);
ps_step = (WORD)EP0BUF[8] | ((WORD)EP0BUF[9] << 8);
ps_sr = (DWORD)EP0BUF[10] |
((DWORD)EP0BUF[11] << 8) |
((DWORD)EP0BUF[12] << 16) |
((DWORD)EP0BUF[13] << 24);
ps_mod = EP0BUF[14];
ps_fec = EP0BUF[15];
if (ps_step == 0)
ps_step = 1000;
ps_buf_idx = 0;
ps_cur = ps_start;
while (ps_cur <= ps_stop) {
wdt_kick(); /* sweep is progressing, not hung */
/*
* Set up a tune payload in EP0BUF for do_tune():
* [0..3] = symbol_rate (LE), [4..7] = freq (LE), [8] = mod, [9] = fec
*/
EP0BUF[0] = (BYTE)(ps_sr);
EP0BUF[1] = (BYTE)(ps_sr >> 8);
EP0BUF[2] = (BYTE)(ps_sr >> 16);
EP0BUF[3] = (BYTE)(ps_sr >> 24);
EP0BUF[4] = (BYTE)(ps_cur);
EP0BUF[5] = (BYTE)(ps_cur >> 8);
EP0BUF[6] = (BYTE)(ps_cur >> 16);
EP0BUF[7] = (BYTE)(ps_cur >> 24);
EP0BUF[8] = ps_mod;
EP0BUF[9] = ps_fec;
do_tune();
/* Dwell for AGC settling */
delay(10);
/* Read signal strength via indirect register */
ps_snr_lo = 0;
ps_snr_hi = 0;
bcm_indirect_read(0x00, &ps_snr_lo);
bcm_indirect_read(0x01, &ps_snr_hi);
/* Store u16 LE into EP2 FIFO buffer */
if (ps_buf_idx < 1024 - 1) {
EP2FIFOBUF[ps_buf_idx++] = ps_snr_lo;
EP2FIFOBUF[ps_buf_idx++] = ps_snr_hi;
}
/* Commit chunk when buffer is half full */
if (ps_buf_idx >= 512) {
EP2BCH = MSB(ps_buf_idx);
SYNCDELAY;
EP2BCL = LSB(ps_buf_idx);
SYNCDELAY;
ps_buf_idx = 0;
{
WORD ep2_to = EP2_TIMEOUT;
while (EP2CS & bmEPFULL) {
if (--ep2_to == 0) {
last_error = ERR_EP2_TIMEOUT;
return;
}
}
}
}
ps_cur += ps_step;
}
/* Commit remaining data */
if (ps_buf_idx > 0) {
EP2BCH = MSB(ps_buf_idx);
SYNCDELAY;
EP2BCL = LSB(ps_buf_idx);
SYNCDELAY;
}
}
/* ---------- Adaptive blind scan (0xBB) ---------- */
/*
* Enhanced blind scan with quick AGC pre-check.
* EP0 payload (18 bytes):
* [0..3] freq_khz (u32 LE)
* [4..7] sr_min (u32 LE, sps)
* [8..11] sr_max (u32 LE, sps)
* [12..15] sr_step (u32 LE, sps)
* [16..17] quick_dwell_ms (u16 LE, 0=disabled)
*
* When quick_dwell_ms > 0: at each SR step, first do a quick AGC read.
* If AGC indicates no energy (below threshold), skip the full 100ms dwell.
* Cuts survey time ~80% on empty frequencies.
*/
static BOOL do_adaptive_blind_scan(void) {
static __xdata DWORD abs_freq, abs_sr_min, abs_sr_max, abs_sr_step, abs_sr_cur;
static __xdata WORD abs_quick_dwell, abs_agc_val;
static __xdata BYTE abs_lock_val, abs_agc_lo, abs_agc_hi;
abs_freq = (DWORD)EP0BUF[0] |
((DWORD)EP0BUF[1] << 8) |
((DWORD)EP0BUF[2] << 16) |
((DWORD)EP0BUF[3] << 24);
abs_sr_min = (DWORD)EP0BUF[4] |
((DWORD)EP0BUF[5] << 8) |
((DWORD)EP0BUF[6] << 16) |
((DWORD)EP0BUF[7] << 24);
abs_sr_max = (DWORD)EP0BUF[8] |
((DWORD)EP0BUF[9] << 8) |
((DWORD)EP0BUF[10] << 16) |
((DWORD)EP0BUF[11] << 24);
abs_sr_step = (DWORD)EP0BUF[12] |
((DWORD)EP0BUF[13] << 8) |
((DWORD)EP0BUF[14] << 16) |
((DWORD)EP0BUF[15] << 24);
abs_quick_dwell = (WORD)EP0BUF[16] | ((WORD)EP0BUF[17] << 8);
if (abs_sr_step == 0)
abs_sr_step = 1000000;
abs_sr_cur = abs_sr_min;
while (abs_sr_cur <= abs_sr_max) {
wdt_kick(); /* scan is progressing, not hung */
/* Program SR and frequency into BCM4500 */
i2c_buf[0] = (BYTE)(abs_sr_cur >> 24);
i2c_buf[1] = (BYTE)(abs_sr_cur >> 16);
i2c_buf[2] = (BYTE)(abs_sr_cur >> 8);
i2c_buf[3] = (BYTE)(abs_sr_cur);
if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
abs_sr_cur += abs_sr_step;
continue;
}
i2c_buf[0] = (BYTE)(abs_freq >> 24);
i2c_buf[1] = (BYTE)(abs_freq >> 16);
i2c_buf[2] = (BYTE)(abs_freq >> 8);
i2c_buf[3] = (BYTE)(abs_freq);
if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
abs_sr_cur += abs_sr_step;
continue;
}
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE)) {
abs_sr_cur += abs_sr_step;
continue;
}
/* Quick AGC pre-check if enabled */
if (abs_quick_dwell > 0) {
delay((BYTE)(abs_quick_dwell > 255 ? 255 : abs_quick_dwell));
/* Read AGC registers for energy detection */
abs_agc_lo = 0;
abs_agc_hi = 0;
bcm_indirect_read(0x02, &abs_agc_lo);
bcm_indirect_read(0x03, &abs_agc_hi);
abs_agc_val = ((WORD)abs_agc_hi << 8) | abs_agc_lo;
/* High AGC = weak signal. Threshold: ~60000 means no energy.
* Skip full dwell if no energy detected. */
if (abs_agc_val > 60000) {
abs_sr_cur += abs_sr_step;
continue;
}
}
/* Full acquisition dwell */
delay(100);
/* Check lock */
abs_lock_val = 0;
bcm_direct_read(BCM_REG_LOCK, &abs_lock_val);
if (abs_lock_val & BCM_LOCK_BIT) {
EP0BUF[0] = (BYTE)(abs_freq);
EP0BUF[1] = (BYTE)(abs_freq >> 8);
EP0BUF[2] = (BYTE)(abs_freq >> 16);
EP0BUF[3] = (BYTE)(abs_freq >> 24);
EP0BUF[4] = (BYTE)(abs_sr_cur);
EP0BUF[5] = (BYTE)(abs_sr_cur >> 8);
EP0BUF[6] = (BYTE)(abs_sr_cur >> 16);
EP0BUF[7] = (BYTE)(abs_sr_cur >> 24);
EP0BCH = 0;
EP0BCL = 8;
return TRUE;
}
abs_sr_cur += abs_sr_step;
}
/* No lock found */
EP0BUF[0] = 0x00;
EP0BCH = 0;
EP0BCL = 1;
return FALSE;
}
/* ---------- Spectrum sweep (0xB0) ---------- */
/*
* Step through frequencies from start to stop, reading signal strength
* at each step. Results are packed as u16 LE power values into EP2 bulk.
*
* The host sends a 10-byte payload via EP0:
* [0..3] start_freq (u32 LE, kHz)
* [4..7] stop_freq (u32 LE, kHz)
* [8..9] step_khz (u16 LE)
*
* We tune to each frequency with a fixed symbol rate (e.g. 20000 sps, DVB-S
* QPSK auto-FEC), read the SNR register, and pack u16 results into EP2.
*
* The sweep uses a simple approach: program freq via BCM4500 indirect write
* at each step, wait briefly, and read the signal energy register.
*/
static void do_spectrum_sweep(void) {
static __xdata DWORD start_freq, stop_freq, cur_freq;
static __xdata WORD step_khz, ss_buf_idx;
static __xdata BYTE ss_snr_lo, ss_snr_hi;
/* Parse the 10-byte EP0 payload */
start_freq = (DWORD)EP0BUF[0] |
((DWORD)EP0BUF[1] << 8) |
((DWORD)EP0BUF[2] << 16) |
((DWORD)EP0BUF[3] << 24);
stop_freq = (DWORD)EP0BUF[4] |
((DWORD)EP0BUF[5] << 8) |
((DWORD)EP0BUF[6] << 16) |
((DWORD)EP0BUF[7] << 24);
step_khz = (WORD)EP0BUF[8] | ((WORD)EP0BUF[9] << 8);
if (step_khz == 0)
step_khz = 1000;
ss_buf_idx = 0;
cur_freq = start_freq;
while (cur_freq <= stop_freq) {
wdt_kick(); /* sweep is progressing, not hung */
/*
* Program frequency into BCM4500 via indirect write.
* The BCM4500 expects big-endian frequency bytes at page 0.
* We write 4 freq bytes (BE) to the data register.
*/
i2c_buf[0] = (BYTE)(cur_freq >> 24);
i2c_buf[1] = (BYTE)(cur_freq >> 16);
i2c_buf[2] = (BYTE)(cur_freq >> 8);
i2c_buf[3] = (BYTE)(cur_freq);
if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
cur_freq += step_khz;
continue;
}
/* Wait for demod to settle */
delay(10);
/* Read signal strength via indirect register */
ss_snr_lo = 0;
ss_snr_hi = 0;
bcm_indirect_read(0x00, &ss_snr_lo);
bcm_indirect_read(0x01, &ss_snr_hi);
/* Store u16 LE into EP2 FIFO buffer */
if (ss_buf_idx < 1024 - 1) {
EP2FIFOBUF[ss_buf_idx++] = ss_snr_lo;
EP2FIFOBUF[ss_buf_idx++] = ss_snr_hi;
}
/* If buffer is nearly full, commit this chunk */
if (ss_buf_idx >= 512) {
EP2BCH = MSB(ss_buf_idx);
SYNCDELAY;
EP2BCL = LSB(ss_buf_idx);
SYNCDELAY;
ss_buf_idx = 0;
/* Wait for the buffer to be taken by host */
{
WORD ep2_to = EP2_TIMEOUT;
while (EP2CS & bmEPFULL) {
if (--ep2_to == 0) {
last_error = ERR_EP2_TIMEOUT;
return;
}
}
}
}
cur_freq += step_khz;
}
/* Commit any remaining data */
if (ss_buf_idx > 0) {
EP2BCH = MSB(ss_buf_idx);
SYNCDELAY;
EP2BCL = LSB(ss_buf_idx);
SYNCDELAY;
}
}
/* ---------- Blind scan (0xB3) ---------- */
/*
* Try symbol rates from sr_min to sr_max in sr_step increments
* at a given frequency, looking for signal lock.
*
* EP0 payload (16 bytes):
* [0..3] freq_khz (u32 LE)
* [4..7] sr_min (u32 LE, sps)
* [8..11] sr_max (u32 LE, sps)
* [12..15] sr_step (u32 LE, sps)
*
* Returns via EP0: 8 bytes on lock [freq_khz(4) + sr_locked(4)]
* or 1 byte 0x00 if no lock found.
*/
static BOOL do_blind_scan(void) {
static __xdata DWORD freq_khz, sr_min, sr_max, sr_step, sr_cur;
static __xdata BYTE bs_lock_val;
freq_khz = (DWORD)EP0BUF[0] |
((DWORD)EP0BUF[1] << 8) |
((DWORD)EP0BUF[2] << 16) |
((DWORD)EP0BUF[3] << 24);
sr_min = (DWORD)EP0BUF[4] |
((DWORD)EP0BUF[5] << 8) |
((DWORD)EP0BUF[6] << 16) |
((DWORD)EP0BUF[7] << 24);
sr_max = (DWORD)EP0BUF[8] |
((DWORD)EP0BUF[9] << 8) |
((DWORD)EP0BUF[10] << 16) |
((DWORD)EP0BUF[11] << 24);
sr_step = (DWORD)EP0BUF[12] |
((DWORD)EP0BUF[13] << 8) |
((DWORD)EP0BUF[14] << 16) |
((DWORD)EP0BUF[15] << 24);
if (sr_step == 0)
sr_step = 1000000;
sr_cur = sr_min;
while (sr_cur <= sr_max) {
wdt_kick(); /* scan is progressing, not hung */
/*
* Program frequency (BE) and symbol rate (BE) into BCM4500.
* We write both in a single block: 4 bytes SR + 4 bytes freq.
*/
i2c_buf[0] = (BYTE)(sr_cur >> 24);
i2c_buf[1] = (BYTE)(sr_cur >> 16);
i2c_buf[2] = (BYTE)(sr_cur >> 8);
i2c_buf[3] = (BYTE)(sr_cur);
if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
sr_cur += sr_step;
continue;
}
i2c_buf[0] = (BYTE)(freq_khz >> 24);
i2c_buf[1] = (BYTE)(freq_khz >> 16);
i2c_buf[2] = (BYTE)(freq_khz >> 8);
i2c_buf[3] = (BYTE)(freq_khz);
if (!bcm_indirect_write_block(0x00, i2c_buf, 4)) {
sr_cur += sr_step;
continue;
}
/* Issue tune command */
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE)) {
sr_cur += sr_step;
continue;
}
/* Wait for acquisition attempt */
delay(100);
/* Check lock */
bs_lock_val = 0;
bcm_direct_read(BCM_REG_LOCK, &bs_lock_val);
if (bs_lock_val & BCM_LOCK_BIT) {
/* Locked -- report back via EP0 */
EP0BUF[0] = (BYTE)(freq_khz);
EP0BUF[1] = (BYTE)(freq_khz >> 8);
EP0BUF[2] = (BYTE)(freq_khz >> 16);
EP0BUF[3] = (BYTE)(freq_khz >> 24);
EP0BUF[4] = (BYTE)(sr_cur);
EP0BUF[5] = (BYTE)(sr_cur >> 8);
EP0BUF[6] = (BYTE)(sr_cur >> 16);
EP0BUF[7] = (BYTE)(sr_cur >> 24);
EP0BCH = 0;
EP0BCL = 8;
return TRUE;
}
sr_cur += sr_step;
}
/* No lock found */
EP0BUF[0] = 0x00;
EP0BCH = 0;
EP0BCL = 1;
return FALSE;
}
/* ---------- TUNE_8PSK (0x86) handler ---------- */
/*
* Parse 10-byte EP0 payload, program BCM4500 via I2C indirect registers.
* Follows the stock firmware's protocol:
* EP0BUF[0..3] = symbol_rate (LE u32, sps)
* EP0BUF[4..7] = freq_khz (LE u32, kHz)
* EP0BUF[8] = modulation index (0-9)
* EP0BUF[9] = FEC index
*/
static void do_tune(void) {
static __xdata BYTE tune_i;
static __xdata BYTE tune_data[13]; /* 12 data + 1 scratch for reg addr */
if (!(config_status & BM_STARTED)) {
last_error = ERR_BCM_NOT_READY;
return;
}
/*
* Byte-reverse symbol rate (LE->BE) into tune_data[0..3]
* and frequency (LE->BE) into tune_data[4..7]
*/
for (tune_i = 0; tune_i < 4; tune_i++) {
tune_data[tune_i] = EP0BUF[3 - tune_i]; /* SR BE */
tune_data[4 + tune_i] = EP0BUF[7 - tune_i]; /* Freq BE */
}
/* Modulation type and FEC rate */
tune_data[8] = EP0BUF[8];
tune_data[9] = EP0BUF[9];
/* Demod mode: default standard (0x10) */
tune_data[10] = 0x10;
/* Turbo flag: 0x00 for DVB-S, 0x01 for turbo modes */
tune_data[11] = 0x00;
if (EP0BUF[8] >= 1 && EP0BUF[8] <= 3)
tune_data[11] = 0x01;
/* Set demod mode for DCII variants */
switch (EP0BUF[8]) {
case 5: tune_data[10] = 0x12; break; /* DCII I-stream */
case 6: tune_data[10] = 0x16; break; /* DCII Q-stream */
case 7: tune_data[10] = 0x11; break; /* DCII Offset QPSK */
default: break;
}
/* Poll BCM4500 for readiness */
if (!bcm_poll_ready())
return;
/* Write page 0 */
if (!bcm_direct_write(BCM_REG_PAGE, 0x00))
return;
/* Write all 12 configuration bytes to BCM4500 data register (0xA7).
* Multi-byte I2C write — BCM3440 gateway uses FIFO mode for A7. */
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, 12, tune_data))
return;
/* Execute indirect write */
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE))
return;
/* Wait for command completion */
bcm_poll_ready();
}
/* ---------- Vendor command handler ---------- */
BOOL handle_vendorcommand(BYTE cmd) {
WORD wval;
BYTE val;
wval = SETUP_VALUE();
switch (cmd) {
/* 0x80: GET_8PSK_CONFIG -- return config status byte */
case GET_8PSK_CONFIG:
EP0BUF[0] = config_status;
EP0BCH = 0;
EP0BCL = 1;
return TRUE;
/* 0x85: ARM_TRANSFER -- start/stop MPEG-2 streaming */
case ARM_TRANSFER:
if (wval)
gpif_start();
else
gpif_stop();
return TRUE;
/* 0x86: TUNE_8PSK -- 10-byte tuning payload */
case TUNE_8PSK:
/* EP0 data phase: wait for 10 bytes from host */
EP0BCL = 0;
SYNCDELAY;
if (!ep0_wait_data()) return TRUE;
if (EP0BCL < 10) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_tune();
return TRUE;
/* 0x87: GET_SIGNAL_STRENGTH -- read 6 bytes from BCM4500 */
case GET_SIGNAL_STRENGTH:
if (!(config_status & BM_STARTED)) {
EP0BUF[0] = 0; EP0BUF[1] = 0;
EP0BUF[2] = 0; EP0BUF[3] = 0;
EP0BUF[4] = 0; EP0BUF[5] = 0;
EP0BCH = 0;
EP0BCL = 6;
return TRUE;
}
/* Zero-fill before reads so I2C failures return zeros, not stale data */
EP0BUF[0] = 0; EP0BUF[1] = 0; EP0BUF[2] = 0;
EP0BUF[3] = 0; EP0BUF[4] = 0; EP0BUF[5] = 0;
/* Read signal quality via indirect registers */
bcm_indirect_read(0x00, &EP0BUF[0]);
bcm_indirect_read(0x01, &EP0BUF[1]);
bcm_indirect_read(0x02, &EP0BUF[2]);
bcm_indirect_read(0x03, &EP0BUF[3]);
bcm_indirect_read(0x04, &EP0BUF[4]);
bcm_indirect_read(0x05, &EP0BUF[5]);
EP0BCH = 0;
EP0BCL = 6;
return TRUE;
/* 0x89: BOOT_8PSK -- initialize BCM4500 demodulator
* wValue=0: shutdown
* wValue=1: full boot (reset + power + register init)
* wValue=0x80: debug -- return boot_stage only (no-op)
* wValue=0x81: debug -- GPIO setup + delays only
* wValue=0x82: debug -- GPIO + I2C probe only
* wValue=0x83: debug -- GPIO + I2C probe + 1 init block */
case BOOT_8PSK:
if (wval == 0x80) {
/* Debug: no-op, just return current state */
} else if (wval == 0x81) {
/* Debug: GPIO only, no I2C */
boot_stage = 1;
IOD |= 0xE0;
OEA |= PIN_BCM_RESET;
IOA &= ~PIN_BCM_RESET;
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
boot_stage = 2;
delay(30);
IOA |= PIN_BCM_RESET;
delay(50);
boot_stage = 0xA1; /* success marker for debug 0x81 */
} else if (wval == 0x82) {
/* Debug: GPIO + probe read (same as 0x85 now -- bmSTOP removed) */
boot_stage = 1;
IOD |= 0xE0;
OEA |= PIN_BCM_RESET;
IOA &= ~PIN_BCM_RESET;
/* bmSTOP removed -- corrupts FX2 I2C controller */
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
boot_stage = 2;
delay(30);
IOA |= PIN_BCM_RESET;
delay(50);
boot_stage = 3;
if (bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) {
EP0BUF[2] = i2c_rd[0];
boot_stage = 0xA2;
} else {
EP0BUF[2] = 0xEE;
boot_stage = 0xE3; /* failed at I2C probe */
}
} else if (wval == 0x83) {
/* Debug: GPIO + probe + first init block */
boot_stage = 1;
cancel_i2c_trans = FALSE;
IOD |= 0xE0;
OEA |= PIN_BCM_RESET;
IOA &= ~PIN_BCM_RESET;
/* bmSTOP removed -- corrupts FX2 I2C controller */
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
boot_stage = 2;
delay(30);
IOA |= PIN_BCM_RESET;
delay(50);
boot_stage = 3;
if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) {
boot_stage = 0xE3;
} else {
boot_stage = 4;
if (bcm_write_init_block(bcm_init_block0, BCM_INIT_BLOCK0_LEN))
boot_stage = 0xA3;
else
boot_stage = 0xE4;
}
} else if (wval == 0x84) {
/* Debug: I2C-only probe, no GPIO (assumes chip already powered) */
boot_stage = 3;
if (bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) {
EP0BUF[2] = i2c_rd[0];
boot_stage = 0xA4;
} else {
EP0BUF[2] = 0xEE;
boot_stage = 0xE3;
}
} else if (wval == 0x85) {
/* Debug: Same as 0x82 but WITHOUT I2C bus reset (no bmSTOP) */
boot_stage = 1;
IOD |= 0xE0;
OEA |= PIN_BCM_RESET;
IOA &= ~PIN_BCM_RESET;
/* NOTE: no I2CS bmSTOP here, unlike 0x82 */
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
boot_stage = 2;
delay(30);
IOA |= PIN_BCM_RESET;
delay(50);
boot_stage = 3;
if (bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) {
EP0BUF[2] = i2c_rd[0];
boot_stage = 0xA5;
} else {
EP0BUF[2] = 0xEE;
boot_stage = 0xE3;
}
} else if (wval) {
/* wIndex serves as boot flags for diagnostic A/B testing:
* bit 0 (0x01): skip EEPROM firmware download
* bit 1 (0x02): add 200ms DSP startup delay after download
* bit 2 (0x04): skip register init blocks
* Normal boot: wIndex=0. */
vc_diag[0] = (BYTE)SETUP_INDEX();
if (bcm4500_boot()) {
config_status |= (BM_STARTED | BM_FW_LOADED);
} else {
bcm4500_shutdown();
config_status &= ~(BM_STARTED | BM_FW_LOADED);
}
vc_diag[0] = 0;
} else {
bcm4500_shutdown();
config_status &= ~(BM_STARTED | BM_FW_LOADED);
}
EP0BUF[0] = config_status;
EP0BUF[1] = boot_stage;
EP0BCH = 0;
EP0BCL = 3;
return TRUE;
/* 0x8A: START_INTERSIL -- enable LNB power supply */
case START_INTERSIL:
if (wval) {
/* Enable LNB power */
OEA |= (PIN_22KHZ | PIN_LNB_VOLT | PIN_DISEQC);
config_status |= BM_INTERSIL;
} else {
config_status &= ~BM_INTERSIL;
}
EP0BUF[0] = config_status;
EP0BCH = 0;
EP0BCL = 1;
return TRUE;
/* 0x8B: SET_LNB_VOLTAGE -- 13V (wval=0) or 18V (wval=1) */
case SET_LNB_VOLTAGE:
if (wval) {
IOA |= PIN_LNB_VOLT;
config_status |= BM_SEL18V;
} else {
IOA &= ~PIN_LNB_VOLT;
config_status &= ~BM_SEL18V;
}
return TRUE;
/* 0x8C: SET_22KHZ_TONE -- on (wval=1) or off (wval=0) */
case SET_22KHZ_TONE:
if (wval) {
IOA |= PIN_22KHZ;
config_status |= BM_22KHZ;
} else {
IOA &= ~PIN_22KHZ;
config_status &= ~BM_22KHZ;
}
return TRUE;
/* 0x8D: SEND_DISEQC -- tone burst or DiSEqC message */
case SEND_DISEQC: {
WORD wlen;
wlen = SETUP_LENGTH();
if (wlen == 0) {
/* Tone burst: A if wval==0, B if wval!=0 */
diseqc_tone_burst((BYTE)wval);
} else if (wlen >= 3 && wlen <= 6) {
/* Full DiSEqC message: reject if streaming */
if (config_status & BM_ARMED) {
last_error = ERR_BCM_NOT_READY;
return TRUE;
}
/* EP0 data phase: receive message bytes */
EP0BCL = 0;
SYNCDELAY;
if (!ep0_wait_data()) return TRUE;
if (EP0BCL < (BYTE)wlen) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
/* Copy message from EP0BUF to diseqc_msg buffer */
{
BYTE di;
for (di = 0; di < (BYTE)wlen; di++)
diseqc_msg[di] = EP0BUF[di];
}
diseqc_send_message((BYTE)wlen);
}
return TRUE;
}
/* 0x90: GET_SIGNAL_LOCK -- read BCM4500 lock register */
case GET_SIGNAL_LOCK:
val = 0;
if (config_status & BM_STARTED) {
bcm_direct_read(BCM_REG_LOCK, &val);
}
EP0BUF[0] = val;
EP0BCH = 0;
EP0BCL = 1;
return TRUE;
/* 0x92: GET_FW_VERS -- return firmware version and build date */
case GET_FW_VERS:
EP0BUF[0] = 0x00; /* patch -> version 3.05.0 */
EP0BUF[1] = 0x05; /* minor */
EP0BUF[2] = 0x03; /* major */
EP0BUF[3] = 0x10; /* day = 16 */
EP0BUF[4] = 0x02; /* month = 2 */
EP0BUF[5] = 0x1A; /* year - 2000 = 26 */
EP0BCH = 0;
EP0BCL = 6;
return TRUE;
/* 0x94: USE_EXTRA_VOLT -- enable +1V LNB boost */
case USE_EXTRA_VOLT:
/* This would write to the LNB regulator; no-op for now */
return TRUE;
/* --- Custom commands --- */
/* 0xB0: SPECTRUM_SWEEP */
case SPECTRUM_SWEEP:
/* EP0 data phase: wait for 10 bytes from host */
EP0BCL = 0;
SYNCDELAY;
if (!ep0_wait_data()) return TRUE;
if (EP0BCL < 10) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_spectrum_sweep();
return TRUE;
/* 0xB1: RAW_DEMOD_READ -- read BCM4500 register
* wIndex=0: indirect read (page = wValue)
* wIndex=1: direct read (register = wValue) */
case RAW_DEMOD_READ: {
WORD ridx;
ridx = SETUP_INDEX();
val = 0;
if (ridx == 1)
bcm_direct_read((BYTE)wval, &val);
else
bcm_indirect_read((BYTE)wval, &val);
EP0BUF[0] = val;
EP0BCH = 0;
EP0BCL = 1;
return TRUE;
}
/* 0xB2: RAW_DEMOD_WRITE -- write BCM4500 register */
case RAW_DEMOD_WRITE: {
WORD widx;
widx = SETUP_INDEX();
bcm_indirect_write((BYTE)wval, (BYTE)widx);
return TRUE;
}
/* 0xB3: BLIND_SCAN */
case BLIND_SCAN:
/* EP0 data phase: wait for 16 bytes from host */
EP0BCL = 0;
SYNCDELAY;
if (!ep0_wait_data()) return TRUE;
if (EP0BCL < 16) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_blind_scan();
return TRUE;
/* 0xB4: I2C_BUS_SCAN -- probe all 7-bit addresses, return 16-byte bitmap */
case 0xB4: {
BYTE a, byte_idx, bit;
/* 128 addresses / 8 = 16 bytes bitmap */
for (byte_idx = 0; byte_idx < 16; byte_idx++)
EP0BUF[byte_idx] = 0;
for (a = 1; a < 0x78; a++) {
/* Try START + address + write, see if ACK comes back */
I2CS |= bmSTART;
I2DAT = a << 1; /* write direction */
if (!i2c_wait_done()) break;
if (I2CS & bmACK) {
/* Device responded at this address */
byte_idx = a >> 3;
bit = a & 0x07;
EP0BUF[byte_idx] |= (1 << bit);
}
I2CS |= bmSTOP;
if (!i2c_wait_stop()) break;
}
EP0BCH = 0;
EP0BCL = 16;
return TRUE;
}
/* 0xB5: I2C_RAW_READ -- read N bytes from any I2C address
* wValue = 7-bit I2C address, wIndex = register, wLength = bytes to read
* Uses combined write-read with repeated START */
case 0xB5: {
BYTE i2c_addr_b5 = (BYTE)wval;
BYTE i2c_reg_b5 = (BYTE)SETUP_INDEX();
BYTE i2c_len_b5 = (BYTE)SETUP_LENGTH();
BYTE ok;
if (i2c_len_b5 > 64) i2c_len_b5 = 64;
ok = i2c_combined_read(i2c_addr_b5, i2c_reg_b5, i2c_len_b5, EP0BUF);
if (!ok) {
BYTE fi;
for (fi = 0; fi < i2c_len_b5; fi++)
EP0BUF[fi] = 0xFF;
}
EP0BCH = 0;
EP0BCL = i2c_len_b5;
return TRUE;
}
/* 0xB6: I2C_DIAG -- step-by-step indirect register read diagnostic
* wValue = page/register to read
* Returns 8 bytes: [write_A6_ok, readback_A6, write_A8_ok, readback_A8,
* readback_A7, direct_read_A6, direct_read_A7, direct_read_A8] */
case 0xB6: {
/* Use shared xdata diag buffer to save DSEG.
* wValue = page to read, wIndex = A8 command (0 defaults to READ) */
BYTE cmd_b6;
vc_diag[0] = (BYTE)wval; /* target page */
cmd_b6 = (BYTE)SETUP_INDEX();
if (cmd_b6 == 0) cmd_b6 = BCM_CMD_READ;
/* Step 1: Write page to A6 */
vc_diag[1] = bcm_direct_write(BCM_REG_PAGE, vc_diag[0]) ? 0x01 : 0x00;
/* Step 2: Read back A6 */
vc_diag[2] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &vc_diag[2]);
/* Step 3: Write command to A8 */
vc_diag[3] = bcm_direct_write(BCM_REG_CMD, cmd_b6) ? 0x01 : 0x00;
/* Step 4: IMMEDIATE readback of A8 (no delay) */
vc_diag[4] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &vc_diag[4]);
/* Step 5: Delay for command execution */
delay(2);
/* Step 6: Read A8 AGAIN after delay */
vc_diag[5] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &vc_diag[5]);
/* Step 7: Read A7 (data result) */
vc_diag[6] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &vc_diag[6]);
/* Step 8: Final A6 state */
vc_diag[7] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &vc_diag[7]);
EP0BUF[0] = vc_diag[1]; /* write_A6_ok */
EP0BUF[1] = vc_diag[2]; /* A6 readback */
EP0BUF[2] = vc_diag[3]; /* write_A8_ok */
EP0BUF[3] = vc_diag[4]; /* A8 IMMEDIATE readback */
EP0BUF[4] = vc_diag[5]; /* A8 after 2ms delay */
EP0BUF[5] = vc_diag[6]; /* A7 data */
EP0BUF[6] = vc_diag[7]; /* A6 final */
EP0BUF[7] = cmd_b6; /* echo: command sent */
EP0BCH = 0;
EP0BCL = 8;
return TRUE;
}
/* 0xB7: SIGNAL_MONITOR -- fast combined signal read (8 bytes)
* Returns SNR(2) + AGC1(2) + AGC2(2) + lock(1) + status(1)
* in a single USB transfer instead of 3 separate reads. */
case SIGNAL_MONITOR: {
BYTE sm_val;
/* Zero-fill before reads so I2C failures return zeros, not stale data */
EP0BUF[0] = 0; EP0BUF[1] = 0; EP0BUF[2] = 0;
EP0BUF[3] = 0; EP0BUF[4] = 0; EP0BUF[5] = 0;
/* SNR: indirect regs 0x00-0x01 */
bcm_indirect_read(0x00, &EP0BUF[0]);
bcm_indirect_read(0x01, &EP0BUF[1]);
/* AGC1: indirect regs 0x02-0x03 */
bcm_indirect_read(0x02, &EP0BUF[2]);
bcm_indirect_read(0x03, &EP0BUF[3]);
/* AGC2: indirect regs 0x04-0x05 */
bcm_indirect_read(0x04, &EP0BUF[4]);
bcm_indirect_read(0x05, &EP0BUF[5]);
/* Lock register (direct 0xA4) */
sm_val = 0;
bcm_direct_read(BCM_REG_LOCK, &sm_val);
EP0BUF[6] = sm_val;
/* Status register (direct 0xA2) */
sm_val = 0;
bcm_direct_read(BCM_REG_STATUS, &sm_val);
EP0BUF[7] = sm_val;
EP0BCH = 0;
EP0BCL = 8;
return TRUE;
}
/* 0xB8: TUNE_MONITOR -- tune + dwell + read in one round-trip
* OUT phase (0x40): receive 10-byte tune payload, tune, dwell, read signal
* IN phase (0xC0): return stored 10-byte result
* wValue = dwell time in ms (1-255) */
case TUNE_MONITOR: {
if (SETUPDAT[0] & 0x80) {
/* IN phase: return stored result from previous OUT phase */
BYTE ti;
for (ti = 0; ti < 10; ti++)
EP0BUF[ti] = tm_result[ti];
EP0BCH = 0;
EP0BCL = 10;
} else {
/* OUT phase: tune, dwell, measure */
BYTE dwell = (BYTE)wval;
EP0BCL = 0;
SYNCDELAY;
if (!ep0_wait_data()) return TRUE;
if (EP0BCL < 10) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_tune();
if (dwell > 0)
delay(dwell);
/* Read signal into result buffer */
bcm_indirect_read(0x00, &tm_result[0]);
bcm_indirect_read(0x01, &tm_result[1]);
bcm_indirect_read(0x02, &tm_result[2]);
bcm_indirect_read(0x03, &tm_result[3]);
bcm_indirect_read(0x04, &tm_result[4]);
bcm_indirect_read(0x05, &tm_result[5]);
tm_result[6] = 0;
bcm_direct_read(BCM_REG_LOCK, &tm_result[6]);
tm_result[7] = 0;
bcm_direct_read(BCM_REG_STATUS, &tm_result[7]);
tm_result[8] = dwell;
tm_result[9] = (BYTE)(wval >> 8);
}
return TRUE;
}
/* 0xB9: MULTI_REG_READ -- batch read of contiguous indirect registers
* wValue = start register, wIndex = count (1-64)
* Returns count bytes, one per register */
case MULTI_REG_READ: {
BYTE start_reg = (BYTE)wval;
BYTE count = (BYTE)SETUP_INDEX();
BYTE mi;
if (count == 0 || count > 64)
count = 1;
for (mi = 0; mi < count; mi++) {
EP0BUF[mi] = 0;
bcm_indirect_read(start_reg + mi, &EP0BUF[mi]);
}
EP0BCH = 0;
EP0BCL = count;
return TRUE;
}
/* 0xBA: PARAM_SWEEP -- parameterized spectrum sweep */
case PARAM_SWEEP:
EP0BCL = 0;
SYNCDELAY;
if (!ep0_wait_data()) return TRUE;
if (EP0BCL < 16) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_param_sweep();
return TRUE;
/* 0xBB: ADAPTIVE_BLIND_SCAN -- blind scan with AGC pre-check */
case ADAPTIVE_BLIND_SCAN:
EP0BCL = 0;
SYNCDELAY;
if (!ep0_wait_data()) return TRUE;
if (EP0BCL < 18) { last_error = ERR_EP0_TIMEOUT; return TRUE; }
do_adaptive_blind_scan();
return TRUE;
/* 0xBC: GET_LAST_ERROR -- return diagnostic error code */
case GET_LAST_ERROR:
EP0BUF[0] = last_error;
EP0BCH = 0;
EP0BCL = 1;
return TRUE;
/* 0xBD: GET_STREAM_DIAG -- streaming diagnostics counters
* Returns 12 bytes:
* [0-3] u32 LE poll_count (main-loop poll cycles while armed)
* [4-5] u16 LE overflow_count (EP2 FIFO full events)
* [6-7] u16 LE sync_loss (BCM4500 lock-lost transitions)
* [8] u8 last_status (BCM4500 register 0xA2)
* [9] u8 last_lock (BCM4500 register 0xA4)
* [10] u8 armed (1 if currently streaming)
* [11] u8 had_sync (1 if currently locked)
* wValue=1 to reset counters after read. */
case GET_STREAM_DIAG:
EP0BUF[0] = (BYTE)(sd_poll_count);
EP0BUF[1] = (BYTE)(sd_poll_count >> 8);
EP0BUF[2] = (BYTE)(sd_poll_count >> 16);
EP0BUF[3] = (BYTE)(sd_poll_count >> 24);
EP0BUF[4] = (BYTE)(sd_overflow_count);
EP0BUF[5] = (BYTE)(sd_overflow_count >> 8);
EP0BUF[6] = (BYTE)(sd_sync_loss);
EP0BUF[7] = (BYTE)(sd_sync_loss >> 8);
EP0BUF[8] = sd_last_status;
EP0BUF[9] = sd_last_lock;
EP0BUF[10] = (config_status & BM_ARMED) ? 1 : 0;
EP0BUF[11] = sd_had_sync;
/* Reset counters if wValue=1.
* Non-atomic read+reset is safe: single-threaded main loop,
* ISR only sets got_sud (never touches diag counters). */
if (wval == 1) {
sd_poll_count = 0;
sd_overflow_count = 0;
sd_sync_loss = 0;
}
EP0BCH = 0;
EP0BCL = 12;
return TRUE;
/* 0xBE: GET_HOTPLUG_STATUS -- I2C device change detection
* Returns 36 bytes:
* [0-15] current I2C bus bitmap (16 bytes)
* [16-17] u16 LE change_count (cumulative change events)
* [18] u8 devices_added (in last scan)
* [19] u8 devices_removed (in last scan)
* [20-35] previous I2C bus bitmap (16 bytes)
* wValue=1 to reset change counter after read.
* wValue=2 to force immediate rescan. */
case GET_HOTPLUG_STATUS: {
static __xdata BYTE hp_i;
if (wval == 2) {
/* Force rescan (only when not streaming) */
if (!(config_status & BM_ARMED))
i2c_hotplug_scan();
}
for (hp_i = 0; hp_i < 16; hp_i++)
EP0BUF[hp_i] = hp_curr[hp_i];
EP0BUF[16] = (BYTE)(hp_changes);
EP0BUF[17] = (BYTE)(hp_changes >> 8);
EP0BUF[18] = hp_added;
EP0BUF[19] = hp_removed;
for (hp_i = 0; hp_i < 16; hp_i++)
EP0BUF[20 + hp_i] = hp_prev[hp_i];
if (wval == 1)
hp_changes = 0;
EP0BCH = 0;
EP0BCL = 36;
return TRUE;
}
/* 0xC0: EEPROM_READ -- read bytes from EEPROM at 16-bit address
* wValue = 16-bit EEPROM address
* wIndex = number of bytes to read (1-64)
* Returns the raw EEPROM data */
case EEPROM_READ: {
BYTE ei, elen;
elen = (BYTE)SETUP_INDEX();
if (elen > 64) elen = 64;
if (elen == 0) elen = 1;
/* Set EEPROM address: START → 0xA2 → addr_hi → addr_lo */
I2CS |= bmSTART;
I2DAT = 0xA2;
if (!i2c_wait_done()) goto ee_fail;
if (!(I2CS & bmACK)) goto ee_fail;
I2DAT = (BYTE)(wval >> 8); /* address high byte */
if (!i2c_wait_done()) goto ee_fail;
if (!(I2CS & bmACK)) goto ee_fail;
I2DAT = (BYTE)(wval); /* address low byte */
if (!i2c_wait_done()) goto ee_fail;
if (!(I2CS & bmACK)) goto ee_fail;
/* Repeated START → 0xA3 → read data */
I2CS |= bmSTART;
I2DAT = 0xA3;
if (!i2c_wait_done()) goto ee_fail;
if (!(I2CS & bmACK)) goto ee_fail;
if (elen == 1) I2CS |= bmLASTRD;
ei = I2DAT; /* dummy read */
for (ei = 0; ei < elen; ei++) {
if (!i2c_wait_done()) goto ee_fail;
if (ei == elen - 2) I2CS |= bmLASTRD;
if (ei == elen - 1) I2CS |= bmSTOP;
EP0BUF[ei] = I2DAT;
}
i2c_wait_stop();
EP0BCH = 0;
EP0BCL = elen;
return TRUE;
ee_fail:
I2CS |= bmSTOP;
i2c_wait_stop();
for (ei = 0; ei < elen; ei++)
EP0BUF[ei] = 0xFF;
EP0BCH = 0;
EP0BCL = elen;
return TRUE;
}
/* 0xBF: GET_PLL_DIAG -- PLL config diagnostic from last boot
* Returns 10 bytes:
* [0] eeprom_present (1=ok, 0=fail)
* [1] first_block_count (0xFF if not reached)
* [2] blocks_written
* [3] last_A9 value (0xFF if none)
* [4] last_AA value (0xFF if none)
* [5] last_AB_count (0xFF if none)
* [6] config_exit (1=ok, 0=fail, 0xFF=not reached)
* [7] overall_result (1=ok, 0=fail)
* [8] boot_stage (0xFF=complete, else stage where it stopped)
* [9] last_error snapshot */
case GET_PLL_DIAG: {
BYTE di;
for (di = 0; di < 8; di++)
EP0BUF[di] = pll_diag[di];
EP0BUF[8] = boot_stage;
EP0BUF[9] = last_error;
EP0BCH = 0;
EP0BCL = 10;
return TRUE;
}
/* 0xC1: I2C_RAW_WRITE -- write 1 byte to any I2C register
* wValue = 7-bit I2C address
* wIndex high byte = register, low byte = data
* Returns 1 byte: 0x01 on success, 0x00 on failure */
case 0xC1: {
WORD widx_c1 = SETUP_INDEX();
EP0BUF[0] = i2c_write_timeout((BYTE)wval,
(BYTE)(widx_c1 >> 8), (BYTE)widx_c1) ? 0x01 : 0x00;
EP0BCH = 0;
EP0BCL = 1;
return TRUE;
}
/* 0xC2: MULTI_WRITE_TEST -- auto-increment test for BCM3440 gateway.
* Writes 3 test bytes to register A7 as multi-byte I2C, then reads
* A6/A7/A8/A9. Returns 8 bytes. Uses vc_diag[] to avoid DSEG. */
case 0xC2:
i2c_buf[0] = 0xAA;
i2c_buf[1] = 0xBB;
i2c_buf[2] = 0xCC;
vc_diag[0] = i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, 3, i2c_buf) ? 1 : 0;
delay(1);
vc_diag[1] = 0xEE; bcm_direct_read(BCM_REG_PAGE, &vc_diag[1]);
vc_diag[2] = 0xEE; bcm_direct_read(BCM_REG_DATA, &vc_diag[2]);
vc_diag[3] = 0xEE; bcm_direct_read(BCM_REG_CMD, &vc_diag[3]);
vc_diag[4] = 0xEE; bcm_direct_read(0xA9, &vc_diag[4]);
EP0BUF[0] = vc_diag[0]; EP0BUF[1] = vc_diag[1];
EP0BUF[2] = vc_diag[2]; EP0BUF[3] = vc_diag[3];
EP0BUF[4] = vc_diag[4]; EP0BUF[5] = 0;
EP0BUF[6] = 0; EP0BUF[7] = 0;
EP0BCH = 0;
EP0BCL = 8;
return TRUE;
default:
return FALSE;
}
}
/* ---------- Required fx2lib callbacks ---------- */
BOOL handle_get_descriptor(void) {
return FALSE;
}
BOOL handle_get_interface(BYTE ifc, BYTE *alt_ifc) {
if (ifc == 0) {
*alt_ifc = 0;
return TRUE;
}
return FALSE;
}
BOOL handle_set_interface(BYTE ifc, BYTE alt_ifc) {
if (ifc == 0 && alt_ifc == 0) {
RESETTOGGLE(0x82);
RESETFIFO(0x02);
return TRUE;
}
return FALSE;
}
BYTE handle_get_configuration(void) {
return 1;
}
BOOL handle_set_configuration(BYTE cfg) {
return cfg == 1 ? TRUE : FALSE;
}
/* ---------- USB interrupt handlers ---------- */
void sudav_isr(void) __interrupt (SUDAV_ISR) {
got_sud = TRUE;
CLEAR_SUDAV();
}
void usbreset_isr(void) __interrupt (USBRESET_ISR) {
handle_hispeed(FALSE);
CLEAR_USBRESET();
}
void hispeed_isr(void) __interrupt (HISPEED_ISR) {
handle_hispeed(TRUE);
CLEAR_HISPEED();
}
/* Software watchdog Timer0 ISR: fires every ~16.384ms.
* If the main loop stops kicking, cut LNB power for safety.
* wdt_armed states: 0=off, 1=armed, 2=fired (power was cut). */
void timer0_isr(void) __interrupt (1) {
if (wdt_armed != 1) return;
if (wdt_counter > 0) {
wdt_counter--;
} else {
/* Main loop stalled — cut LNB power for safety.
* IOA RMW race note: if the main loop is genuinely hung (which
* it must be for wdt_counter to reach 0), it is not executing
* IOA modifications. If by rare coincidence we interrupt an IOA
* RMW, the main loop's stale write re-enables power — but then
* wdt_armed=2 prevents wdt_kick() from rearming, so the next
* ISR tick exits immediately and the main loop's own guard
* checks (BM_STARTED etc.) will prevent further I2C activity. */
IOA = (IOA & ~PIN_PWR_EN) | PIN_PWR_DIS;
wdt_armed = 2; /* fired — main loop must not re-arm */
}
}
/* ---------- Main ---------- */
void main(void) {
config_status = 0;
last_error = ERR_OK;
got_sud = FALSE;
/* Initialize hot-plug detection state */
hp_changes = 0;
hp_added = 0;
hp_removed = 0;
hp_scan_ok = 0;
hp_last_frame = 0;
/* Initialize streaming diagnostics */
sd_poll_count = 0;
sd_overflow_count = 0;
sd_sync_loss = 0;
sd_last_status = 0;
sd_last_lock = 0;
sd_had_sync = 0;
REVCTL = 0x03; /* NOAUTOARM + SKIPCOMMIT */
SYNCDELAY;
RENUMERATE_UNCOND();
SETCPUFREQ(CLK_48M);
SETIF48MHZ();
USE_USB_INTS();
ENABLE_SUDAV();
ENABLE_HISPEED();
ENABLE_USBRESET();
/* Configure I2C: 400kHz */
I2CTL = bm400KHZ;
/* Configure GPIO output enables (v2.06 pin map):
* P0.1=power_en, P0.2=power_dis, P0.3=22kHz, P0.4=LNB,
* P0.5=BCM_reset, P0.7=DiSEqC/streaming */
OEA |= (PIN_PWR_EN | PIN_PWR_DIS | PIN_22KHZ | PIN_LNB_VOLT |
PIN_BCM_RESET | PIN_DISEQC);
/* Initial GPIO state matches stock firmware:
* P0.7=1 (DiSEqC idle), P0.5=0 (BCM4500 held in reset),
* P0.2=1 (power disable), all others LOW */
IOA = 0x84;
IOD = 0xE1; /* P3.7:5=1 (controls idle), P3.0=1 */
/* EP2 is bulk IN (0x82), 512 byte, double-buffered */
EP2CFG = 0xE2; /* valid, IN, bulk, 512, double */
SYNCDELAY;
/* Disable unused endpoints */
EP1INCFG &= ~bmVALID;
SYNCDELAY;
EP1OUTCFG &= ~bmVALID;
SYNCDELAY;
EP4CFG &= ~bmVALID;
SYNCDELAY;
EP6CFG &= ~bmVALID;
SYNCDELAY;
EP8CFG &= ~bmVALID;
SYNCDELAY;
/* Reset all FIFOs */
RESETFIFOS();
/* IFCONFIG: internal 48MHz, GPIF master, async */
IFCONFIG = 0xEE;
SYNCDELAY;
/* EP2FIFOCFG: AUTOIN, ZEROLENIN, 8-bit */
EP2FIFOCFG = 0x0C;
SYNCDELAY;
/* Disable other FIFO configs */
EP4FIFOCFG = 0;
SYNCDELAY;
EP6FIFOCFG = 0;
SYNCDELAY;
EP8FIFOCFG = 0;
SYNCDELAY;
EA = 1; /* global interrupt enable */
wdt_init(); /* start software watchdog (~2s timeout) */
while (TRUE) {
wdt_kick(); /* main loop alive — reset watchdog */
/* If watchdog fired while we were blocked in a vendor command,
* acknowledge it: set error code so host can read via 0xBC,
* and clear config flags so stale state doesn't cause confusion. */
if (wdt_armed == 2) {
last_error = ERR_WDT_FIRED;
config_status &= ~(BM_STARTED | BM_FW_LOADED | BM_ARMED);
wdt_armed = 0; /* acknowledged — host must re-boot to recover */
}
if (got_sud) {
handle_setupdata();
got_sud = FALSE;
}
/* Periodic tasks using USB frame counter (125us/frame at HS).
* USBFRAMEH:USBFRAMEL wraps every 2048ms (16384 frames).
* We check every ~8000 frames (~1 second).
*
* The two SFRs are incremented by USB hardware asynchronously,
* so we read H-L-H and retry if H changed (carry propagation). */
{
WORD cur_frame;
BYTE fh;
do {
fh = USBFRAMEH;
cur_frame = ((WORD)fh << 8) | USBFRAMEL;
} while (fh != USBFRAMEH);
/* Streaming diagnostics: poll every main-loop iteration when armed */
if (config_status & BM_ARMED) {
stream_diag_poll();
}
/* I2C hot-plug: scan every ~1s, but only when NOT streaming
* (I2C bus contention with GPIF would corrupt data) */
if (!(config_status & BM_ARMED) &&
((WORD)(cur_frame - hp_last_frame) > 8000)) {
hp_last_frame = cur_frame;
i2c_hotplug_scan();
}
}
}
}