/* * Genpix SkyWalker-1 Custom Firmware * For Cypress CY7C68013A (FX2LP) + Broadcom BCM4500 demodulator * * Stock-compatible vendor commands (0x80-0x94) plus new * spectrum sweep, raw demod access, and blind scan commands (0xB0-0xB3). * * SDCC + fx2lib toolchain. Loaded into FX2 RAM for testing. */ #include #include #include #include #include #include #include #define SYNCDELAY SYNCDELAY4 /* BCM4500 I2C address (7-bit); 8-bit wire address is 0x10/0x11 */ #define BCM4500_ADDR 0x08 /* BCM4500 indirect register protocol registers */ #define BCM_REG_PAGE 0xA6 #define BCM_REG_DATA 0xA7 #define BCM_REG_CMD 0xA8 /* BCM4500 status registers */ #define BCM_REG_STATUS 0xA2 #define BCM_REG_LOCK 0xA4 /* 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 /* 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_22KHZ 0x08 /* P0.3 */ #define PIN_LNB_VOLT 0x10 /* P0.4 */ #define PIN_DISEQC 0x80 /* P0.7 */ /* configuration status byte -- stored in ordinary variable */ static volatile BYTE config_status; /* ISR flag */ volatile __bit got_sud; /* I2C scratch buffers in xdata */ static __xdata BYTE i2c_buf[8]; static __xdata BYTE i2c_rd[8]; /* ---------- BCM4500 I2C helpers ---------- */ /* * 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; while (!(I2CS & bmDONE)) ; if (!(I2CS & bmACK)) goto fail; /* Write register address */ I2DAT = reg; while (!(I2CS & bmDONE)) ; if (!(I2CS & bmACK)) goto fail; /* REPEATED START + read address */ I2CS |= bmSTART; I2DAT = (addr << 1) | 1; while (!(I2CS & bmDONE)) ; if (!(I2CS & bmACK)) 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++) { while (!(I2CS & bmDONE)) ; if (i == len - 2) I2CS |= bmLASTRD; if (i == len - 1) I2CS |= bmSTOP; buf[i] = I2DAT; } while (I2CS & bmSTOP) ; return TRUE; fail: I2CS |= bmSTOP; while (I2CS & bmSTOP) ; return FALSE; } /* * Write one byte to a BCM4500 direct I2C register (subaddr). * This writes to the I2C register directly, not through the * indirect protocol. */ static BOOL bcm_direct_write(BYTE reg, BYTE val) { i2c_buf[0] = val; return i2c_write(BCM4500_ADDR, 1, ®, 1, i2c_buf); } /* * 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) { BYTE start_reg = BCM_REG_PAGE; i2c_rd[0] = reg; i2c_rd[1] = val; i2c_rd[2] = BCM_CMD_WRITE; return i2c_write(BCM4500_ADDR, 1, &start_reg, 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) { BYTE start_reg = BCM_REG_PAGE; /* 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(BCM4500_ADDR, 1, &start_reg, 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) { BYTE reg; reg = BCM_REG_PAGE; i2c_buf[0] = page; if (!i2c_write(BCM4500_ADDR, 1, ®, 1, i2c_buf)) return FALSE; reg = BCM_REG_DATA; if (!i2c_write(BCM4500_ADDR, 1, ®, 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 < 20; i++) { if (bcm_direct_read(BCM_REG_CMD, &val)) { if (!(val & 0x01)) return TRUE; } delay(5); } return FALSE; } /* ---------- 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; } /* ---------- 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; WORD buf_idx; BYTE snr_lo, 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; 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 */ snr_lo = 0; snr_hi = 0; bcm_indirect_read(0x00, &snr_lo); bcm_indirect_read(0x01, &snr_hi); /* Store u16 LE into EP2 FIFO buffer */ if (buf_idx < 1024 - 1) { EP2FIFOBUF[buf_idx++] = snr_lo; EP2FIFOBUF[buf_idx++] = snr_hi; } /* If buffer is nearly full, commit this chunk */ if (buf_idx >= 512) { EP2BCH = MSB(buf_idx); SYNCDELAY; EP2BCL = LSB(buf_idx); SYNCDELAY; buf_idx = 0; /* Wait for the buffer to be taken by host */ while (EP2CS & bmEPFULL) ; } cur_freq += step_khz; } /* Commit any remaining data */ if (buf_idx > 0) { EP2BCH = MSB(buf_idx); SYNCDELAY; EP2BCL = LSB(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; BYTE 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 */ lock_val = 0; bcm_direct_read(BCM_REG_LOCK, &lock_val); if (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) { BYTE i; __xdata BYTE tune_data[12]; 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 (i = 0; i < 4; i++) { tune_data[i] = EP0BUF[3 - i]; /* SR BE */ tune_data[4 + i] = EP0BUF[7 - 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 */ { BYTE reg = BCM_REG_DATA; i2c_write(BCM4500_ADDR, 1, ®, 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 */ case BOOT_8PSK: if (wval) { /* Power on: scan for BCM4500 at address 0x10 */ val = 0; if (bcm_direct_read(BCM_REG_STATUS, &val)) { config_status |= BM_STARTED; config_status |= BM_FW_LOADED; } } else { config_status &= ~BM_STARTED; } EP0BUF[0] = config_status; EP0BCH = 0; EP0BCL = 1; 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); } /* Full DiSEqC message: future implementation */ 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] = 0x01; /* patch -> version 3.00.1 */ EP0BUF[1] = 0x00; /* minor */ EP0BUF[2] = 0x03; /* major */ EP0BUF[3] = 0x0B; /* day = 11 */ 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: { BYTE target_reg = (BYTE)wval; BYTE diag[8]; BYTE rb; /* Step 1: Write target register to page select (0xA6) */ diag[0] = bcm_direct_write(BCM_REG_PAGE, target_reg) ? 0x01 : 0x00; /* Step 2: Read back 0xA6 to verify write */ rb = 0xEE; i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &rb); diag[1] = rb; /* Step 3: Write read command (0x01) to 0xA8 */ diag[2] = bcm_direct_write(BCM_REG_CMD, BCM_CMD_READ) ? 0x01 : 0x00; /* Step 4: Read back 0xA8 to check command status */ rb = 0xEE; i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &rb); diag[3] = rb; /* Step 5: Small delay for command execution */ delay(2); /* Step 6: Read 0xA7 (data register) — this is the result */ rb = 0xEE; i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &rb); diag[4] = rb; /* Step 7: Read back all three control regs for final state */ rb = 0xEE; i2c_combined_read(BCM4500_ADDR, BCM_REG_PAGE, 1, &rb); diag[5] = rb; rb = 0xEE; i2c_combined_read(BCM4500_ADDR, BCM_REG_DATA, 1, &rb); diag[6] = rb; rb = 0xEE; i2c_combined_read(BCM4500_ADDR, BCM_REG_CMD, 1, &rb); diag[7] = rb; EP0BUF[0] = diag[0]; EP0BUF[1] = diag[1]; EP0BUF[2] = diag[2]; EP0BUF[3] = diag[3]; EP0BUF[4] = diag[4]; EP0BUF[5] = diag[5]; EP0BUF[6] = diag[6]; EP0BUF[7] = diag[7]; EP0BCH = 0; EP0BCL = 8; return TRUE; } default: return FALSE; } } /* ---------- Required fx2lib callbacks ---------- */ BOOL handle_get_descriptor(void) { return FALSE; } BOOL handle_get_interface(BYTE ifc, BYTE *alt_ifc) { if (ifc == 0) { *alt_ifc = 0; return TRUE; } return FALSE; } BOOL handle_set_interface(BYTE ifc, BYTE alt_ifc) { if (ifc == 0 && alt_ifc == 0) { RESETTOGGLE(0x82); RESETFIFO(0x02); return TRUE; } return FALSE; } BYTE handle_get_configuration(void) { return 1; } BOOL handle_set_configuration(BYTE cfg) { return cfg == 1 ? TRUE : FALSE; } /* ---------- USB interrupt handlers ---------- */ void sudav_isr(void) __interrupt (SUDAV_ISR) { got_sud = TRUE; CLEAR_SUDAV(); } void usbreset_isr(void) __interrupt (USBRESET_ISR) { handle_hispeed(FALSE); CLEAR_USBRESET(); } void hispeed_isr(void) __interrupt (HISPEED_ISR) { handle_hispeed(TRUE); CLEAR_HISPEED(); } /* ---------- Main ---------- */ void main(void) { config_status = 0; got_sud = FALSE; 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 for LNB/tone/DiSEqC (v2.06 pin map) */ OEA |= (PIN_22KHZ | PIN_LNB_VOLT | PIN_DISEQC); /* P0.3, P0.4, P0.7 output */ /* Initial GPIO state: LNB off, tone off, DiSEqC idle */ IOA = 0x84; /* P0.7=1 (idle), P0.2=1 (BCM4500 control) */ 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; } } }