/* * 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 #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[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; /* 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(); } /* * 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(); } } } }