Phase D firmware hardening: vendor commands 0xBD (streaming diagnostics) and 0xBE (I2C hot-plug detection) with Python library, bridge, and demo support. All I2C operations use timeout-protected helpers, BCM4500 reads are rate-limited during streaming, and frame counter reads use atomic read-verify-reread pattern. Counters saturate instead of wrapping.
2086 lines
64 KiB
C
2086 lines
64 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>
|
|
#include <i2c.h>
|
|
|
|
#define SYNCDELAY SYNCDELAY4
|
|
|
|
/* BCM4500 I2C address (7-bit); 8-bit wire address is 0x10/0x11 */
|
|
#define BCM4500_ADDR 0x08
|
|
|
|
/* BCM4500 indirect register protocol registers */
|
|
#define BCM_REG_PAGE 0xA6
|
|
#define BCM_REG_DATA 0xA7
|
|
#define BCM_REG_CMD 0xA8
|
|
|
|
/* BCM4500 status registers */
|
|
#define BCM_REG_STATUS 0xA2
|
|
#define BCM_REG_LOCK 0xA4
|
|
|
|
/* BCM commands */
|
|
#define BCM_CMD_READ 0x01
|
|
#define BCM_CMD_WRITE 0x03
|
|
|
|
/* vendor command IDs */
|
|
#define GET_8PSK_CONFIG 0x80
|
|
#define TUNE_8PSK 0x86
|
|
#define GET_SIGNAL_STRENGTH 0x87
|
|
#define BOOT_8PSK 0x89
|
|
#define START_INTERSIL 0x8A
|
|
#define SET_LNB_VOLTAGE 0x8B
|
|
#define SET_22KHZ_TONE 0x8C
|
|
#define SEND_DISEQC 0x8D
|
|
#define ARM_TRANSFER 0x85
|
|
#define GET_SIGNAL_LOCK 0x90
|
|
#define GET_FW_VERS 0x92
|
|
#define USE_EXTRA_VOLT 0x94
|
|
|
|
/* custom vendor commands */
|
|
#define SPECTRUM_SWEEP 0xB0
|
|
#define RAW_DEMOD_READ 0xB1
|
|
#define RAW_DEMOD_WRITE 0xB2
|
|
#define BLIND_SCAN 0xB3
|
|
#define SIGNAL_MONITOR 0xB7
|
|
#define TUNE_MONITOR 0xB8
|
|
#define MULTI_REG_READ 0xB9
|
|
#define PARAM_SWEEP 0xBA
|
|
#define ADAPTIVE_BLIND_SCAN 0xBB
|
|
#define GET_LAST_ERROR 0xBC
|
|
#define GET_STREAM_DIAG 0xBD
|
|
#define GET_HOTPLUG_STATUS 0xBE
|
|
|
|
/* error codes (set by I2C helpers, read via 0xBC) */
|
|
#define ERR_OK 0x00
|
|
#define ERR_I2C_TIMEOUT 0x01
|
|
#define ERR_I2C_NAK 0x02
|
|
#define ERR_I2C_ARB_LOST 0x03
|
|
#define ERR_BCM_NOT_READY 0x04
|
|
#define ERR_BCM_TIMEOUT 0x05
|
|
|
|
/* configuration status byte bits */
|
|
#define BM_STARTED 0x01
|
|
#define BM_FW_LOADED 0x02
|
|
#define BM_INTERSIL 0x04
|
|
#define BM_DVB_MODE 0x08
|
|
#define BM_22KHZ 0x10
|
|
#define BM_SEL18V 0x20
|
|
#define BM_DC_TUNED 0x40
|
|
#define BM_ARMED 0x80
|
|
|
|
/* GPIO pin definitions for v2.06 hardware */
|
|
#define PIN_PWR_EN 0x02 /* P0.1 -- power supply enable */
|
|
#define PIN_PWR_DIS 0x04 /* P0.2 -- power supply disable */
|
|
#define PIN_22KHZ 0x08 /* P0.3 */
|
|
#define PIN_LNB_VOLT 0x10 /* P0.4 */
|
|
#define PIN_BCM_RESET 0x20 /* P0.5 -- BCM4500 hardware reset (active LOW) */
|
|
#define PIN_DISEQC 0x80 /* P0.7 */
|
|
|
|
/* configuration status byte -- stored in ordinary variable */
|
|
static volatile BYTE config_status;
|
|
|
|
/* boot progress tracker for diagnostics (0=not started, 1-6=step, 0xFF=done) */
|
|
static volatile BYTE boot_stage;
|
|
|
|
/* ISR flag */
|
|
volatile __bit got_sud;
|
|
|
|
/* I2C scratch buffers in xdata */
|
|
static __xdata BYTE i2c_buf[16];
|
|
static __xdata BYTE i2c_rd[8];
|
|
|
|
/* TUNE_MONITOR result buffer: filled by OUT phase, returned by IN phase */
|
|
static __xdata BYTE tm_result[10];
|
|
|
|
/* DiSEqC message buffer (3-6 bytes) for full message transmission */
|
|
static __xdata BYTE diseqc_msg[6];
|
|
|
|
/* last error code for diagnostic reads via 0xBC */
|
|
static __xdata BYTE last_error;
|
|
|
|
/* Shared scratch buffer for vendor command case blocks (saves DSEG) */
|
|
static __xdata BYTE vc_diag[8];
|
|
|
|
/* I2C hot-plug detection: previous and current bus scan bitmaps (16 bytes each) */
|
|
static __xdata BYTE hp_prev[16]; /* last completed scan */
|
|
static __xdata BYTE hp_curr[16]; /* working scan buffer */
|
|
static __xdata WORD hp_changes; /* cumulative device change events */
|
|
static __xdata BYTE hp_added; /* devices added in last scan */
|
|
static __xdata BYTE hp_removed; /* devices removed in last scan */
|
|
static __xdata BYTE hp_scan_ok; /* 1 after first scan completes */
|
|
|
|
/* Streaming diagnostics counters */
|
|
static __xdata DWORD sd_poll_count; /* main-loop poll cycles while armed */
|
|
static __xdata WORD sd_overflow_count; /* EP2 FULL events detected */
|
|
static __xdata WORD sd_sync_loss; /* BCM4500 transport sync losses */
|
|
static __xdata BYTE sd_last_status; /* last BCM4500 status register */
|
|
static __xdata BYTE sd_last_lock; /* last BCM4500 lock register */
|
|
static __xdata BYTE sd_had_sync; /* had sync in previous poll */
|
|
|
|
/* Main loop timing: USB frame counter for periodic tasks */
|
|
static __xdata WORD hp_last_frame; /* frame counter at last I2C scan */
|
|
|
|
/*
|
|
* BCM4500 register initialization data extracted from stock v2.06 firmware.
|
|
* FUN_CODE_0ddd writes these 3 blocks to BCM4500 indirect registers (page 0)
|
|
* via the A6/A7/A8 control interface during BOOT_8PSK.
|
|
*/
|
|
static const __code BYTE bcm_init_block0[] = {
|
|
0x06, 0x0b, 0x17, 0x38, 0x9f, 0xd9, 0x80
|
|
};
|
|
static const __code BYTE bcm_init_block1[] = {
|
|
0x07, 0x09, 0x39, 0x4f, 0x00, 0x65, 0xb7, 0x10
|
|
};
|
|
static const __code BYTE bcm_init_block2[] = {
|
|
0x0f, 0x0c, 0x09
|
|
};
|
|
#define BCM_INIT_BLOCK0_LEN 7
|
|
#define BCM_INIT_BLOCK1_LEN 8
|
|
#define BCM_INIT_BLOCK2_LEN 3
|
|
|
|
/* ---------- BCM4500 I2C helpers ---------- */
|
|
|
|
/*
|
|
* I2C timeout: ~5ms at 48MHz CPU clock (4 clocks/cycle, ~12 MIPS).
|
|
* At 400kHz I2C, one byte = 22.5us; 5ms gives >200x margin.
|
|
* The FX2 I2C controller has no hardware timeout -- if a slave holds
|
|
* SCL low (clock stretching), the master waits forever without this.
|
|
*/
|
|
#define I2C_TIMEOUT 6000
|
|
|
|
static BOOL i2c_wait_done(void) {
|
|
WORD timeout = I2C_TIMEOUT;
|
|
while (!(I2CS & bmDONE)) {
|
|
if (--timeout == 0) {
|
|
last_error = ERR_I2C_TIMEOUT;
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL i2c_wait_stop(void) {
|
|
WORD timeout = I2C_TIMEOUT;
|
|
while (I2CS & bmSTOP) {
|
|
if (--timeout == 0)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Combined I2C write-read with repeated START (no STOP between
|
|
* write and read phases). Many I2C devices including the BCM4500
|
|
* require this pattern instead of separate write+stop/read+stop.
|
|
*
|
|
* Sequence: START -> addr+W -> reg -> RESTART -> addr+R -> data -> STOP
|
|
*/
|
|
static BOOL i2c_combined_read(BYTE addr, BYTE reg, BYTE len, BYTE *buf) {
|
|
BYTE i;
|
|
BYTE tmp;
|
|
|
|
/* START + write address */
|
|
I2CS |= bmSTART;
|
|
I2DAT = addr << 1;
|
|
if (!i2c_wait_done())
|
|
goto fail;
|
|
if (!(I2CS & bmACK)) {
|
|
last_error = ERR_I2C_NAK;
|
|
goto fail;
|
|
}
|
|
|
|
/* Write register address */
|
|
I2DAT = reg;
|
|
if (!i2c_wait_done())
|
|
goto fail;
|
|
if (!(I2CS & bmACK)) {
|
|
last_error = ERR_I2C_NAK;
|
|
goto fail;
|
|
}
|
|
|
|
/* REPEATED START + read address */
|
|
I2CS |= bmSTART;
|
|
I2DAT = (addr << 1) | 1;
|
|
if (!i2c_wait_done())
|
|
goto fail;
|
|
if (!(I2CS & bmACK)) {
|
|
last_error = ERR_I2C_NAK;
|
|
goto fail;
|
|
}
|
|
|
|
/* For single byte, set LASTRD before dummy read */
|
|
if (len == 1)
|
|
I2CS |= bmLASTRD;
|
|
|
|
/* Dummy read to trigger first clock burst */
|
|
tmp = I2DAT;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (!i2c_wait_done())
|
|
goto fail;
|
|
if (i == len - 2)
|
|
I2CS |= bmLASTRD;
|
|
if (i == len - 1)
|
|
I2CS |= bmSTOP;
|
|
buf[i] = I2DAT;
|
|
}
|
|
|
|
i2c_wait_stop();
|
|
return TRUE;
|
|
|
|
fail:
|
|
I2CS |= bmSTOP;
|
|
i2c_wait_stop();
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* I2C write with timeout -- writes addr+reg+data without using fx2lib,
|
|
* so we have full control over timeout behavior.
|
|
* Sends: START -> (addr<<1) -> reg -> data -> STOP
|
|
*/
|
|
static BOOL i2c_write_timeout(BYTE addr, BYTE reg, BYTE val) {
|
|
/* START + write address */
|
|
I2CS |= bmSTART;
|
|
I2DAT = addr << 1;
|
|
if (!i2c_wait_done()) goto fail;
|
|
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
|
|
|
|
/* Register address */
|
|
I2DAT = reg;
|
|
if (!i2c_wait_done()) goto fail;
|
|
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
|
|
|
|
/* Data byte */
|
|
I2DAT = val;
|
|
if (!i2c_wait_done()) goto fail;
|
|
|
|
/* STOP */
|
|
I2CS |= bmSTOP;
|
|
i2c_wait_stop();
|
|
return TRUE;
|
|
|
|
fail:
|
|
I2CS |= bmSTOP;
|
|
i2c_wait_stop();
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Multi-byte I2C write with timeout.
|
|
* Sends: START -> (addr<<1) -> reg -> data[0..len-1] -> STOP
|
|
*/
|
|
static BOOL i2c_write_multi_timeout(BYTE addr, BYTE reg, BYTE len,
|
|
__xdata BYTE *data) {
|
|
BYTE i;
|
|
|
|
I2CS |= bmSTART;
|
|
I2DAT = addr << 1;
|
|
if (!i2c_wait_done()) goto fail;
|
|
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
|
|
|
|
I2DAT = reg;
|
|
if (!i2c_wait_done()) goto fail;
|
|
if (!(I2CS & bmACK)) { last_error = ERR_I2C_NAK; goto fail; }
|
|
|
|
for (i = 0; i < len; i++) {
|
|
I2DAT = data[i];
|
|
if (!i2c_wait_done()) goto fail;
|
|
}
|
|
|
|
I2CS |= bmSTOP;
|
|
i2c_wait_stop();
|
|
return TRUE;
|
|
|
|
fail:
|
|
I2CS |= bmSTOP;
|
|
i2c_wait_stop();
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Write one byte to a BCM4500 direct I2C register (subaddr).
|
|
*/
|
|
static BOOL bcm_direct_write(BYTE reg, BYTE val) {
|
|
return i2c_write_timeout(BCM4500_ADDR, reg, val);
|
|
}
|
|
|
|
/*
|
|
* Read one byte from a BCM4500 direct I2C register using
|
|
* combined write-read with repeated START.
|
|
*/
|
|
static BOOL bcm_direct_read(BYTE reg, BYTE *val) {
|
|
return i2c_combined_read(BCM4500_ADDR, reg, 1, val);
|
|
}
|
|
|
|
/*
|
|
* Write a value to a BCM4500 indirect register.
|
|
* Single multi-byte I2C write to 0xA6 with auto-increment:
|
|
* [0xA6] = page, [0xA7] = data, [0xA8] = 0x03 (write cmd)
|
|
*/
|
|
static BOOL bcm_indirect_write(BYTE reg, BYTE val) {
|
|
i2c_rd[0] = reg;
|
|
i2c_rd[1] = val;
|
|
i2c_rd[2] = BCM_CMD_WRITE;
|
|
return i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_PAGE, 3, i2c_rd);
|
|
}
|
|
|
|
/*
|
|
* Read a value from a BCM4500 indirect register.
|
|
* Protocol: single multi-byte I2C write to 0xA6 with auto-increment:
|
|
* [0xA6] = page, [0xA7] = 0x00, [0xA8] = 0x01 (read cmd)
|
|
* Then read the result from 0xA7.
|
|
*/
|
|
static BOOL bcm_indirect_read(BYTE reg, BYTE *val) {
|
|
/* page, placeholder data, read command -- written to A6,A7,A8 in one shot */
|
|
i2c_rd[0] = reg;
|
|
i2c_rd[1] = 0x00;
|
|
i2c_rd[2] = BCM_CMD_READ;
|
|
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_PAGE, 3, i2c_rd))
|
|
return FALSE;
|
|
delay(1);
|
|
return i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, val);
|
|
}
|
|
|
|
/*
|
|
* Write a multi-byte block to BCM4500 via indirect protocol.
|
|
* Page select, then N data bytes to 0xA7, then commit with 0x03.
|
|
*/
|
|
static BOOL bcm_indirect_write_block(BYTE page, __xdata BYTE *data, BYTE len) {
|
|
if (!bcm_direct_write(BCM_REG_PAGE, page))
|
|
return FALSE;
|
|
|
|
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, len, data))
|
|
return FALSE;
|
|
|
|
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Poll BCM4500 for readiness. Reads status registers and waits
|
|
* for the command register to indicate idle.
|
|
*/
|
|
static BOOL bcm_poll_ready(void) {
|
|
BYTE i, val;
|
|
for (i = 0; i < 10; i++) {
|
|
if (bcm_direct_read(BCM_REG_CMD, &val)) {
|
|
if (!(val & 0x01))
|
|
return TRUE;
|
|
} else {
|
|
return FALSE; /* I2C error, last_error already set */
|
|
}
|
|
delay(2);
|
|
}
|
|
last_error = ERR_BCM_TIMEOUT;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Write one block of initialization data to BCM4500 indirect registers.
|
|
* Replicates FUN_CODE_0ddd's per-iteration I2C sequence from stock firmware:
|
|
* 1. Write 0x00 to reg 0xA6 (page select = page 0)
|
|
* 2. Write data[0..len-1] to reg 0xA7 (data buffer, auto-increment)
|
|
* 3. Write 0x00 to reg 0xA7 (trailing zero -- stock firmware sends this)
|
|
* 4. Write 0x03 to reg 0xA8 (commit indirect write)
|
|
* 5. Wait for BCM4500 to finish processing
|
|
*/
|
|
static BOOL bcm_write_init_block(const __code BYTE *data, BYTE len) {
|
|
BYTE i;
|
|
|
|
/* Page select = 0 */
|
|
if (!bcm_direct_write(BCM_REG_PAGE, 0x00))
|
|
return FALSE;
|
|
|
|
/* Copy block data from code space to xdata scratch buffer */
|
|
for (i = 0; i < len; i++)
|
|
i2c_buf[i] = data[i];
|
|
|
|
/* Write data bytes to 0xA7 */
|
|
if (!i2c_write_multi_timeout(BCM4500_ADDR, BCM_REG_DATA, len, i2c_buf))
|
|
return FALSE;
|
|
|
|
/* Trailing zero to 0xA7 (stock firmware does this as separate write) */
|
|
if (!bcm_direct_write(BCM_REG_DATA, 0x00))
|
|
return FALSE;
|
|
|
|
/* Commit: write command 0x03 to 0xA8 */
|
|
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE))
|
|
return FALSE;
|
|
|
|
/* Wait for BCM4500 to process the write */
|
|
return bcm_poll_ready();
|
|
}
|
|
|
|
/*
|
|
* BCM4500 full boot sequence, reverse-engineered from stock firmware
|
|
* FUN_CODE_1D4F (reset/power) + FUN_CODE_0ddd (register init).
|
|
*
|
|
* GPIO sequence from disassembly:
|
|
* P3 |= 0xE0 -- P3.7, P3.6, P3.5 HIGH (control lines idle)
|
|
* P0 &= ~0x20 -- P0.5 LOW = assert BCM4500 hardware RESET
|
|
* I2C bus reset
|
|
* P0.1 set, P0.2 clr -- power supply enable
|
|
* delay(30) -- wait for power settle
|
|
* P0 |= 0x20 -- P0.5 HIGH = release BCM4500 from RESET
|
|
* Write 3 register initialization blocks
|
|
*/
|
|
static BOOL bcm4500_boot(void) {
|
|
boot_stage = 1; /* Stage 1: GPIO setup */
|
|
|
|
/* Ensure fx2lib I2C functions won't spin forever */
|
|
cancel_i2c_trans = FALSE;
|
|
|
|
/* P3.7, P3.6, P3.5 HIGH (idle state for control lines) */
|
|
IOD |= 0xE0;
|
|
|
|
/* Assert BCM4500 hardware RESET (P0.5 LOW) */
|
|
OEA |= PIN_BCM_RESET;
|
|
IOA &= ~PIN_BCM_RESET;
|
|
|
|
/* NOTE: Do NOT send I2CS bmSTOP here. Sending STOP when no transaction
|
|
* is active corrupts the FX2 I2C controller state, causing subsequent
|
|
* START+ACK detection to fail. The I2C bus will be in a clean state
|
|
* when we reach the probe step -- any prior transaction ended with STOP. */
|
|
|
|
/* Power on: P0.1 HIGH (enable), P0.2 LOW (disable off) */
|
|
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
|
|
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
|
|
|
|
boot_stage = 2; /* Stage 2: power settled, releasing reset */
|
|
|
|
/* Wait for power supply to settle (stock firmware uses 30 iterations) */
|
|
delay(30);
|
|
|
|
/* Release BCM4500 from RESET (P0.5 HIGH) */
|
|
IOA |= PIN_BCM_RESET;
|
|
|
|
/* Wait for BCM4500 internal POR and mask ROM boot to complete */
|
|
delay(50);
|
|
|
|
boot_stage = 3; /* Stage 3: I2C probe */
|
|
|
|
/* Verify BCM4500 is alive on I2C before attempting register init.
|
|
* If we can't read a direct register, the chip didn't come out of reset. */
|
|
if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0]))
|
|
return FALSE;
|
|
|
|
boot_stage = 4; /* Stage 4: register init block 0 */
|
|
|
|
/* Initialize BCM4500 registers -- 3 blocks from stock firmware */
|
|
if (!bcm_write_init_block(bcm_init_block0, BCM_INIT_BLOCK0_LEN))
|
|
return FALSE;
|
|
|
|
boot_stage = 5; /* Stage 5: register init block 1 */
|
|
|
|
if (!bcm_write_init_block(bcm_init_block1, BCM_INIT_BLOCK1_LEN))
|
|
return FALSE;
|
|
|
|
boot_stage = 6; /* Stage 6: register init block 2 */
|
|
|
|
if (!bcm_write_init_block(bcm_init_block2, BCM_INIT_BLOCK2_LEN))
|
|
return FALSE;
|
|
|
|
boot_stage = 0xFF; /* Success */
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* BCM4500 shutdown -- reverse of boot.
|
|
* From stock firmware FUN_CODE_1D4F shutdown path at 0x1D93.
|
|
*/
|
|
static void bcm4500_shutdown(void) {
|
|
/* Power off: P0.1 LOW (enable off), P0.2 HIGH (disable) */
|
|
IOA = (IOA & ~PIN_PWR_EN) | PIN_PWR_DIS;
|
|
}
|
|
|
|
/* ---------- I2C hot-plug detection ---------- */
|
|
|
|
/*
|
|
* Scan all I2C addresses 0x01-0x77, storing result as 16-byte bitmap
|
|
* in hp_curr[]. Then compare with hp_prev[] to detect changes.
|
|
* Must NOT be called while streaming (I2C bus contention with GPIF).
|
|
*/
|
|
static void i2c_hotplug_scan(void) {
|
|
static __xdata BYTE hp_a, hp_byte, hp_bit, hp_diff;
|
|
|
|
/* Save current as previous before starting new scan.
|
|
* This keeps hp_prev valid between scans so the host can read
|
|
* both bitmaps and see the actual transition. */
|
|
for (hp_a = 0; hp_a < 16; hp_a++)
|
|
hp_prev[hp_a] = hp_curr[hp_a];
|
|
|
|
/* Clear current scan buffer */
|
|
for (hp_a = 0; hp_a < 16; hp_a++)
|
|
hp_curr[hp_a] = 0;
|
|
|
|
/* Probe each 7-bit address using timeout-protected I2C ops */
|
|
for (hp_a = 1; hp_a < 0x78; hp_a++) {
|
|
I2CS |= bmSTART;
|
|
I2DAT = hp_a << 1; /* write direction */
|
|
if (!i2c_wait_done())
|
|
goto hp_abort; /* I2C hung — abandon scan */
|
|
if (I2CS & bmACK) {
|
|
hp_byte = hp_a >> 3;
|
|
hp_bit = hp_a & 0x07;
|
|
hp_curr[hp_byte] |= (1 << hp_bit);
|
|
}
|
|
I2CS |= bmSTOP;
|
|
if (!i2c_wait_stop())
|
|
goto hp_abort;
|
|
}
|
|
|
|
/* Compare with previous scan (only after first successful scan) */
|
|
if (hp_scan_ok) {
|
|
hp_added = 0;
|
|
hp_removed = 0;
|
|
for (hp_a = 0; hp_a < 16; hp_a++) {
|
|
hp_diff = hp_curr[hp_a] ^ hp_prev[hp_a];
|
|
if (hp_diff) {
|
|
/* Count new devices (in curr but not prev) */
|
|
hp_byte = hp_diff & hp_curr[hp_a];
|
|
while (hp_byte) {
|
|
hp_added++;
|
|
hp_byte &= (hp_byte - 1); /* Kernighan clear-lowest */
|
|
}
|
|
/* Count removed devices (in prev but not curr) */
|
|
hp_byte = hp_diff & hp_prev[hp_a];
|
|
while (hp_byte) {
|
|
hp_removed++;
|
|
hp_byte &= (hp_byte - 1);
|
|
}
|
|
/* Count per-device changes (not per-byte) */
|
|
hp_byte = hp_diff;
|
|
while (hp_byte) {
|
|
hp_changes++;
|
|
hp_byte &= (hp_byte - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
hp_scan_ok = 1;
|
|
return;
|
|
|
|
hp_abort:
|
|
/* I2C timeout during scan — issue STOP and bail out.
|
|
* hp_curr is partial so we don't update hp_prev. */
|
|
I2CS |= bmSTOP;
|
|
last_error = ERR_I2C_TIMEOUT;
|
|
}
|
|
|
|
/* ---------- Streaming diagnostics poll ---------- */
|
|
|
|
/*
|
|
* Called from main loop while streaming (BM_ARMED set).
|
|
* Checks EP2 FIFO overflow and BCM4500 transport sync state.
|
|
*/
|
|
/* Rate-limit I2C reads: only poll BCM4500 every SD_I2C_INTERVAL polls.
|
|
* At ~100K main-loop iterations/sec, 4096 ≈ every 40ms — fast enough
|
|
* for meaningful sync-loss detection without saturating the I2C bus. */
|
|
#define SD_I2C_INTERVAL 4096
|
|
|
|
static void stream_diag_poll(void) {
|
|
static __xdata BYTE sd_rd[2]; /* dedicated I2C buffer for diag reads */
|
|
|
|
/* Saturate at max instead of wrapping (I1 fix) */
|
|
if (sd_poll_count < 0xFFFFFFFF)
|
|
sd_poll_count++;
|
|
|
|
/* EP2 FIFO full check is a pure SFR read — no I2C, always safe */
|
|
if (EP2CS & bmEPFULL) {
|
|
if (sd_overflow_count < 0xFFFF)
|
|
sd_overflow_count++;
|
|
}
|
|
|
|
/* Rate-limited BCM4500 I2C reads for sync tracking.
|
|
* Only attempt if BCM is booted and interval elapsed. */
|
|
if ((config_status & BM_FW_LOADED) &&
|
|
((WORD)sd_poll_count & (SD_I2C_INTERVAL - 1)) == 0) {
|
|
sd_rd[0] = 0;
|
|
sd_rd[1] = 0;
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_STATUS, 1, &sd_rd[0]);
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_LOCK, 1, &sd_rd[1]);
|
|
|
|
sd_last_status = sd_rd[0];
|
|
sd_last_lock = sd_rd[1];
|
|
|
|
/* Detect sync loss: had lock (bit 5) previously, lost it now */
|
|
if (sd_had_sync && !(sd_last_lock & 0x20)) {
|
|
if (sd_sync_loss < 0xFFFF)
|
|
sd_sync_loss++;
|
|
}
|
|
sd_had_sync = (sd_last_lock & 0x20) ? 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 */
|
|
while (!(GPIFTRIG & 0x80))
|
|
;
|
|
|
|
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 */
|
|
while (!(GPIFTRIG & 0x80))
|
|
;
|
|
|
|
/* Skip/discard partial EP2 packet */
|
|
OUTPKTEND = 0x82;
|
|
SYNCDELAY;
|
|
|
|
config_status &= ~BM_ARMED;
|
|
|
|
/* De-assert all BCM4500 control lines on P3 */
|
|
IOD |= 0xE0;
|
|
}
|
|
|
|
/* ---------- DiSEqC tone burst ---------- */
|
|
|
|
/*
|
|
* Send a tone burst (mini DiSEqC). This is the simpler variant.
|
|
* Tone burst A: unmodulated 22kHz for 12.5ms
|
|
* Tone burst B: modulated (not implemented yet)
|
|
*
|
|
* Uses Timer2 for timing as the stock firmware does.
|
|
*/
|
|
static void diseqc_tone_burst(BYTE sat_b) {
|
|
BYTE i;
|
|
|
|
(void)sat_b; /* both A and B send 22kHz burst for now */
|
|
|
|
/* Configure Timer2 auto-reload */
|
|
/* CKCON.T2M = 0 -> Timer2 clk = 48MHz/12 = 4MHz */
|
|
CKCON &= ~0x20;
|
|
T2CON = 0x04; /* auto-reload, running */
|
|
RCAP2H = 0xF8;
|
|
RCAP2L = 0x2F; /* reload = 63535 -> ~500us tick */
|
|
TL2 = 0xFF;
|
|
TH2 = 0xFF; /* force immediate overflow */
|
|
|
|
/* Pre-burst settling: 15 ticks (~7.5ms) with carrier off */
|
|
IOA &= ~PIN_22KHZ;
|
|
TF2 = 0;
|
|
for (i = 0; i < 15; i++) {
|
|
while (!TF2)
|
|
;
|
|
TF2 = 0;
|
|
}
|
|
|
|
/* Burst: 25 ticks (~12.5ms) with carrier on */
|
|
IOA |= PIN_22KHZ;
|
|
for (i = 0; i < 25; i++) {
|
|
while (!TF2)
|
|
;
|
|
TF2 = 0;
|
|
}
|
|
|
|
/* Carrier off */
|
|
IOA &= ~PIN_22KHZ;
|
|
|
|
/* Post-burst settling: 5 ticks (~2.5ms) */
|
|
for (i = 0; i < 5; i++) {
|
|
while (!TF2)
|
|
;
|
|
TF2 = 0;
|
|
}
|
|
|
|
/* Stop Timer2 */
|
|
TR2 = 0;
|
|
}
|
|
|
|
/* ---------- DiSEqC Manchester encoder ---------- */
|
|
|
|
/*
|
|
* DiSEqC uses Manchester encoding over a 22 kHz carrier.
|
|
* The external oscillator generates 22 kHz continuously when P0.3 is HIGH;
|
|
* gating P0.3 LOW silences the carrier. Timer2 provides ~500.25 us ticks.
|
|
*
|
|
* Timing (EN 50494 / DiSEqC bus spec):
|
|
* Bit '1': 1 tick tone + 2 ticks silence = ~1.5 ms
|
|
* Bit '0': 2 ticks tone + 1 tick silence = ~1.5 ms
|
|
* Preamble: 30 ticks continuous tone (~15 ms)
|
|
* Start gap: 3 ticks silence (~1.5 ms)
|
|
* Inter-byte gap: 12 ticks silence (~6 ms)
|
|
* Post-message: 12 ticks silence (~6 ms)
|
|
*/
|
|
|
|
static void diseqc_wait_ticks(BYTE count) {
|
|
static __xdata BYTE dt_i;
|
|
for (dt_i = 0; dt_i < count; dt_i++) {
|
|
while (!TF2)
|
|
;
|
|
TF2 = 0;
|
|
}
|
|
}
|
|
|
|
static BYTE diseqc_parity(BYTE val) {
|
|
/* Compute odd parity: returns 1 if even number of set bits */
|
|
BYTE p = val;
|
|
p ^= (p >> 4);
|
|
p ^= (p >> 2);
|
|
p ^= (p >> 1);
|
|
return (~p) & 0x01;
|
|
}
|
|
|
|
static void diseqc_send_bit(BYTE bit) {
|
|
if (bit) {
|
|
/* '1': 1 tick tone ON, 2 ticks silence */
|
|
IOA |= PIN_22KHZ;
|
|
diseqc_wait_ticks(1);
|
|
IOA &= ~PIN_22KHZ;
|
|
diseqc_wait_ticks(2);
|
|
} else {
|
|
/* '0': 2 ticks tone ON, 1 tick silence */
|
|
IOA |= PIN_22KHZ;
|
|
diseqc_wait_ticks(2);
|
|
IOA &= ~PIN_22KHZ;
|
|
diseqc_wait_ticks(1);
|
|
}
|
|
}
|
|
|
|
static void diseqc_send_byte(BYTE val) {
|
|
static __xdata BYTE db_i, db_parity;
|
|
|
|
/* 8 data bits, MSB first */
|
|
for (db_i = 0; db_i < 8; db_i++) {
|
|
diseqc_send_bit((val >> (7 - db_i)) & 0x01);
|
|
}
|
|
|
|
/* Odd parity bit */
|
|
db_parity = diseqc_parity(val);
|
|
diseqc_send_bit(db_parity);
|
|
}
|
|
|
|
static void diseqc_send_message(BYTE len) {
|
|
static __xdata BYTE dm_i, dm_saved_tone;
|
|
|
|
if (len < 3 || len > 6)
|
|
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;
|
|
}
|
|
|
|
/* ---------- 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) {
|
|
/*
|
|
* 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;
|
|
|
|
while (EP2CS & bmEPFULL)
|
|
;
|
|
}
|
|
|
|
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) {
|
|
/* 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);
|
|
bcm_indirect_write_block(0x00, i2c_buf, 4);
|
|
|
|
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);
|
|
bcm_indirect_write_block(0x00, i2c_buf, 4);
|
|
|
|
bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE);
|
|
|
|
/* 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 & 0x20) {
|
|
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) {
|
|
/*
|
|
* 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);
|
|
bcm_indirect_write_block(0x00, i2c_buf, 4);
|
|
|
|
/* 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 */
|
|
while (EP2CS & bmEPFULL)
|
|
;
|
|
}
|
|
|
|
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) {
|
|
/*
|
|
* 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);
|
|
bcm_indirect_write_block(0x00, i2c_buf, 4);
|
|
|
|
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);
|
|
bcm_indirect_write_block(0x00, i2c_buf, 4);
|
|
|
|
/* Issue tune command */
|
|
bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE);
|
|
|
|
/* 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 & 0x20) {
|
|
/* 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))
|
|
return;
|
|
|
|
/*
|
|
* Byte-reverse symbol rate (LE->BE) into tune_data[0..3]
|
|
* and frequency (LE->BE) into tune_data[4..7]
|
|
*/
|
|
for (tune_i = 0; tune_i < 4; tune_i++) {
|
|
tune_data[tune_i] = EP0BUF[3 - tune_i]; /* SR BE */
|
|
tune_data[4 + tune_i] = EP0BUF[7 - tune_i]; /* Freq BE */
|
|
}
|
|
|
|
/* Modulation type and FEC rate */
|
|
tune_data[8] = EP0BUF[8];
|
|
tune_data[9] = EP0BUF[9];
|
|
|
|
/* Demod mode: default standard (0x10) */
|
|
tune_data[10] = 0x10;
|
|
|
|
/* Turbo flag: 0x00 for DVB-S, 0x01 for turbo modes */
|
|
tune_data[11] = 0x00;
|
|
if (EP0BUF[8] >= 1 && EP0BUF[8] <= 3)
|
|
tune_data[11] = 0x01;
|
|
|
|
/* Set demod mode for DCII variants */
|
|
switch (EP0BUF[8]) {
|
|
case 5: tune_data[10] = 0x12; break; /* DCII I-stream */
|
|
case 6: tune_data[10] = 0x16; break; /* DCII Q-stream */
|
|
case 7: tune_data[10] = 0x11; break; /* DCII Offset QPSK */
|
|
default: break;
|
|
}
|
|
|
|
/* Poll BCM4500 for readiness */
|
|
bcm_poll_ready();
|
|
|
|
/* Write page 0 */
|
|
bcm_direct_write(BCM_REG_PAGE, 0x00);
|
|
|
|
/* Write all configuration data to BCM4500 data register */
|
|
tune_data[12] = BCM_REG_DATA; /* borrow byte past data (safe: 13 bytes in xdata) */
|
|
i2c_write(BCM4500_ADDR, 1, &tune_data[12], 12, tune_data);
|
|
|
|
/* Execute indirect write */
|
|
bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE);
|
|
|
|
/* 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;
|
|
while (EP0CS & bmEPBUSY)
|
|
;
|
|
do_tune();
|
|
return TRUE;
|
|
|
|
/* 0x87: GET_SIGNAL_STRENGTH -- read 6 bytes from BCM4500 */
|
|
case GET_SIGNAL_STRENGTH:
|
|
if (!(config_status & BM_STARTED)) {
|
|
EP0BUF[0] = 0; EP0BUF[1] = 0;
|
|
EP0BUF[2] = 0; EP0BUF[3] = 0;
|
|
EP0BUF[4] = 0; EP0BUF[5] = 0;
|
|
EP0BCH = 0;
|
|
EP0BCL = 6;
|
|
return TRUE;
|
|
}
|
|
/* Read signal quality via indirect registers */
|
|
bcm_indirect_read(0x00, &EP0BUF[0]);
|
|
bcm_indirect_read(0x01, &EP0BUF[1]);
|
|
bcm_indirect_read(0x02, &EP0BUF[2]);
|
|
bcm_indirect_read(0x03, &EP0BUF[3]);
|
|
bcm_indirect_read(0x04, &EP0BUF[4]);
|
|
bcm_indirect_read(0x05, &EP0BUF[5]);
|
|
EP0BCH = 0;
|
|
EP0BCL = 6;
|
|
return TRUE;
|
|
|
|
/* 0x89: BOOT_8PSK -- initialize BCM4500 demodulator
|
|
* wValue=0: shutdown
|
|
* wValue=1: full boot (reset + power + register init)
|
|
* wValue=0x80: debug -- return boot_stage only (no-op)
|
|
* wValue=0x81: debug -- GPIO setup + delays only
|
|
* wValue=0x82: debug -- GPIO + I2C probe only
|
|
* wValue=0x83: debug -- GPIO + I2C probe + 1 init block */
|
|
case BOOT_8PSK:
|
|
if (wval == 0x80) {
|
|
/* Debug: no-op, just return current state */
|
|
} else if (wval == 0x81) {
|
|
/* Debug: GPIO only, no I2C */
|
|
boot_stage = 1;
|
|
IOD |= 0xE0;
|
|
OEA |= PIN_BCM_RESET;
|
|
IOA &= ~PIN_BCM_RESET;
|
|
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
|
|
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
|
|
boot_stage = 2;
|
|
delay(30);
|
|
IOA |= PIN_BCM_RESET;
|
|
delay(50);
|
|
boot_stage = 0xA1; /* success marker for debug 0x81 */
|
|
} else if (wval == 0x82) {
|
|
/* Debug: GPIO + probe read (same as 0x85 now -- bmSTOP removed) */
|
|
boot_stage = 1;
|
|
IOD |= 0xE0;
|
|
OEA |= PIN_BCM_RESET;
|
|
IOA &= ~PIN_BCM_RESET;
|
|
/* bmSTOP removed -- corrupts FX2 I2C controller */
|
|
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
|
|
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
|
|
boot_stage = 2;
|
|
delay(30);
|
|
IOA |= PIN_BCM_RESET;
|
|
delay(50);
|
|
boot_stage = 3;
|
|
if (bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) {
|
|
EP0BUF[2] = i2c_rd[0];
|
|
boot_stage = 0xA2;
|
|
} else {
|
|
EP0BUF[2] = 0xEE;
|
|
boot_stage = 0xE3; /* failed at I2C probe */
|
|
}
|
|
} else if (wval == 0x83) {
|
|
/* Debug: GPIO + probe + first init block */
|
|
boot_stage = 1;
|
|
cancel_i2c_trans = FALSE;
|
|
IOD |= 0xE0;
|
|
OEA |= PIN_BCM_RESET;
|
|
IOA &= ~PIN_BCM_RESET;
|
|
/* bmSTOP removed -- corrupts FX2 I2C controller */
|
|
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
|
|
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
|
|
boot_stage = 2;
|
|
delay(30);
|
|
IOA |= PIN_BCM_RESET;
|
|
delay(50);
|
|
boot_stage = 3;
|
|
if (!bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) {
|
|
boot_stage = 0xE3;
|
|
} else {
|
|
boot_stage = 4;
|
|
if (bcm_write_init_block(bcm_init_block0, BCM_INIT_BLOCK0_LEN))
|
|
boot_stage = 0xA3;
|
|
else
|
|
boot_stage = 0xE4;
|
|
}
|
|
} else if (wval == 0x84) {
|
|
/* Debug: I2C-only probe, no GPIO (assumes chip already powered) */
|
|
boot_stage = 3;
|
|
if (bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) {
|
|
EP0BUF[2] = i2c_rd[0];
|
|
boot_stage = 0xA4;
|
|
} else {
|
|
EP0BUF[2] = 0xEE;
|
|
boot_stage = 0xE3;
|
|
}
|
|
} else if (wval == 0x85) {
|
|
/* Debug: Same as 0x82 but WITHOUT I2C bus reset (no bmSTOP) */
|
|
boot_stage = 1;
|
|
IOD |= 0xE0;
|
|
OEA |= PIN_BCM_RESET;
|
|
IOA &= ~PIN_BCM_RESET;
|
|
/* NOTE: no I2CS bmSTOP here, unlike 0x82 */
|
|
OEA |= (PIN_PWR_EN | PIN_PWR_DIS);
|
|
IOA = (IOA & ~PIN_PWR_DIS) | PIN_PWR_EN;
|
|
boot_stage = 2;
|
|
delay(30);
|
|
IOA |= PIN_BCM_RESET;
|
|
delay(50);
|
|
boot_stage = 3;
|
|
if (bcm_direct_read(BCM_REG_STATUS, &i2c_rd[0])) {
|
|
EP0BUF[2] = i2c_rd[0];
|
|
boot_stage = 0xA5;
|
|
} else {
|
|
EP0BUF[2] = 0xEE;
|
|
boot_stage = 0xE3;
|
|
}
|
|
} else if (wval) {
|
|
if (bcm4500_boot()) {
|
|
config_status |= (BM_STARTED | BM_FW_LOADED);
|
|
} else {
|
|
bcm4500_shutdown();
|
|
config_status &= ~(BM_STARTED | BM_FW_LOADED);
|
|
}
|
|
} else {
|
|
bcm4500_shutdown();
|
|
config_status &= ~(BM_STARTED | BM_FW_LOADED);
|
|
}
|
|
EP0BUF[0] = config_status;
|
|
EP0BUF[1] = boot_stage;
|
|
EP0BCH = 0;
|
|
EP0BCL = 3;
|
|
return TRUE;
|
|
|
|
/* 0x8A: START_INTERSIL -- enable LNB power supply */
|
|
case START_INTERSIL:
|
|
if (wval) {
|
|
/* Enable LNB power */
|
|
OEA |= (PIN_22KHZ | PIN_LNB_VOLT | PIN_DISEQC);
|
|
config_status |= BM_INTERSIL;
|
|
} else {
|
|
config_status &= ~BM_INTERSIL;
|
|
}
|
|
EP0BUF[0] = config_status;
|
|
EP0BCH = 0;
|
|
EP0BCL = 1;
|
|
return TRUE;
|
|
|
|
/* 0x8B: SET_LNB_VOLTAGE -- 13V (wval=0) or 18V (wval=1) */
|
|
case SET_LNB_VOLTAGE:
|
|
if (wval) {
|
|
IOA |= PIN_LNB_VOLT;
|
|
config_status |= BM_SEL18V;
|
|
} else {
|
|
IOA &= ~PIN_LNB_VOLT;
|
|
config_status &= ~BM_SEL18V;
|
|
}
|
|
return TRUE;
|
|
|
|
/* 0x8C: SET_22KHZ_TONE -- on (wval=1) or off (wval=0) */
|
|
case SET_22KHZ_TONE:
|
|
if (wval) {
|
|
IOA |= PIN_22KHZ;
|
|
config_status |= BM_22KHZ;
|
|
} else {
|
|
IOA &= ~PIN_22KHZ;
|
|
config_status &= ~BM_22KHZ;
|
|
}
|
|
return TRUE;
|
|
|
|
/* 0x8D: SEND_DISEQC -- tone burst or DiSEqC message */
|
|
case SEND_DISEQC: {
|
|
WORD wlen;
|
|
wlen = SETUP_LENGTH();
|
|
if (wlen == 0) {
|
|
/* Tone burst: A if wval==0, B if wval!=0 */
|
|
diseqc_tone_burst((BYTE)wval);
|
|
} else if (wlen >= 3 && wlen <= 6) {
|
|
/* Full DiSEqC message: reject if streaming */
|
|
if (config_status & BM_ARMED) {
|
|
last_error = ERR_BCM_NOT_READY;
|
|
return TRUE;
|
|
}
|
|
/* EP0 data phase: receive message bytes */
|
|
EP0BCL = 0;
|
|
SYNCDELAY;
|
|
while (EP0CS & bmEPBUSY)
|
|
;
|
|
/* 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.04.0 */
|
|
EP0BUF[1] = 0x04; /* minor */
|
|
EP0BUF[2] = 0x03; /* major */
|
|
EP0BUF[3] = 0x0F; /* day = 15 */
|
|
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;
|
|
while (EP0CS & bmEPBUSY)
|
|
;
|
|
do_spectrum_sweep();
|
|
return TRUE;
|
|
|
|
/* 0xB1: RAW_DEMOD_READ -- read BCM4500 register */
|
|
case RAW_DEMOD_READ:
|
|
val = 0;
|
|
bcm_indirect_read((BYTE)wval, &val);
|
|
EP0BUF[0] = val;
|
|
EP0BCH = 0;
|
|
EP0BCL = 1;
|
|
return TRUE;
|
|
|
|
/* 0xB2: RAW_DEMOD_WRITE -- write BCM4500 register */
|
|
case RAW_DEMOD_WRITE: {
|
|
WORD widx;
|
|
widx = SETUP_INDEX();
|
|
bcm_indirect_write((BYTE)wval, (BYTE)widx);
|
|
return TRUE;
|
|
}
|
|
|
|
/* 0xB3: BLIND_SCAN */
|
|
case BLIND_SCAN:
|
|
/* EP0 data phase: wait for 16 bytes from host */
|
|
EP0BCL = 0;
|
|
SYNCDELAY;
|
|
while (EP0CS & bmEPBUSY)
|
|
;
|
|
do_blind_scan();
|
|
return TRUE;
|
|
|
|
/* 0xB4: I2C_BUS_SCAN -- probe all 7-bit addresses, return 16-byte bitmap */
|
|
case 0xB4: {
|
|
BYTE a, byte_idx, bit;
|
|
/* 128 addresses / 8 = 16 bytes bitmap */
|
|
for (byte_idx = 0; byte_idx < 16; byte_idx++)
|
|
EP0BUF[byte_idx] = 0;
|
|
|
|
for (a = 1; a < 0x78; a++) {
|
|
/* Try START + address + write, see if ACK comes back */
|
|
I2CS |= bmSTART;
|
|
I2DAT = a << 1; /* write direction */
|
|
while (!(I2CS & bmDONE))
|
|
;
|
|
if (I2CS & bmACK) {
|
|
/* Device responded at this address */
|
|
byte_idx = a >> 3;
|
|
bit = a & 0x07;
|
|
EP0BUF[byte_idx] |= (1 << bit);
|
|
}
|
|
I2CS |= bmSTOP;
|
|
while (I2CS & bmSTOP)
|
|
;
|
|
}
|
|
EP0BCH = 0;
|
|
EP0BCL = 16;
|
|
return TRUE;
|
|
}
|
|
|
|
/* 0xB5: I2C_RAW_READ -- read N bytes from any I2C address
|
|
* wValue = 7-bit I2C address, wIndex = register, wLength = bytes to read
|
|
* Uses combined write-read with repeated START */
|
|
case 0xB5: {
|
|
BYTE i2c_addr_b5 = (BYTE)wval;
|
|
BYTE i2c_reg_b5 = (BYTE)SETUP_INDEX();
|
|
BYTE i2c_len_b5 = (BYTE)SETUP_LENGTH();
|
|
BYTE ok;
|
|
|
|
if (i2c_len_b5 > 64) i2c_len_b5 = 64;
|
|
|
|
ok = i2c_combined_read(i2c_addr_b5, i2c_reg_b5, i2c_len_b5, EP0BUF);
|
|
if (!ok) {
|
|
BYTE fi;
|
|
for (fi = 0; fi < i2c_len_b5; fi++)
|
|
EP0BUF[fi] = 0xFF;
|
|
}
|
|
EP0BCH = 0;
|
|
EP0BCL = i2c_len_b5;
|
|
return TRUE;
|
|
}
|
|
|
|
/* 0xB6: I2C_DIAG -- step-by-step indirect register read diagnostic
|
|
* wValue = page/register to read
|
|
* Returns 8 bytes: [write_A6_ok, readback_A6, write_A8_ok, readback_A8,
|
|
* readback_A7, direct_read_A6, direct_read_A7, direct_read_A8] */
|
|
case 0xB6: {
|
|
/* Use shared xdata diag buffer to save DSEG */
|
|
vc_diag[0] = (BYTE)wval; /* target_reg */
|
|
|
|
/* Step 1: Write target register to page select (0xA6) */
|
|
vc_diag[1] = bcm_direct_write(BCM_REG_PAGE, vc_diag[0]) ? 0x01 : 0x00;
|
|
|
|
/* Step 2: Read back 0xA6 to verify write */
|
|
vc_diag[2] = 0xEE;
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &vc_diag[2]);
|
|
|
|
/* Step 3: Write read command (0x01) to 0xA8 */
|
|
vc_diag[3] = bcm_direct_write(BCM_REG_CMD, BCM_CMD_READ) ? 0x01 : 0x00;
|
|
|
|
/* Step 4: Read back 0xA8 to check command status */
|
|
vc_diag[4] = 0xEE;
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &vc_diag[4]);
|
|
|
|
/* Step 5: Small delay for command execution */
|
|
delay(2);
|
|
|
|
/* Step 6: Read 0xA7 (data register) — this is the result */
|
|
vc_diag[5] = 0xEE;
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &vc_diag[5]);
|
|
|
|
/* Step 7: Read back all three control regs for final state */
|
|
vc_diag[6] = 0xEE;
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &vc_diag[6]);
|
|
vc_diag[7] = 0xEE;
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &vc_diag[7]);
|
|
|
|
EP0BUF[0] = vc_diag[1]; /* write_A6_ok */
|
|
EP0BUF[1] = vc_diag[2]; /* readback_A6 */
|
|
EP0BUF[2] = vc_diag[3]; /* write_A8_ok */
|
|
EP0BUF[3] = vc_diag[4]; /* readback_A8 */
|
|
EP0BUF[4] = vc_diag[5]; /* readback_A7 */
|
|
EP0BUF[5] = vc_diag[6]; /* direct_read_A6 */
|
|
|
|
/* Read remaining registers directly into EP0BUF */
|
|
vc_diag[6] = 0xEE;
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &vc_diag[6]);
|
|
EP0BUF[6] = vc_diag[6]; /* direct_read_A7 */
|
|
vc_diag[7] = 0xEE;
|
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &vc_diag[7]);
|
|
EP0BUF[7] = vc_diag[7]; /* direct_read_A8 */
|
|
|
|
EP0BCH = 0;
|
|
EP0BCL = 8;
|
|
return TRUE;
|
|
}
|
|
|
|
/* 0xB7: SIGNAL_MONITOR -- fast combined signal read (8 bytes)
|
|
* Returns SNR(2) + AGC1(2) + AGC2(2) + lock(1) + status(1)
|
|
* in a single USB transfer instead of 3 separate reads. */
|
|
case SIGNAL_MONITOR: {
|
|
BYTE sm_val;
|
|
/* SNR: indirect regs 0x00-0x01 */
|
|
bcm_indirect_read(0x00, &EP0BUF[0]);
|
|
bcm_indirect_read(0x01, &EP0BUF[1]);
|
|
/* AGC1: indirect regs 0x02-0x03 */
|
|
bcm_indirect_read(0x02, &EP0BUF[2]);
|
|
bcm_indirect_read(0x03, &EP0BUF[3]);
|
|
/* AGC2: indirect regs 0x04-0x05 */
|
|
bcm_indirect_read(0x04, &EP0BUF[4]);
|
|
bcm_indirect_read(0x05, &EP0BUF[5]);
|
|
/* Lock register (direct 0xA4) */
|
|
sm_val = 0;
|
|
bcm_direct_read(BCM_REG_LOCK, &sm_val);
|
|
EP0BUF[6] = sm_val;
|
|
/* Status register (direct 0xA2) */
|
|
sm_val = 0;
|
|
bcm_direct_read(BCM_REG_STATUS, &sm_val);
|
|
EP0BUF[7] = sm_val;
|
|
EP0BCH = 0;
|
|
EP0BCL = 8;
|
|
return TRUE;
|
|
}
|
|
|
|
/* 0xB8: TUNE_MONITOR -- tune + dwell + read in one round-trip
|
|
* OUT phase (0x40): receive 10-byte tune payload, tune, dwell, read signal
|
|
* IN phase (0xC0): return stored 10-byte result
|
|
* wValue = dwell time in ms (1-255) */
|
|
case TUNE_MONITOR: {
|
|
if (SETUPDAT[0] & 0x80) {
|
|
/* IN phase: return stored result from previous OUT phase */
|
|
BYTE ti;
|
|
for (ti = 0; ti < 10; ti++)
|
|
EP0BUF[ti] = tm_result[ti];
|
|
EP0BCH = 0;
|
|
EP0BCL = 10;
|
|
} else {
|
|
/* OUT phase: tune, dwell, measure */
|
|
BYTE dwell = (BYTE)wval;
|
|
EP0BCL = 0;
|
|
SYNCDELAY;
|
|
while (EP0CS & bmEPBUSY)
|
|
;
|
|
do_tune();
|
|
if (dwell > 0)
|
|
delay(dwell);
|
|
/* Read signal into result buffer */
|
|
bcm_indirect_read(0x00, &tm_result[0]);
|
|
bcm_indirect_read(0x01, &tm_result[1]);
|
|
bcm_indirect_read(0x02, &tm_result[2]);
|
|
bcm_indirect_read(0x03, &tm_result[3]);
|
|
bcm_indirect_read(0x04, &tm_result[4]);
|
|
bcm_indirect_read(0x05, &tm_result[5]);
|
|
tm_result[6] = 0;
|
|
bcm_direct_read(BCM_REG_LOCK, &tm_result[6]);
|
|
tm_result[7] = 0;
|
|
bcm_direct_read(BCM_REG_STATUS, &tm_result[7]);
|
|
tm_result[8] = dwell;
|
|
tm_result[9] = (BYTE)(wval >> 8);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* 0xB9: MULTI_REG_READ -- batch read of contiguous indirect registers
|
|
* wValue = start register, wIndex = count (1-64)
|
|
* Returns count bytes, one per register */
|
|
case MULTI_REG_READ: {
|
|
BYTE start_reg = (BYTE)wval;
|
|
BYTE count = (BYTE)SETUP_INDEX();
|
|
BYTE mi;
|
|
if (count == 0 || count > 64)
|
|
count = 1;
|
|
for (mi = 0; mi < count; mi++) {
|
|
EP0BUF[mi] = 0;
|
|
bcm_indirect_read(start_reg + mi, &EP0BUF[mi]);
|
|
}
|
|
EP0BCH = 0;
|
|
EP0BCL = count;
|
|
return TRUE;
|
|
}
|
|
|
|
/* 0xBA: PARAM_SWEEP -- parameterized spectrum sweep */
|
|
case PARAM_SWEEP:
|
|
EP0BCL = 0;
|
|
SYNCDELAY;
|
|
while (EP0CS & bmEPBUSY)
|
|
;
|
|
do_param_sweep();
|
|
return TRUE;
|
|
|
|
/* 0xBB: ADAPTIVE_BLIND_SCAN -- blind scan with AGC pre-check */
|
|
case ADAPTIVE_BLIND_SCAN:
|
|
EP0BCL = 0;
|
|
SYNCDELAY;
|
|
while (EP0CS & bmEPBUSY)
|
|
;
|
|
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 */
|
|
if (wval == 1) {
|
|
sd_poll_count = 0;
|
|
sd_overflow_count = 0;
|
|
sd_sync_loss = 0;
|
|
}
|
|
EP0BCH = 0;
|
|
EP0BCL = 12;
|
|
return TRUE;
|
|
|
|
/* 0xBE: GET_HOTPLUG_STATUS -- I2C device change detection
|
|
* Returns 36 bytes:
|
|
* [0-15] current I2C bus bitmap (16 bytes)
|
|
* [16-17] u16 LE change_count (cumulative change events)
|
|
* [18] u8 devices_added (in last scan)
|
|
* [19] u8 devices_removed (in last scan)
|
|
* [20-35] previous I2C bus bitmap (16 bytes)
|
|
* wValue=1 to reset change counter after read.
|
|
* wValue=2 to force immediate rescan. */
|
|
case GET_HOTPLUG_STATUS: {
|
|
static __xdata BYTE hp_i;
|
|
if (wval == 2) {
|
|
/* Force rescan (only when not streaming) */
|
|
if (!(config_status & BM_ARMED))
|
|
i2c_hotplug_scan();
|
|
}
|
|
for (hp_i = 0; hp_i < 16; hp_i++)
|
|
EP0BUF[hp_i] = hp_curr[hp_i];
|
|
EP0BUF[16] = (BYTE)(hp_changes);
|
|
EP0BUF[17] = (BYTE)(hp_changes >> 8);
|
|
EP0BUF[18] = hp_added;
|
|
EP0BUF[19] = hp_removed;
|
|
for (hp_i = 0; hp_i < 16; hp_i++)
|
|
EP0BUF[20 + hp_i] = hp_prev[hp_i];
|
|
if (wval == 1)
|
|
hp_changes = 0;
|
|
EP0BCH = 0;
|
|
EP0BCL = 36;
|
|
return TRUE;
|
|
}
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* ---------- Required fx2lib callbacks ---------- */
|
|
|
|
BOOL handle_get_descriptor(void) {
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL handle_get_interface(BYTE ifc, BYTE *alt_ifc) {
|
|
if (ifc == 0) {
|
|
*alt_ifc = 0;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL handle_set_interface(BYTE ifc, BYTE alt_ifc) {
|
|
if (ifc == 0 && alt_ifc == 0) {
|
|
RESETTOGGLE(0x82);
|
|
RESETFIFO(0x02);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BYTE handle_get_configuration(void) {
|
|
return 1;
|
|
}
|
|
|
|
BOOL handle_set_configuration(BYTE cfg) {
|
|
return cfg == 1 ? TRUE : FALSE;
|
|
}
|
|
|
|
/* ---------- USB interrupt handlers ---------- */
|
|
|
|
void sudav_isr(void) __interrupt (SUDAV_ISR) {
|
|
got_sud = TRUE;
|
|
CLEAR_SUDAV();
|
|
}
|
|
|
|
void usbreset_isr(void) __interrupt (USBRESET_ISR) {
|
|
handle_hispeed(FALSE);
|
|
CLEAR_USBRESET();
|
|
}
|
|
|
|
void hispeed_isr(void) __interrupt (HISPEED_ISR) {
|
|
handle_hispeed(TRUE);
|
|
CLEAR_HISPEED();
|
|
}
|
|
|
|
/* ---------- Main ---------- */
|
|
|
|
void main(void) {
|
|
|
|
config_status = 0;
|
|
last_error = ERR_OK;
|
|
got_sud = FALSE;
|
|
|
|
/* Initialize hot-plug detection state */
|
|
hp_changes = 0;
|
|
hp_added = 0;
|
|
hp_removed = 0;
|
|
hp_scan_ok = 0;
|
|
hp_last_frame = 0;
|
|
|
|
/* Initialize streaming diagnostics */
|
|
sd_poll_count = 0;
|
|
sd_overflow_count = 0;
|
|
sd_sync_loss = 0;
|
|
sd_last_status = 0;
|
|
sd_last_lock = 0;
|
|
sd_had_sync = 0;
|
|
|
|
REVCTL = 0x03; /* NOAUTOARM + SKIPCOMMIT */
|
|
SYNCDELAY;
|
|
|
|
RENUMERATE_UNCOND();
|
|
|
|
SETCPUFREQ(CLK_48M);
|
|
SETIF48MHZ();
|
|
|
|
USE_USB_INTS();
|
|
ENABLE_SUDAV();
|
|
ENABLE_HISPEED();
|
|
ENABLE_USBRESET();
|
|
|
|
/* Configure I2C: 400kHz */
|
|
I2CTL = bm400KHZ;
|
|
|
|
/* Configure GPIO output enables (v2.06 pin map):
|
|
* P0.1=power_en, P0.2=power_dis, P0.3=22kHz, P0.4=LNB,
|
|
* P0.5=BCM_reset, P0.7=DiSEqC/streaming */
|
|
OEA |= (PIN_PWR_EN | PIN_PWR_DIS | PIN_22KHZ | PIN_LNB_VOLT |
|
|
PIN_BCM_RESET | PIN_DISEQC);
|
|
|
|
/* Initial GPIO state matches stock firmware:
|
|
* P0.7=1 (DiSEqC idle), P0.5=0 (BCM4500 held in reset),
|
|
* P0.2=1 (power disable), all others LOW */
|
|
IOA = 0x84;
|
|
IOD = 0xE1; /* P3.7:5=1 (controls idle), P3.0=1 */
|
|
|
|
/* EP2 is bulk IN (0x82), 512 byte, double-buffered */
|
|
EP2CFG = 0xE2; /* valid, IN, bulk, 512, double */
|
|
SYNCDELAY;
|
|
|
|
/* Disable unused endpoints */
|
|
EP1INCFG &= ~bmVALID;
|
|
SYNCDELAY;
|
|
EP1OUTCFG &= ~bmVALID;
|
|
SYNCDELAY;
|
|
EP4CFG &= ~bmVALID;
|
|
SYNCDELAY;
|
|
EP6CFG &= ~bmVALID;
|
|
SYNCDELAY;
|
|
EP8CFG &= ~bmVALID;
|
|
SYNCDELAY;
|
|
|
|
/* Reset all FIFOs */
|
|
RESETFIFOS();
|
|
|
|
/* IFCONFIG: internal 48MHz, GPIF master, async */
|
|
IFCONFIG = 0xEE;
|
|
SYNCDELAY;
|
|
|
|
/* EP2FIFOCFG: AUTOIN, ZEROLENIN, 8-bit */
|
|
EP2FIFOCFG = 0x0C;
|
|
SYNCDELAY;
|
|
|
|
/* Disable other FIFO configs */
|
|
EP4FIFOCFG = 0;
|
|
SYNCDELAY;
|
|
EP6FIFOCFG = 0;
|
|
SYNCDELAY;
|
|
EP8FIFOCFG = 0;
|
|
SYNCDELAY;
|
|
|
|
EA = 1; /* global interrupt enable */
|
|
|
|
while (TRUE) {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|