skywalker-1/firmware/skywalker1.c
Ryan Malloy e9e5ab859a Add init block readback verification to match stock firmware
Stock firmware at 0x0DDD reads back each init block from register A7
after writing and verifies the data matches (with bit 7 XOR'd on the
block address byte). Without this readback, the BCM4500 may not
finalize the write — our init blocks were "silently failing" (I2C
succeeds, status reports booted, but SNR reads all zeros).

Changes:
- bcm_write_init_block: add pre-write bcm_poll_ready(), post-write
  A7 readback with XOR(0x80) on byte[0] and full data comparison
- i2c_rd buffer: expand from 8 to 24 bytes for 16-byte block readback

This is the most likely root cause of the BCM4500 "boots but doesn't
work" issue (Task #5). Needs hardware test to confirm.
2026-02-20 11:08:40 -07:00

3227 lines
105 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

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

/*
* Genpix SkyWalker-1 Custom Firmware
* For Cypress CY7C68013A (FX2LP) + Broadcom BCM4500 demodulator
*
* Stock-compatible vendor commands (0x80-0x94) plus custom
* spectrum sweep, raw demod access, blind scan (0xB0-0xB3),
* hardware diagnostics (0xB4-0xB6), signal monitoring (0xB7-0xB9),
* advanced commands: parameterized sweep (0xBA), adaptive blind scan
* (0xBB), error codes (0xBC), DiSEqC messaging (0x8D), streaming
* diagnostics (0xBD), and I2C hot-plug detection (0xBE).
*
* SDCC + fx2lib toolchain. Loaded into FX2 RAM for testing.
*/
#include <fx2regs.h>
#include <fx2macros.h>
#include <delay.h>
#include <autovector.h>
#include <setupdat.h>
#include <eputils.h>
#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[24];
/* 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[24];
/* 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 */
/* BCM4500 signal read: 16-byte block returned by indirect register protocol */
static __xdata BYTE sig_block[16];
/* Track current modulation index for signal read sub-address selection.
* Stock firmware uses sub_addr 0x10 for standard modes (DVB-S QPSK, DSS,
* BPSK) and 0x11 for turbo/DigiCipher modes (mod_index >= 4). */
static __xdata BYTE current_mod_index;
/* 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.
*
* Boot blocks (FUN_CODE_0ddd at 0x0DDD): written once during BOOT_8PSK.
* Tune blocks (FUN_CODE_0ee9 at 0x0EE9): written before every retune,
* zeroing filter coefficients while preserving structure/address bytes.
*
* Stock stores both sets at code:0x0B4E in 17-byte XDATA format:
* [count, data[0..count-1], padding...] where count = bytes to write to A7.
* The first data byte in each block is the indirect register block address
* (0x06, 0x07, 0x03) — the BCM4500 uses it to route the remaining bytes.
*/
/* Boot blocks — full coefficients (code:0x0B4E, XDATA 0xE0F7) */
static const __code BYTE bcm_boot_block0[] = {
0x06, 0x0b, 0x17, 0x38, 0x9f, 0xd9, 0x80
};
static const __code BYTE bcm_boot_block1[] = {
0x07, 0x09, 0x39, 0x4f, 0x00, 0x65, 0xb7, 0x10
};
static const __code BYTE bcm_boot_block2[] = {
0x03, 0x0f, 0x0c, 0x09, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xeb, 0x00
};
#define BCM_BOOT_BLOCK0_LEN 7
#define BCM_BOOT_BLOCK1_LEN 8
#define BCM_BOOT_BLOCK2_LEN 16
/* Tune blocks — zeroed coefficients (code:0x0B85, XDATA 0xE0C4).
* Same structure as boot blocks but with filter coefficients cleared.
* Block 2 is identical in both sets. */
static const __code BYTE bcm_tune_block0[] = {
0x06, 0x0b, 0x17, 0x00, 0x00, 0x00, 0x00
};
static const __code BYTE bcm_tune_block1[] = {
0x07, 0x09, 0x39, 0x4f, 0x00, 0x00, 0x00, 0x00
};
#define BCM_TUNE_BLOCK0_LEN 7
#define BCM_TUNE_BLOCK1_LEN 8
/* Tune block 2 = bcm_boot_block2 (identical data, reuse to save code space) */
/* ---------- Bit-bang I2C via GPIO ---------- */
/*
* Bypasses the FX2LP hardware I2C controller, which deadlocks when the
* CPU is halted (via 0xA0 CPUCS write) while the stock firmware has an
* I2C transaction in progress. The deadlock is unrecoverable by any
* software means — only a full chip power cycle clears it.
*
* SDA = PA0, true open-drain via OEA toggling (PA0 latch held at 0)
* SCL = PA1, push-pull (also PIN_PWR_EN — each LOW pulse briefly
* disables the power supply, but decoupling caps ride through the
* ~2-3μs glitch with <25mV droop)
*
* PORTACFG = 0x00: GPIO mode, I2C controller connected but NMOS frozen
* OFF in BERR state. Internal 1.5kΩ pull-ups active on both lines.
* Speed: ~30kHz at 48MHz CPU (conservative — 50×SYNCDELAY per half-period)
*/
#define I2C_TIMEOUT 6000 /* kept for ep0_wait_data */
#define GPIF_TIMEOUT 60000 /* GPIF idle wait (~15ms at 4MHz tick) */
#define EP2_TIMEOUT 60000 /* EP2 drain wait */
/* GPIO primitives — macros for tight inner loops.
*
* CRITICAL: IOA (SFR 0x80) has the classic 8051 read-modify-write bug.
* Byte-level RMW (IOA |= / IOA &= ~) reads ACTUAL PIN LEVELS for input
* pins, not the output latch. If PA0 (SDA) is an input (HIGH via pull-up),
* any byte RMW on IOA reads pin=1 and writes it to the latch, corrupting
* our SDA LOW state. Result: SDA never goes LOW, no valid I2C waveforms.
*
* Fix: PA0/PA1 are __sbit at SFR 0x80 (from fx2regs.h). These compile to
* SETB/CLR instructions — atomic single-bit ops, no RMW, no corruption.
*
* SDA (PA0): Pure GPIO, PORTACFG bit 0 = 1 ALWAYS (v6, experiment 0xD6).
* 0xD5 proved: with PORTACFG=0x00 + OEA=0, both SDA and SCL read HIGH.
* The I2C NMOS is NOT stuck ON at startup — it only re-latches when
* PORTACFG toggles back to 0x00 AFTER a GPIO LOW drive (0xD1 confirmed).
* The charge-then-disconnect approach (v5) CAUSED the re-latch by
* reconnecting PORTACFG=0x00 during BB_SDA_HIGH after BB_SDA_LOW.
*
* Fix: never toggle PORTACFG. Keep bit 0 = 1 (SDA disconnected from I2C
* controller permanently). Use GPIO push-pull to charge bus to 3.3V,
* then OEA release for open-drain emulation. Bus capacitance holds the
* charge for >100μs — well beyond our 17μs bit period.
*
* SCL (PA1): push-pull via atomic SETB/CLR (PORTACFG bit 1 always 0).
* GPIO push-pull overpowers any SCL NMOS (confirmed by 0xCD Phase 2).
* Internal 1.5kΩ pull-up assists (connected when PORTACFG bit 1 = 0). */
/* True open-drain I2C primitives (v7 — 0xD7).
*
* PORTACFG = 0x00 ALWAYS: GPIO mode with I2C controller connected.
* 0xD5 proved: I2C NMOS is frozen OFF in BERR state (IOA reads H
* with PORTACFG=0x00 + OEA=0). The internal 1.5kΩ pull-up is active.
* 0xD6 proved: PORTACFG=0x01 (INT0 mode) disconnects GPIO output —
* PA0 can't drive the pin at all. Must use PORTACFG=0x00.
*
* SDA: true open-drain via OEA toggling.
* LOW: PA0=0, OEA=1 → GPIO NMOS sinks to 0V (I2C NMOS is OFF).
* HIGH: OEA=0 → release, 1.5kΩ pull-up → 3.3V in ~0.6μs RC.
* No PORTACFG toggling → no NMOS re-trigger risk.
* No voltage divider → full 3.3V swing → slaves see clean signals.
*
* SCL: GPIO push-pull via atomic PA1 SETB/CLR.
* OEA bit 1 stays enabled. Internal 1.5kΩ pull-up assists. */
#define BB_SDA_HIGH() do { OEA &= ~0x01; } while(0)
#define BB_SDA_LOW() do { PA0 = 0; OEA |= 0x01; } while(0)
#define BB_SDA_READ() (IOA & 0x01)
#define BB_SCL_HIGH() do { PA1 = 1; } while(0)
#define BB_SCL_LOW() do { PA1 = 0; } while(0)
/* Half-period delay: 50×SYNCDELAY4 = 200 NOPs ≈ 17μs at 48MHz.
* Deliberately slow (~30kHz I2C) to test if the bus scan "all ACK"
* problem is caused by insufficient pull-up rise time.
* Normal I2C: 4.7kΩ pull-up × 400pF bus = 1.9μs RC constant.
* At 6 SYNCDELAYs (~2μs) we're marginal; at 50 SYNCDELAYs (~17μs)
* we have ~9 RC time constants = guaranteed full charge. */
static void bb_delay(void) {
BYTE bb_d;
for (bb_d = 0; bb_d < 50; bb_d++)
SYNCDELAY;
}
static void bb_i2c_start(void) {
BB_SDA_HIGH();
BB_SCL_HIGH();
bb_delay();
BB_SDA_LOW(); /* SDA falls while SCL high = START */
bb_delay();
BB_SCL_LOW();
bb_delay();
}
static void bb_i2c_stop(void) {
BB_SDA_LOW();
bb_delay();
BB_SCL_HIGH();
bb_delay();
BB_SDA_HIGH(); /* SDA rises while SCL high = STOP */
bb_delay();
}
/* Write one byte MSB-first. Returns 0 on ACK, 1 on NAK. */
static BYTE bb_i2c_write_byte(BYTE val) {
BYTE i, ack;
for (i = 0; i < 8; i++) {
if (val & 0x80) BB_SDA_HIGH(); else BB_SDA_LOW();
val <<= 1;
BB_SCL_HIGH();
bb_delay();
BB_SCL_LOW();
bb_delay();
}
BB_SDA_HIGH(); /* release for slave ACK */
bb_delay(); /* settle: pull-up charges bus from LOW → HIGH */
BB_SCL_HIGH();
bb_delay();
ack = BB_SDA_READ() ? 1 : 0;
BB_SCL_LOW();
bb_delay();
return ack;
}
/* Read one byte MSB-first. Sends ACK if ack=1, NAK if ack=0.
*
* Between each bit we do a brief SDA charge pulse (~166ns push-pull HIGH,
* then release). Without the I2C controller's 1.5kΩ pull-up, a '0' bit
* leaves bus capacitance at LOW — a subsequent '1' bit would read as '0'
* because nothing pulls SDA back HIGH. The charge pulse restores HIGH;
* the slave's NMOS can then pull LOW in ~20ns if the next bit is '0'. */
static BYTE bb_i2c_read_byte(BYTE ack) {
BYTE i, val = 0;
BB_SDA_HIGH(); /* initial charge + release for slave */
for (i = 0; i < 8; i++) {
BB_SCL_HIGH();
bb_delay();
val = (val << 1) | (BB_SDA_READ() ? 1 : 0);
BB_SCL_LOW();
/* Recharge SDA before next bit — slave overrides if it drives LOW */
BB_SDA_HIGH();
bb_delay();
}
if (ack) BB_SDA_LOW(); else BB_SDA_HIGH();
BB_SCL_HIGH();
bb_delay();
BB_SCL_LOW();
bb_delay();
BB_SDA_HIGH(); /* release SDA */
return val;
}
/* Probe a 7-bit I2C address. Returns 1 if device ACKs, 0 if NAK. */
static BYTE bb_i2c_probe(BYTE addr) {
BYTE nak;
bb_i2c_start();
nak = bb_i2c_write_byte(addr << 1);
bb_i2c_stop();
return nak ? 0 : 1;
}
/* Hardware I2C probe: uses the FX2 I2C controller (NOT bit-bang).
* Sends START + (addr<<1) on the bus, checks ACK, sends STOP.
* Returns 1 if slave ACKs, 0 if NAK or timeout.
* ONLY works when the I2C controller is in a clean state (no BERR).
* If BERR is set at entry, returns 0xFF as an error indicator. */
static BYTE hw_i2c_probe(BYTE addr) {
BYTE timeout, ack;
/* Bail if BERR — hardware controller is unusable */
if (I2CS & bmBERR)
return 0xFF;
/* START condition: setting bmSTART causes the controller to
* generate START when the next byte is written to I2DAT. */
I2CS = bmSTART;
/* Write address byte (7-bit addr + W=0). This triggers
* the START + address clock-out on the physical bus. */
I2DAT = addr << 1;
/* Wait for DONE (byte clocked out, ACK/NAK received) */
for (timeout = 0; timeout < 255; timeout++) {
SYNCDELAY;
if (I2CS & bmDONE) break;
}
/* Check if slave acknowledged */
ack = (I2CS & bmACK) ? 1 : 0;
/* STOP condition */
I2CS = bmSTOP;
/* Wait for STOP to complete (STOP bit clears when done) */
for (timeout = 0; timeout < 255; timeout++) {
SYNCDELAY;
if (!(I2CS & bmSTOP)) break;
}
return ack;
}
/* Hardware I2C register read: START → addr+W → reg → rSTART → addr+R → data → STOP.
* Uses the FX2 hardware I2C controller. Returns TRUE on success.
* Only works when the controller is clean (no BERR). */
static BOOL hw_i2c_read(BYTE addr, BYTE reg, BYTE len, __xdata BYTE *buf) {
BYTE timeout, i;
if (I2CS & bmBERR)
return FALSE;
/* Write phase: START + addr_W + register */
I2CS = bmSTART;
I2DAT = addr << 1;
for (timeout = 0; timeout < 255; timeout++) {
SYNCDELAY;
if (I2CS & bmDONE) break;
}
if (!(I2CS & bmACK)) goto hw_fail;
I2DAT = reg;
for (timeout = 0; timeout < 255; timeout++) {
SYNCDELAY;
if (I2CS & bmDONE) break;
}
/* Read phase: repeated START + addr_R */
I2CS = bmSTART;
I2DAT = (addr << 1) | 1;
for (timeout = 0; timeout < 255; timeout++) {
SYNCDELAY;
if (I2CS & bmDONE) break;
}
if (!(I2CS & bmACK)) goto hw_fail;
/* Read data bytes.
* First I2DAT read is a dummy that triggers clocking.
* Subsequent reads return previous byte and trigger next clock.
* Before last byte: set LASTRD so controller sends NAK. */
for (i = 0; i < len; i++) {
if (i == len - 1)
I2CS = bmLASTRD;
buf[i] = I2DAT;
for (timeout = 0; timeout < 255; timeout++) {
SYNCDELAY;
if (I2CS & bmDONE) break;
}
}
/* Required dummy read after LASTRD */
{ volatile BYTE dummy = I2DAT; (void)dummy; }
I2CS = bmSTOP;
for (timeout = 0; timeout < 255; timeout++) {
SYNCDELAY;
if (!(I2CS & bmSTOP)) break;
}
return TRUE;
hw_fail:
I2CS = bmSTOP;
for (timeout = 0; timeout < 255; timeout++) {
SYNCDELAY;
if (!(I2CS & bmSTOP)) break;
}
return FALSE;
}
/*
* 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.
* Sequence: START → addr+W → reg → RESTART → addr+R → data[0..len-1] → STOP
* Same signature as the old hardware-I2C version — all callers unchanged.
*/
static BOOL i2c_combined_read(BYTE addr, BYTE reg, BYTE len, BYTE *buf) {
BYTE i;
bb_i2c_start();
if (bb_i2c_write_byte(addr << 1))
{ last_error = ERR_I2C_NAK; goto fail; }
if (bb_i2c_write_byte(reg))
{ last_error = ERR_I2C_NAK; goto fail; }
bb_i2c_start(); /* repeated START */
if (bb_i2c_write_byte((addr << 1) | 1))
{ last_error = ERR_I2C_NAK; goto fail; }
for (i = 0; i < len; i++)
buf[i] = bb_i2c_read_byte(i < len - 1 ? 1 : 0);
bb_i2c_stop();
return TRUE;
fail:
bb_i2c_stop();
return FALSE;
}
/*
* Single-byte I2C write (bit-bang).
* Sends: START → (addr<<1) → reg → val → STOP
*/
static BOOL i2c_write_timeout(BYTE addr, BYTE reg, BYTE val) {
bb_i2c_start();
if (bb_i2c_write_byte(addr << 1))
{ last_error = ERR_I2C_NAK; goto fail; }
if (bb_i2c_write_byte(reg))
{ last_error = ERR_I2C_NAK; goto fail; }
if (bb_i2c_write_byte(val))
{ last_error = ERR_I2C_NAK; goto fail; }
bb_i2c_stop();
return TRUE;
fail:
bb_i2c_stop();
return FALSE;
}
/*
* Multi-byte I2C write (bit-bang).
* 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;
bb_i2c_start();
if (bb_i2c_write_byte(addr << 1))
{ last_error = ERR_I2C_NAK; goto fail; }
if (bb_i2c_write_byte(reg))
{ last_error = ERR_I2C_NAK; goto fail; }
for (i = 0; i < len; i++) {
if (bb_i2c_write_byte(data[i]))
{ last_error = ERR_I2C_NAK; goto fail; }
}
bb_i2c_stop();
return TRUE;
fail:
bb_i2c_stop();
return FALSE;
}
/* ---------- EEPROM (calibration data) ---------- */
/*
* Check EEPROM presence by reading 1 byte from address 0x3FFF (bit-bang).
* Sets internal pointer to 0x4000 where PLL data blocks begin.
* Result stored in i2c_buf[0].
*/
static BOOL eeprom_check_present(void) {
bb_i2c_start();
if (bb_i2c_write_byte(0xA2))
{ last_error = ERR_I2C_NAK; goto fail; }
if (bb_i2c_write_byte(0x3F))
{ last_error = ERR_I2C_NAK; goto fail; }
if (bb_i2c_write_byte(0xFF))
{ last_error = ERR_I2C_NAK; goto fail; }
bb_i2c_start(); /* repeated START */
if (bb_i2c_write_byte(0xA3))
{ last_error = ERR_I2C_NAK; goto fail; }
i2c_buf[0] = bb_i2c_read_byte(0); /* single byte, NAK */
bb_i2c_stop();
return TRUE;
fail:
bb_i2c_stop();
return FALSE;
}
/*
* Read 20 bytes sequentially from EEPROM into i2c_buf[] (bit-bang).
* EEPROM auto-increments its address pointer after each byte.
*/
static BOOL eeprom_read_block(void) {
BYTE i;
bb_i2c_start();
if (bb_i2c_write_byte(0xA3))
{ last_error = ERR_I2C_NAK; goto fail; }
for (i = 0; i < 20; i++)
i2c_buf[i] = bb_i2c_read_byte(i < 19 ? 1 : 0);
bb_i2c_stop();
return TRUE;
fail:
bb_i2c_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;
}
/*
* Read 16-byte signal data block from BCM4500 via indirect register protocol.
*
* Reverse-engineered from stock firmware function at code:0x0C97.
* The A7 register is a bidirectional FIFO: load the indirect address,
* execute with A8=0x03, then read the result from the same A7 register.
*
* Protocol:
* 1. A6 = 0x00 (page 0)
* 2. A7 FIFO = [0x01, sub_addr] (2-byte indirect block address)
* 3. A7 FIFO += [0x00] (padding/count byte)
* 4. A8 = 0x03 (execute)
* 5. Poll A8 for completion
* 6. A6 = 0x00 (re-select page 0 for readback)
* 7. Read 16 bytes from A7 FIFO into sig_block[]
* 8. Validate: sig_block[0] must be 0x81 (BCM4500 chip signature)
*
* Result layout (16 bytes):
* [0] = 0x81 (signature)
* [1] = sub_addr echo
* [2..5] = reserved/internal
* [6..7] = AGC2 (big-endian)
* [8..9] = AGC1 (big-endian)
* [10..11] = SNR (big-endian)
* [12..15] = reserved
*
* Stock firmware copies bytes 6-11 into EP0BUF[5..0] (reversed).
* Sub_addr: 0x10 for DVB-S/DSS/BPSK, 0x11 for turbo/DigiCipher (mod >= 4).
*/
static BOOL bcm_read_signal_block(void) {
BYTE sub_addr = (current_mod_index >= 4) ? 0x11 : 0x10;
/* A6 = page 0 */
if (!bcm_direct_write(BCM_REG_PAGE, 0x00))
return FALSE;
/* A7 = [0x01, sub_addr] — indirect block address */
i2c_rd[0] = 0x01;
i2c_rd[1] = sub_addr;
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, 2, i2c_rd))
return FALSE;
/* A7 = [0x00] — padding byte */
i2c_rd[0] = 0x00;
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, 1, i2c_rd))
return FALSE;
/* A8 = 0x03 — execute command */
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE))
return FALSE;
/* Poll for completion */
if (!bcm_poll_ready())
return FALSE;
/* Re-select page 0 for readback */
if (!bcm_direct_write(BCM_REG_PAGE, 0x00))
return FALSE;
/* Read 16 bytes from A7 FIFO */
if (!i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 16, sig_block))
return FALSE;
/* Validate BCM4500 chip signature */
if (sig_block[0] != 0x81)
return FALSE;
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;
/* Pre-write readiness check — stock firmware 0x0DE9 calls wait_for_ready()
* (0x2000) before each block. This polls A8 + additional conditions with
* up to 10 retries. Our simplified version polls A8 only. */
if (!bcm_poll_ready())
return FALSE;
/* 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 */
if (!bcm_poll_ready())
return FALSE;
/* Readback verification — stock firmware 0x0DDD does this after every
* init block write. The BCM4500 may require the readback to finalize
* the write (two-phase commit), or this catches silent write failures.
*
* Protocol (from disassembly at 0x0E61-0x0EE2):
* 1. Re-select page 0 (A6 = 0x00)
* 2. Read back len bytes from A7 into scratch buffer
* 3. First readback byte has bit 7 set by BCM4500 — XOR to clear
* 4. Compare all bytes against original data
*/
if (!bcm_direct_write(BCM_REG_PAGE, 0x00))
return FALSE;
if (!i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, len, i2c_rd))
return FALSE;
/* BCM4500 sets bit 7 of the block address byte in readback */
i2c_rd[0] ^= 0x80;
for (i = 0; i < len; i++) {
if (i2c_rd[i] != data[i])
return FALSE;
}
return TRUE;
}
/*
* Write tune-preparation blocks to BCM4500.
*
* Reverse-engineered from stock firmware FUN_CODE_0ee9 (0x0EE9), called from
* the tune handler at 0x099A with up to 3 retries. These blocks zero the
* filter coefficients while keeping the structural/address bytes intact,
* preparing the demodulator for new tuning parameters.
*
* Block 2 is identical for boot and tune, so we reuse bcm_boot_block2.
*/
static BOOL bcm_write_tune_blocks(void) {
if (!bcm_write_init_block(bcm_tune_block0, BCM_TUNE_BLOCK0_LEN))
return FALSE;
if (!bcm_write_init_block(bcm_tune_block1, BCM_TUNE_BLOCK1_LEN))
return FALSE;
if (!bcm_write_init_block(bcm_boot_block2, BCM_BOOT_BLOCK2_LEN))
return FALSE;
return TRUE;
}
/*
* 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 */
/* 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_boot_block0, BCM_BOOT_BLOCK0_LEN))
return FALSE;
boot_stage = 6;
if (!bcm_write_init_block(bcm_boot_block1, BCM_BOOT_BLOCK1_LEN))
return FALSE;
boot_stage = 7;
if (!bcm_write_init_block(bcm_boot_block2, BCM_BOOT_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 bit-bang I2C */
for (hp_a = 1; hp_a < 0x78; hp_a++) {
if (bb_i2c_probe(hp_a)) {
hp_byte = hp_a >> 3;
hp_bit = hp_a & 0x07;
hp_curr[hp_byte] |= (1 << hp_bit);
}
}
/* 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;
}
/* ---------- 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];
current_mod_index = EP0BUF[8];
/* 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;
}
/* Write tune-preparation blocks (stock: 0x0EE9, clears filter coefficients).
* Called before every retune — the stock firmware retries up to 3x. */
bcm_write_tune_blocks();
/* 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 signal from BCM4500
* wValue=0: normal mode — 6 bytes (stock compatible)
* wValue=0xFF: debug mode — 16-byte raw sig_block + 1 status byte (17 total)
* Uses the correct stock protocol: load indirect address into A7 FIFO,
* execute with A8=0x03, read 16-byte result block, extract bytes 6-11. */
case GET_SIGNAL_STRENGTH:
if (wval == 0xFF) {
/* Debug: return raw 16-byte sig_block + success flag */
BYTE sig_ok;
BYTE si;
sig_ok = bcm_read_signal_block() ? 1 : 0;
for (si = 0; si < 16; si++)
EP0BUF[si] = sig_block[si];
EP0BUF[16] = sig_ok;
EP0BCH = 0;
EP0BCL = 17;
return TRUE;
}
if (wval == 0xFE) {
/* Diagnostic: stock-style init block write + readback.
* Uses sig_block[] (xdata) as scratch to avoid DSEG overflow.
* Returns 16 bytes in sig_block:
* [0..6] = A7 readback (7 bytes, should match block0 with bit7 toggle)
* [7] = A8 final poll value (0x02 = completed)
* [8] = A8 poll iteration count (0-10)
* [9] = A2 after write
* [10] = A4 after write
* [11] = A6 after write
* [12] = I2C readback result (1=ok, 0=fail)
* [13] = A8 before start
* [14] = A2 before start
* [15] = I2C write result (1=ok, 0=fail) */
BYTE di;
/* First: verify bcm_direct_write works by toggling A0 */
bcm_direct_read(0xA0, &sig_block[13]); /* should be 0x00 */
bcm_direct_write(0xA0, 0x01); /* enter config mode */
bcm_direct_read(0xA0, &sig_block[14]); /* should be 0x01 if write works */
bcm_direct_write(0xA0, 0x00); /* exit config mode */
/* Write page 0 */
bcm_direct_write(BCM_REG_PAGE, 0x00);
/* Write boot block 0 to A7 — ONE BYTE AT A TIME
* to test if multi-byte writes auto-increment register address
* (would clobber A8/A9/...) instead of using FIFO mode. */
sig_block[15] = 1;
for (di = 0; di < BCM_BOOT_BLOCK0_LEN; di++) {
if (!bcm_direct_write(BCM_REG_DATA, bcm_boot_block0[di])) {
sig_block[15] = 0;
break;
}
}
/* Trailing zero + commit */
bcm_direct_write(BCM_REG_DATA, 0x00);
bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE);
/* Poll A8 */
sig_block[7] = 0xFF;
sig_block[8] = 10;
for (di = 0; di < 10; di++) {
delay(2);
bcm_direct_read(BCM_REG_CMD, &sig_block[7]);
if (!(sig_block[7] & 0x01)) {
sig_block[8] = di;
break;
}
}
/* Post-state */
bcm_direct_read(BCM_REG_STATUS, &sig_block[9]);
bcm_direct_read(BCM_REG_LOCK, &sig_block[10]);
bcm_direct_read(BCM_REG_PAGE, &sig_block[11]);
/* Readback from A7 */
bcm_direct_write(BCM_REG_PAGE, 0x00);
sig_block[12] = i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA,
BCM_BOOT_BLOCK0_LEN, sig_block) ? 1 : 0;
for (di = 0; di < 16; di++)
EP0BUF[di] = sig_block[di];
EP0BCH = 0;
EP0BCL = 16;
return TRUE;
}
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;
}
EP0BUF[0] = 0; EP0BUF[1] = 0; EP0BUF[2] = 0;
EP0BUF[3] = 0; EP0BUF[4] = 0; EP0BUF[5] = 0;
if (bcm_read_signal_block()) {
EP0BUF[0] = sig_block[11];
EP0BUF[1] = sig_block[10];
EP0BUF[2] = sig_block[9];
EP0BUF[3] = sig_block[8];
EP0BUF[4] = sig_block[7];
EP0BUF[5] = sig_block[6];
}
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;
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_boot_block0, BCM_BOOT_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++) {
if (bb_i2c_probe(a)) {
byte_idx = a >> 3;
bit = a & 0x07;
EP0BUF[byte_idx] |= (1 << bit);
}
}
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.
* Uses bcm_read_signal_block() for correct stock-compatible protocol. */
case SIGNAL_MONITOR: {
BYTE sm_val;
EP0BUF[0] = 0; EP0BUF[1] = 0; EP0BUF[2] = 0;
EP0BUF[3] = 0; EP0BUF[4] = 0; EP0BUF[5] = 0;
if (bcm_read_signal_block()) {
/* Same byte order as GET_SIGNAL_STRENGTH (stock compatible) */
EP0BUF[0] = sig_block[11]; /* SNR low */
EP0BUF[1] = sig_block[10]; /* SNR high */
EP0BUF[2] = sig_block[9]; /* AGC1 low */
EP0BUF[3] = sig_block[8]; /* AGC1 high */
EP0BUF[4] = sig_block[7]; /* AGC2 low */
EP0BUF[5] = sig_block[6]; /* AGC2 high */
}
/* 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 via stock-compatible block protocol */
tm_result[0] = 0; tm_result[1] = 0;
tm_result[2] = 0; tm_result[3] = 0;
tm_result[4] = 0; tm_result[5] = 0;
if (bcm_read_signal_block()) {
tm_result[0] = sig_block[11]; /* SNR low */
tm_result[1] = sig_block[10]; /* SNR high */
tm_result[2] = sig_block[9]; /* AGC1 low */
tm_result[3] = sig_block[8]; /* AGC1 high */
tm_result[4] = sig_block[7]; /* AGC2 low */
tm_result[5] = sig_block[6]; /* AGC2 high */
}
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 via bit-bang I2C */
bb_i2c_start();
if (bb_i2c_write_byte(0xA2)) goto ee_fail;
if (bb_i2c_write_byte((BYTE)(wval >> 8))) goto ee_fail;
if (bb_i2c_write_byte((BYTE)(wval))) goto ee_fail;
/* Repeated START → read data */
bb_i2c_start();
if (bb_i2c_write_byte(0xA3)) goto ee_fail;
for (ei = 0; ei < elen; ei++)
EP0BUF[ei] = bb_i2c_read_byte(ei < elen - 1 ? 1 : 0);
bb_i2c_stop();
EP0BCH = 0;
EP0BCL = elen;
return TRUE;
ee_fail:
bb_i2c_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 26 bytes: pll_diag[0..23] + boot_stage + last_error */
case GET_PLL_DIAG: {
BYTE di;
for (di = 0; di < 24; di++)
EP0BUF[di] = pll_diag[di];
EP0BUF[24] = boot_stage;
EP0BUF[25] = last_error;
EP0BCH = 0;
EP0BCL = 26;
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: I2C_HW_DEBUG -- raw I2C controller diagnostic.
* wValue = target 7-bit addr (0=use 0x51 EEPROM).
* Returns 8 bytes: I2CS at each step of a manual transaction. */
case 0xC2: {
/* Live register snapshot — same layout as pll_diag cold-start.
* Compare with 0xBF pll_diag to see if values changed since boot. */
EP0BUF[0] = I2CS; /* 0xE678 */
EP0BUF[1] = I2CTL; /* 0xE67A */
EP0BUF[2] = CPUCS; /* 0xE600 */
EP0BUF[3] = IFCONFIG; /* 0xE601 */
EP0BUF[4] = REVID; /* 0xE60A */
EP0BUF[5] = USBCS; /* 0xE680 */
EP0BUF[6] = PORTACFG; /* 0xE670 */
EP0BUF[7] = REVCTL; /* 0xE60B */
EP0BCH = 0;
EP0BCL = 8;
return TRUE;
}
/* 0xC3: I2C_BUS_TEST -- bit-level I2C bus diagnostic.
* Performs a manual I2C probe of 0x51 (EEPROM), capturing IOA at every
* step. Returns 32 bytes: IOA snapshots through the full transaction.
*
* Layout:
* [0] OEA before test
* [1] IOA idle (before START)
* [2] IOA after SDA LOW (START setup)
* [3] IOA after SCL LOW (START complete)
* [4..11] IOA after each data bit SCL HIGH (8 bits of 0xA2)
* [12..19] IOA after each data bit SCL LOW
* [20] IOA after SDA release for ACK
* [21] IOA after ACK SCL HIGH (THIS IS THE MONEY BIT)
* [22] IOA after ACK SCL LOW
* [23] IOA after STOP (SDA rises while SCL HIGH)
* [24] OEA after test
* [25] SDA toggle test: drive LOW, read back
* [26] SDA toggle test: charge HIGH, read back
* [27] SDA toggle test: release (input), read back after 100us
* [28-31] reserved
*/
case 0xC3: {
BYTE i, val, cap_idx;
cap_idx = 0;
EP0BUF[cap_idx++] = OEA; /* [0] OEA */
/* SDA toggle test first — verify GPIO actually controls the bus */
BB_SDA_LOW(); /* drive SDA LOW */
bb_delay();
EP0BUF[25] = IOA & 0x03; /* should show SDA=L SCL=H */
BB_SDA_HIGH(); /* charge SDA HIGH then release */
bb_delay();
EP0BUF[26] = IOA & 0x03; /* should show SDA=H SCL=H */
/* Wait 100us with SDA released to see if it holds HIGH */
{ BYTE d; for (d = 0; d < 6; d++) bb_delay(); }
EP0BUF[27] = IOA & 0x03; /* SDA holding? */
/* Now do a real I2C transaction to EEPROM 0x51 */
/* Idle state */
BB_SDA_HIGH();
BB_SCL_HIGH();
bb_delay();
EP0BUF[cap_idx++] = IOA & 0x03; /* [1] idle state */
/* START: SDA falls while SCL HIGH */
BB_SDA_LOW();
bb_delay();
EP0BUF[cap_idx++] = IOA & 0x03; /* [2] after SDA LOW */
BB_SCL_LOW();
bb_delay();
EP0BUF[cap_idx++] = IOA & 0x03; /* [3] START complete */
/* Clock out address byte 0xA2 (EEPROM 0x51 write) */
val = 0xA2;
for (i = 0; i < 8; i++) {
if (val & 0x80) BB_SDA_HIGH(); else BB_SDA_LOW();
val <<= 1;
BB_SCL_HIGH();
bb_delay();
EP0BUF[4 + i] = IOA & 0x03; /* [4..11] SDA+SCL during HIGH */
BB_SCL_LOW();
bb_delay();
EP0BUF[12 + i] = IOA & 0x03; /* [12..19] after SCL LOW */
}
/* ACK cycle */
BB_SDA_HIGH(); /* release SDA for slave ACK */
bb_delay();
EP0BUF[20] = IOA & 0x03; /* [20] SDA after release */
BB_SCL_HIGH(); /* 9th clock */
bb_delay();
EP0BUF[21] = IOA & 0x03; /* [21] ACK sample: SDA=L=ACK */
BB_SCL_LOW();
bb_delay();
EP0BUF[22] = IOA & 0x03; /* [22] after ACK clock */
/* STOP */
BB_SDA_LOW();
bb_delay();
BB_SCL_HIGH();
bb_delay();
BB_SDA_HIGH();
bb_delay();
EP0BUF[23] = IOA & 0x03; /* [23] after STOP */
EP0BUF[24] = OEA; /* [24] OEA */
EP0BUF[28] = 0;
EP0BUF[29] = 0;
EP0BUF[30] = 0;
EP0BUF[31] = 0;
EP0BCH = 0;
EP0BCL = 32;
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) {
/* Experiment 0xDB: EEPROM boot with hardware I2C.
*
* Previous experiments (0xD8-0xDA) proved that the CPUCS halt/restart
* cycle (used by USB RAM loading) permanently corrupts the FX2 I2C
* controller with unclearable BERR (I2CS=0xF6). Both hardware I2C
* and bit-bang are blocked after USB load (NMOS latching on SDA).
*
* The EEPROM boot path avoids CPUCS halt/restart entirely: the boot
* ROM loads firmware from EEPROM via I2C, then jumps to it. The CPU
* never halts, so the I2C controller stays clean.
*
* This experiment detects the boot method and uses the appropriate
* I2C strategy:
* - EEPROM boot (I2CS clean): hardware I2C controller
* - USB RAM boot (I2CS=BERR): bit-bang I2C (fallback)
*
* Layout (24 bytes):
* [0] = 0xDB marker
* [1] = I2CS at boot (expect 0x00 from EEPROM, 0xF6 from USB)
* [2] = I2CTL at boot
* [3] = PORTACFG at boot (before we touch it)
* [4] = IOA & 0x03 at boot (SDA/SCL pin states)
* [5] = hw_i2c_probe(0x51) EEPROM
* [6] = hw_i2c_probe(0x10) BCM4500 via BCM3440
* [7] = hw_i2c_probe(0x08) BCM4500 direct
* [8] = hw_i2c_probe(0x50) alt EEPROM
* [9] = I2CS after hw probes
* [10] = hw bus scan count (0x08-0x77), 0xFF if BERR
* [11] = hw_i2c_read(0x10, 0xA2, 1) result: BCM4500 status reg
* [12] = hw_i2c_read success flag (1=ok, 0=fail)
* [13] = hw_i2c_read(0x10, 0xA4, 1): BCM4500 lock register
* [14] = I2CS after all hw tests
* [15] = boot method: 0xEE=EEPROM (clean I2C), 0xBB=USB (BERR)
* [16-21] = reserved
* [22] = PORTACFG final
* [23] = IOA & 0x03 final
*/
/* Capture boot-time register state BEFORE we touch anything */
pll_diag[0] = 0xDB;
pll_diag[1] = I2CS;
pll_diag[2] = I2CTL;
pll_diag[3] = PORTACFG;
pll_diag[4] = IOA & 0x03;
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();
/* --- GPIO setup ---
* PORTACFG = 0x00: GPIO mode for PA0 (SDA) and PA1 (SCL).
* When I2C controller is clean (EEPROM boot): NMOS controlled by
* I2C state machine, don't touch PA0/PA1 GPIO.
* When I2C controller has BERR (USB boot): NMOS frozen, use
* bit-bang via GPIO. */
PORTACFG = 0x00;
I2CTL = 0x01; /* 400kHz I2C */
SYNCDELAY;
/* Configure non-I2C GPIO pins */
OEA |= (PIN_PWR_EN | PIN_PWR_DIS | PIN_22KHZ | PIN_LNB_VOLT |
PIN_BCM_RESET | PIN_DISEQC);
IOA = PIN_DISEQC | PIN_BCM_RESET | PIN_PWR_EN; /* 0xA2 */
IOD = 0xE1;
/* Detect boot method from I2CS state */
if (!(I2CS & bmBERR)) {
/* EEPROM boot: I2C controller is clean! Use hardware I2C. */
pll_diag[15] = 0xEE;
delay(5); /* ~15ms for power-up */
/* Hardware I2C probes */
pll_diag[5] = hw_i2c_probe(0x51); /* EEPROM */
pll_diag[6] = hw_i2c_probe(0x10); /* BCM4500 via BCM3440 */
pll_diag[7] = hw_i2c_probe(0x08); /* BCM4500 direct */
pll_diag[8] = hw_i2c_probe(0x50); /* alt EEPROM addr */
pll_diag[9] = I2CS;
/* Hardware I2C bus scan */
{
BYTE a, cnt = 0;
for (a = 0x08; a < 0x78; a++) {
if (hw_i2c_probe(a) == 1)
cnt++;
}
pll_diag[10] = cnt;
}
/* Read BCM4500 status register via BCM3440 gateway */
if (hw_i2c_read(BCM4500_ADDR, BCM_REG_STATUS, 1, i2c_rd)) {
pll_diag[11] = i2c_rd[0];
pll_diag[12] = 1; /* success */
} else {
pll_diag[11] = 0xFF;
pll_diag[12] = 0; /* fail */
}
/* Read BCM4500 lock register */
if (hw_i2c_read(BCM4500_ADDR, BCM_REG_LOCK, 1, i2c_rd)) {
pll_diag[13] = i2c_rd[0];
} else {
pll_diag[13] = 0xFF;
}
pll_diag[14] = I2CS;
} else {
/* USB RAM boot: BERR present. Use bit-bang I2C (fallback). */
pll_diag[15] = 0xBB;
/* Set up bit-bang GPIO: PA0 latch LOW for SDA open-drain */
PA0 = 0;
PA1 = 1;
OEA &= ~0x01; /* SDA input (floating HIGH via pull-up) */
delay(5); /* ~15ms for power-up */
/* Bit-bang I2C probes */
pll_diag[5] = bb_i2c_probe(0x51);
pll_diag[6] = bb_i2c_probe(0x10);
pll_diag[7] = bb_i2c_probe(0x08);
pll_diag[8] = bb_i2c_probe(0x50);
pll_diag[9] = I2CS;
pll_diag[10] = 0xFF; /* bus scan skipped for bit-bang */
/* Try bit-bang register read */
if (i2c_combined_read(BCM4500_ADDR, BCM_REG_STATUS, 1, i2c_rd)) {
pll_diag[11] = i2c_rd[0];
pll_diag[12] = 1;
} else {
pll_diag[11] = 0xFF;
pll_diag[12] = 0;
}
pll_diag[13] = 0xFF;
pll_diag[14] = I2CS;
}
pll_diag[22] = PORTACFG;
pll_diag[23] = IOA & 0x03;
/* 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.
* Stock firmware uses 0xCA (no IFCLKOE, no GSTATE output).
* IFCLKOE=1 drives 48MHz on the IFCLK pin — if that pin is routed
* to a BCM chip, it could interfere with normal operation.
* GSTATE=1 puts GPIF debug signals on Port E which may also conflict.
* Match stock: keep IFCLK tristate, Port E normal. */
IFCONFIG = 0xCA;
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();
}
}
}
}