skywalker-1/firmware/skywalker1.c
Ryan Malloy 834c2bd9ee Add software watchdog and timeout protection for all I2C/USB paths (firmware v3.05.0)
Safety review identified infinite-hang paths in do_tune(), GPIF streaming,
EP0/EP2 FIFO waits, and the 0xB4 I2C bus scan. The firmware controls an LNB
power supply (750mA at 18V) on a Cypress FX2LP with no hardware watchdog.

Key changes:
- Software watchdog via Timer0 ISR (~2s timeout, cuts LNB power on stall)
- Replace fx2lib i2c_write() in do_tune() with timeout-protected helper
- ep0_wait_data() helper replaces 7 bare EP0 BUSY spin loops
- GPIF idle wait and EP2 FIFO full wait now have timeouts
- 0xB4 I2C bus scan uses i2c_wait_done()/i2c_wait_stop() instead of bare spins
- Return-value checks on all I2C writes in sweep/scan functions
- EP0 payload length validation on all vendor commands with data phase
- Zero-fill EP0BUF before indirect reads (0x87, 0xB7) for deterministic output
- i2c_wait_stop() now sets last_error on timeout
- New error codes: ERR_TUNE_FAIL through ERR_DISEQC_LEN
- BCM_LOCK_BIT constant replaces hardcoded 0x20 in lock checks
- DiSEqC Tone Burst B rejected with ERR_NOT_SUPPORTED
- DiSEqC message length error sets ERR_DISEQC_LEN
- hp_changes counter saturates instead of wrapping
- stream_diag_poll() only updates status on successful I2C reads
- do_tune() forward declaration eliminates implicit-function warning
- do_tune() sets ERR_BCM_NOT_READY when demod not booted
- wdt_kick() in all long-running sweep/scan loops

Code: 13,057 / 15,360 bytes (85%). XRAM: 218 / 512 bytes (43%).
Stack: 132 bytes free. Zero new SDCC warnings.
2026-02-16 03:41:08 -07:00

