diff --git a/firmware/skywalker1.c b/firmware/skywalker1.c index acb4493..e19f04a 100644 --- a/firmware/skywalker1.c +++ b/firmware/skywalker1.c @@ -1,2256 +1,2664 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include - -#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 -#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 */ -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; - - /* 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). - * 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 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(); - } - } - } -} +/* + * 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 +#include +#include +#include +#include +#include +#include + +#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(); + } + } + } +}