Stock firmware wait_for_ready() at 0x2000 checks three conditions before
each init block write — all must be simultaneously true:
1. A2.bit3 = 1 (BCM4500 DSP processing complete)
2. A8.bit0 = 0, A8.bit1 = 1 (command register ready)
3. A4.bit7 = 1 (init pipeline flushed)
Our bcm_poll_ready() only checked A8.bit0, and was used for both
pre-write and post-write checks. Now:
- bcm_wait_ready(): strict 3-condition check for PRE-write
(with fallback to simple A8 check for gateway quirks)
- bcm_poll_ready(): simple A8 check for POST-write
(matches stock firmware's poll_ready() at 0x20C5)
Code size: 15,155 / 15,360 bytes (205 bytes spare)
3274 lines
107 KiB
C
3274 lines
107 KiB
C
/*
|
||
* 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.
|
||
* Matches stock firmware's poll_ready() at 0x20C5: checks A8.bit0 = 0.
|
||
* Used AFTER init block writes / indirect commands.
|
||
*
|
||
* Treats timeout as success with settling delay — the gateway A8 register
|
||
* doesn't always reflect the BCM4500's internal ready state, but commands
|
||
* do complete internally.
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/*
|
||
* Comprehensive BCM4500 readiness check — used BEFORE init block writes.
|
||
*
|
||
* Replicates stock firmware wait_for_ready() at 0x2000, which checks
|
||
* THREE conditions that must ALL be true simultaneously:
|
||
*
|
||
* 1. A2.bit3 = 1 (BCM4500 status register — DSP processing complete)
|
||
* Stock firmware: condition_1() at 0x2314
|
||
*
|
||
* 2. A8.bit0 = 0 (command register not busy)
|
||
* A8.bit1 = 1 (command result ready)
|
||
* Stock firmware: condition_2() at 0x2258
|
||
*
|
||
* 3. A4.bit7 = 1 (lock/status register — init pipeline flushed)
|
||
* Stock firmware: condition_3() at 0x235B
|
||
*
|
||
* This is STRICTER than bcm_poll_ready() which only checks A8.bit0.
|
||
* The stock firmware uses this strict check before writing each init block,
|
||
* ensuring the BCM4500 has fully processed the previous block before
|
||
* accepting a new one.
|
||
*
|
||
* Retries up to 10 times with delays. Returns FALSE on timeout.
|
||
*/
|
||
static BOOL bcm_wait_ready(void) {
|
||
BYTE i, val;
|
||
for (i = 0; i < 10; i++) {
|
||
/* Condition 1: A2.bit3 — DSP status */
|
||
if (!bcm_direct_read(BCM_REG_STATUS, &val))
|
||
return FALSE;
|
||
if (val & 0x08) {
|
||
/* Condition 2: A8.bit0=0, A8.bit1=1 — command ready */
|
||
if (!bcm_direct_read(BCM_REG_CMD, &val))
|
||
return FALSE;
|
||
if (!(val & 0x01) && (val & 0x02)) {
|
||
/* Condition 3: A4.bit7 — pipeline flushed */
|
||
if (!bcm_direct_read(BCM_REG_LOCK, &val))
|
||
return FALSE;
|
||
if (val & 0x80)
|
||
return TRUE; /* All 3 conditions met */
|
||
}
|
||
}
|
||
delay(10);
|
||
}
|
||
/* Fallback: if comprehensive check times out but A8 reports not-busy,
|
||
* proceed anyway — handles case where A2/A4 conditions don't reflect
|
||
* properly through the BCM3440 I2C gateway. */
|
||
if (bcm_direct_read(BCM_REG_CMD, &val) && !(val & 0x01)) {
|
||
delay(5);
|
||
return TRUE;
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
/*
|
||
* 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 checks 3 conditions: A2.bit3, A8.bit0+1,
|
||
* A4.bit7. All must be true before the BCM4500 accepts a new init block. */
|
||
if (!bcm_wait_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();
|
||
}
|
||
}
|
||
}
|
||
}
|