2247 lines
71 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
/* BCM4500 I2C address (7-bit); 8-bit wire address is 0x10/0x11 */
#define BCM4500_ADDR 0x08
/* BCM4500 indirect register protocol registers */
#define BCM_REG_PAGE 0xA6
#define BCM_REG_DATA 0xA7
#define BCM_REG_CMD 0xA8
/* 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
/* 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
/* 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 */
static __xdata BYTE i2c_buf[16];
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];
/* 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.
*/
static const __code BYTE bcm_init_block0[] = {
0x06, 0x0b, 0x17, 0x38, 0x9f, 0xd9, 0x80
};
static const __code BYTE bcm_init_block1[] = {
0x07, 0x09, 0x39, 0x4f, 0x00, 0x65, 0xb7, 0x10
};
static const __code BYTE bcm_init_block2[] = {
0x0f, 0x0c, 0x09
};
#define BCM_INIT_BLOCK0_LEN 7
#define BCM_INIT_BLOCK1_LEN 8
#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). Many I2C devices including the BCM4500
* require this pattern instead of separate write+stop/read+stop.
*
* 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;
}
/* ---------- 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 direct I2C register (subaddr).
*/
static BOOL bcm_direct_write(BYTE reg, BYTE val) {
return i2c_write_timeout(BCM4500_ADDR, reg, val);
}
/*
* Read one byte from a BCM4500 direct I2C register 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 readiness. Reads status registers and waits
* for the command register to indicate idle.
*/
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, last_error already set */
}
delay(2);
}
last_error = ERR_BCM_TIMEOUT;
return FALSE;
}
/*
* Write one block of initialization data to BCM4500 indirect registers.
* Replicates FUN_CODE_0ddd's per-iteration I2C sequence from stock firmware:
* 1. Write 0x00 to reg 0xA6 (page select = page 0)
* 2. Write data[0..len-1] to reg 0xA7 (data buffer, auto-increment)
* 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];
/* Write data bytes to 0xA7 */
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();
}
/*
* BCM4500 full boot sequence, reverse-engineered from stock firmware
* FUN_CODE_1D4F (reset/power) + FUN_CODE_0ddd (register init).
*
* 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
* Write 3 register initialization blocks
*/
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 BCM4500 is alive on I2C before attempting register init.
* If we can't read a direct register, the chip didn't come out of reset. */
if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0]))
return FALSE;
boot_stage = 4; /* Stage 4: register init block 0 */
/* Initialize BCM4500 registers -- 3 blocks from stock firmware */
if (!bcm_write_init_block(bcm_init_block0, BCM_INIT_BLOCK0_LEN))
return FALSE;
boot_stage = 5; /* Stage 5: register init block 1 */
if (!bcm_write_init_block(bcm_init_block1, BCM_INIT_BLOCK1_LEN))
return FALSE;
boot_stage = 6; /* Stage 6: register init block 2 */
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;
/* Save current as previous before starting new scan.
* This keeps hp_prev valid between scans so the host can read
* both bitmaps and see the actual transition. */
for (hp_a = 0; hp_a < 16; hp_a++)
hp_prev[hp_a] = hp_curr[hp_a];
/* Clear current scan buffer */
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);
}
}
}
}
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. */
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;
}
/* ---------- 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) {
BYTE i;
if (sat_b) { last_error = ERR_NOT_SUPPORTED; return; }
/* Configure Timer2 auto-reload */
/* CKCON.T2M = 0 -> Timer2 clk = 48MHz/12 = 4MHz */
CKCON &= ~0x20;
T2CON = 0x04; /* auto-reload, running */
RCAP2H = 0xF8;
RCAP2L = 0x2F; /* reload = 63535 -> ~500us tick */
TL2 = 0xFF;
TH2 = 0xFF; /* force immediate overflow */
/* Pre-burst settling: 15 ticks (~7.5ms) with carrier off */
IOA &= ~PIN_22KHZ;
TF2 = 0;
for (i = 0; i < 15; i++) {
while (!TF2)
;
TF2 = 0;
}
/* Burst: 25 ticks (~12.5ms) with carrier on */
IOA |= PIN_22KHZ;
for (i = 0; i < 25; i++) {
while (!TF2)
;
TF2 = 0;
}
/* Carrier off */
IOA &= ~PIN_22KHZ;
/* Post-burst settling: 5 ticks (~2.5ms) */
for (i = 0; i < 5; i++) {
while (!TF2)
;
TF2 = 0;
}
/* 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;
for (dt_i = 0; dt_i < count; dt_i++) {
while (!TF2)
;
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).
* Uses our timeout-protected multi-byte write instead of fx2lib i2c_write(). */
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) {
if (bcm4500_boot()) {
config_status |= (BM_STARTED | BM_FW_LOADED);
} else {
bcm4500_shutdown();
config_status &= ~(BM_STARTED | BM_FW_LOADED);
}
} 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 */
case RAW_DEMOD_READ:
val = 0;
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 */
vc_diag[0] = (BYTE)wval; /* target_reg */
/* Step 1: Write target register to page select (0xA6) */
vc_diag[1] = bcm_direct_write(BCM_REG_PAGE, vc_diag[0]) ? 0x01 : 0x00;
/* Step 2: Read back 0xA6 to verify write */
vc_diag[2] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &vc_diag[2]);
/* Step 3: Write read command (0x01) to 0xA8 */
vc_diag[3] = bcm_direct_write(BCM_REG_CMD, BCM_CMD_READ) ? 0x01 : 0x00;
/* Step 4: Read back 0xA8 to check command status */
vc_diag[4] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &vc_diag[4]);
/* Step 5: Small delay for command execution */
delay(2);
/* Step 6: Read 0xA7 (data register) — this is the result */
vc_diag[5] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &vc_diag[5]);
/* Step 7: Read back all three control regs for final state */
vc_diag[6] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &vc_diag[6]);
vc_diag[7] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &vc_diag[7]);
EP0BUF[0] = vc_diag[1]; /* write_A6_ok */
EP0BUF[1] = vc_diag[2]; /* readback_A6 */
EP0BUF[2] = vc_diag[3]; /* write_A8_ok */
EP0BUF[3] = vc_diag[4]; /* readback_A8 */
EP0BUF[4] = vc_diag[5]; /* readback_A7 */
EP0BUF[5] = vc_diag[6]; /* direct_read_A6 */
/* Read remaining registers directly into EP0BUF */
vc_diag[6] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &vc_diag[6]);
EP0BUF[6] = vc_diag[6]; /* direct_read_A7 */
vc_diag[7] = 0xEE;
i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &vc_diag[7]);
EP0BUF[7] = vc_diag[7]; /* direct_read_A8 */
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;
}
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 (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();
}
}
}
}