Add custom FX2 firmware and RAM loader for open-source development
Custom firmware (SDCC + fx2lib) implements all stock vendor commands (0x80-0x94) plus new commands for spectrum sweep (0xB0), raw BCM4500 register access (0xB1/0xB2), and blind scan (0xB3). Compiles to 6.3KB of code with healthy RAM margins. RAM loader (fw_load.py) uses the FX2 0xA0 vendor request to load firmware into RAM without touching EEPROM -- power cycle restores factory firmware. Supports Intel HEX and raw binary formats.
This commit is contained in:
parent
c7b5932cc0
commit
5710584267
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,2 +1,9 @@
|
|||||||
# Empty extraction directories
|
# Empty extraction directories
|
||||||
docs/diseqc/images/
|
docs/diseqc/images/
|
||||||
|
|
||||||
|
# Third-party dependencies
|
||||||
|
firmware/fx2lib/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
firmware/build/
|
||||||
|
tools/__pycache__/
|
||||||
|
|||||||
12
firmware/Makefile
Normal file
12
firmware/Makefile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FX2LIBDIR=fx2lib/
|
||||||
|
BASENAME=skywalker1
|
||||||
|
SOURCES=skywalker1.c
|
||||||
|
A51_SOURCES=dscr.a51
|
||||||
|
VID=0x09C0
|
||||||
|
PID=0x0203
|
||||||
|
CODE_SIZE=--code-size 0x3c00
|
||||||
|
|
||||||
|
include $(FX2LIBDIR)lib/fx2.mk
|
||||||
|
|
||||||
|
load: $(BUILDDIR)/$(BASENAME).bix
|
||||||
|
../tools/fw_load.py $(BUILDDIR)/$(BASENAME).bix
|
||||||
230
firmware/dscr.a51
Normal file
230
firmware/dscr.a51
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
; USB Descriptors for Genpix SkyWalker-1 Custom Firmware
|
||||||
|
;
|
||||||
|
; VID=0x09C0, PID=0x0203
|
||||||
|
; Single interface, EP2 IN bulk 512-byte for TS data
|
||||||
|
|
||||||
|
.module DEV_DSCR
|
||||||
|
|
||||||
|
; descriptor types
|
||||||
|
DSCR_DEVICE_TYPE=1
|
||||||
|
DSCR_CONFIG_TYPE=2
|
||||||
|
DSCR_STRING_TYPE=3
|
||||||
|
DSCR_INTERFACE_TYPE=4
|
||||||
|
DSCR_ENDPOINT_TYPE=5
|
||||||
|
DSCR_DEVQUAL_TYPE=6
|
||||||
|
|
||||||
|
; for the repeating interfaces
|
||||||
|
DSCR_INTERFACE_LEN=9
|
||||||
|
DSCR_ENDPOINT_LEN=7
|
||||||
|
|
||||||
|
; endpoint types
|
||||||
|
ENDPOINT_TYPE_CONTROL=0
|
||||||
|
ENDPOINT_TYPE_ISO=1
|
||||||
|
ENDPOINT_TYPE_BULK=2
|
||||||
|
ENDPOINT_TYPE_INT=3
|
||||||
|
|
||||||
|
.globl _dev_dscr, _dev_qual_dscr, _highspd_dscr, _fullspd_dscr, _dev_strings, _dev_strings_end
|
||||||
|
.area DSCR_AREA (CODE)
|
||||||
|
|
||||||
|
_dev_dscr:
|
||||||
|
.db dev_dscr_end-_dev_dscr ; len
|
||||||
|
.db DSCR_DEVICE_TYPE ; type
|
||||||
|
.dw 0x0002 ; usb 2.0
|
||||||
|
.db 0xff ; class (vendor specific)
|
||||||
|
.db 0xff ; subclass (vendor specific)
|
||||||
|
.db 0xff ; protocol (vendor specific)
|
||||||
|
.db 64 ; packet size (ep0)
|
||||||
|
.dw 0xC009 ; vendor id 0x09C0 (byte-swapped)
|
||||||
|
.dw 0x0302 ; product id 0x0203 (byte-swapped)
|
||||||
|
.dw 0x0100 ; version id
|
||||||
|
.db 1 ; manufacturer str idx
|
||||||
|
.db 2 ; product str idx
|
||||||
|
.db 3 ; serial str idx
|
||||||
|
.db 1 ; n configurations
|
||||||
|
dev_dscr_end:
|
||||||
|
|
||||||
|
_dev_qual_dscr:
|
||||||
|
.db dev_qualdscr_end-_dev_qual_dscr
|
||||||
|
.db DSCR_DEVQUAL_TYPE
|
||||||
|
.dw 0x0002 ; usb 2.0
|
||||||
|
.db 0xff
|
||||||
|
.db 0xff
|
||||||
|
.db 0xff
|
||||||
|
.db 64 ; max packet
|
||||||
|
.db 1 ; n configs
|
||||||
|
.db 0 ; extra reserved byte
|
||||||
|
dev_qualdscr_end:
|
||||||
|
|
||||||
|
; --- High speed configuration descriptor ---
|
||||||
|
_highspd_dscr:
|
||||||
|
.db highspd_dscr_end-_highspd_dscr
|
||||||
|
.db DSCR_CONFIG_TYPE
|
||||||
|
.db (highspd_dscr_realend-_highspd_dscr) % 256 ; total length lsb
|
||||||
|
.db (highspd_dscr_realend-_highspd_dscr) / 256 ; total length msb
|
||||||
|
.db 1 ; n interfaces
|
||||||
|
.db 1 ; config number
|
||||||
|
.db 0 ; config string
|
||||||
|
.db 0x80 ; attrs = bus powered, no wakeup
|
||||||
|
.db 0xFA ; max power = 500mA (0xFA * 2 = 500)
|
||||||
|
highspd_dscr_end:
|
||||||
|
|
||||||
|
; interface 0
|
||||||
|
.db DSCR_INTERFACE_LEN
|
||||||
|
.db DSCR_INTERFACE_TYPE
|
||||||
|
.db 0 ; index
|
||||||
|
.db 0 ; alt setting idx
|
||||||
|
.db 1 ; n endpoints (EP2 IN only)
|
||||||
|
.db 0xff ; class (vendor specific)
|
||||||
|
.db 0xff
|
||||||
|
.db 0xff
|
||||||
|
.db 4 ; string index
|
||||||
|
|
||||||
|
; endpoint 2 IN (0x82) -- MPEG-2 transport stream bulk
|
||||||
|
.db DSCR_ENDPOINT_LEN
|
||||||
|
.db DSCR_ENDPOINT_TYPE
|
||||||
|
.db 0x82 ; ep2 dir=IN and address
|
||||||
|
.db ENDPOINT_TYPE_BULK ; type
|
||||||
|
.db 0x00 ; max packet LSB
|
||||||
|
.db 0x02 ; max packet size=512 bytes
|
||||||
|
.db 0x00 ; polling interval
|
||||||
|
|
||||||
|
highspd_dscr_realend:
|
||||||
|
|
||||||
|
.even
|
||||||
|
; --- Full speed configuration descriptor ---
|
||||||
|
_fullspd_dscr:
|
||||||
|
.db fullspd_dscr_end-_fullspd_dscr
|
||||||
|
.db DSCR_CONFIG_TYPE
|
||||||
|
.db (fullspd_dscr_realend-_fullspd_dscr) % 256 ; total length lsb
|
||||||
|
.db (fullspd_dscr_realend-_fullspd_dscr) / 256 ; total length msb
|
||||||
|
.db 1 ; n interfaces
|
||||||
|
.db 1 ; config number
|
||||||
|
.db 0 ; config string
|
||||||
|
.db 0x80 ; attrs = bus powered, no wakeup
|
||||||
|
.db 0xFA ; max power = 500mA
|
||||||
|
fullspd_dscr_end:
|
||||||
|
|
||||||
|
; interface 0
|
||||||
|
.db DSCR_INTERFACE_LEN
|
||||||
|
.db DSCR_INTERFACE_TYPE
|
||||||
|
.db 0 ; index
|
||||||
|
.db 0 ; alt setting idx
|
||||||
|
.db 1 ; n endpoints
|
||||||
|
.db 0xff ; class (vendor specific)
|
||||||
|
.db 0xff
|
||||||
|
.db 0xff
|
||||||
|
.db 4 ; string index
|
||||||
|
|
||||||
|
; endpoint 2 IN (0x82) -- bulk 64 byte at full speed
|
||||||
|
.db DSCR_ENDPOINT_LEN
|
||||||
|
.db DSCR_ENDPOINT_TYPE
|
||||||
|
.db 0x82 ; ep2 dir=IN and address
|
||||||
|
.db ENDPOINT_TYPE_BULK ; type
|
||||||
|
.db 0x40 ; max packet LSB = 64
|
||||||
|
.db 0x00 ; max packet size MSB
|
||||||
|
.db 0x00 ; polling interval
|
||||||
|
|
||||||
|
fullspd_dscr_realend:
|
||||||
|
|
||||||
|
.even
|
||||||
|
_dev_strings:
|
||||||
|
|
||||||
|
; string 0 -- language descriptor
|
||||||
|
_string0:
|
||||||
|
.db string0end-_string0
|
||||||
|
.db DSCR_STRING_TYPE
|
||||||
|
.db 0x09, 0x04 ; English (US)
|
||||||
|
string0end:
|
||||||
|
|
||||||
|
; string 1 -- manufacturer
|
||||||
|
_string1:
|
||||||
|
.db string1end-_string1
|
||||||
|
.db DSCR_STRING_TYPE
|
||||||
|
.ascii 'G'
|
||||||
|
.db 0
|
||||||
|
.ascii 'e'
|
||||||
|
.db 0
|
||||||
|
.ascii 'n'
|
||||||
|
.db 0
|
||||||
|
.ascii 'p'
|
||||||
|
.db 0
|
||||||
|
.ascii 'i'
|
||||||
|
.db 0
|
||||||
|
.ascii 'x'
|
||||||
|
.db 0
|
||||||
|
string1end:
|
||||||
|
|
||||||
|
; string 2 -- product
|
||||||
|
_string2:
|
||||||
|
.db string2end-_string2
|
||||||
|
.db DSCR_STRING_TYPE
|
||||||
|
.ascii 'S'
|
||||||
|
.db 0
|
||||||
|
.ascii 'k'
|
||||||
|
.db 0
|
||||||
|
.ascii 'y'
|
||||||
|
.db 0
|
||||||
|
.ascii 'W'
|
||||||
|
.db 0
|
||||||
|
.ascii 'a'
|
||||||
|
.db 0
|
||||||
|
.ascii 'l'
|
||||||
|
.db 0
|
||||||
|
.ascii 'k'
|
||||||
|
.db 0
|
||||||
|
.ascii 'e'
|
||||||
|
.db 0
|
||||||
|
.ascii 'r'
|
||||||
|
.db 0
|
||||||
|
.ascii '-'
|
||||||
|
.db 0
|
||||||
|
.ascii '1'
|
||||||
|
.db 0
|
||||||
|
.ascii ' '
|
||||||
|
.db 0
|
||||||
|
.ascii 'C'
|
||||||
|
.db 0
|
||||||
|
.ascii 'u'
|
||||||
|
.db 0
|
||||||
|
.ascii 's'
|
||||||
|
.db 0
|
||||||
|
.ascii 't'
|
||||||
|
.db 0
|
||||||
|
.ascii 'o'
|
||||||
|
.db 0
|
||||||
|
.ascii 'm'
|
||||||
|
.db 0
|
||||||
|
string2end:
|
||||||
|
|
||||||
|
; string 3 -- serial number
|
||||||
|
_string3:
|
||||||
|
.db string3end-_string3
|
||||||
|
.db DSCR_STRING_TYPE
|
||||||
|
.ascii '0'
|
||||||
|
.db 0
|
||||||
|
.ascii '0'
|
||||||
|
.db 0
|
||||||
|
.ascii '0'
|
||||||
|
.db 0
|
||||||
|
.ascii '1'
|
||||||
|
.db 0
|
||||||
|
string3end:
|
||||||
|
|
||||||
|
; string 4 -- interface
|
||||||
|
_string4:
|
||||||
|
.db string4end-_string4
|
||||||
|
.db DSCR_STRING_TYPE
|
||||||
|
.ascii 'D'
|
||||||
|
.db 0
|
||||||
|
.ascii 'V'
|
||||||
|
.db 0
|
||||||
|
.ascii 'B'
|
||||||
|
.db 0
|
||||||
|
.ascii '-'
|
||||||
|
.db 0
|
||||||
|
.ascii 'S'
|
||||||
|
.db 0
|
||||||
|
string4end:
|
||||||
|
|
||||||
|
_dev_strings_end:
|
||||||
|
.dw 0x0000
|
||||||
864
firmware/skywalker1.c
Normal file
864
firmware/skywalker1.c
Normal file
@ -0,0 +1,864 @@
|
|||||||
|
/*
|
||||||
|
* 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 <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) */
|
||||||
|
#define BCM4500_ADDR 0x10
|
||||||
|
|
||||||
|
/* 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 ---------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
static BOOL bcm_direct_read(BYTE reg, BYTE *val) {
|
||||||
|
BYTE r = reg;
|
||||||
|
if (!i2c_write(BCM4500_ADDR, 1, &r, 0, NULL))
|
||||||
|
return FALSE;
|
||||||
|
if (!i2c_read(BCM4500_ADDR, 1, val))
|
||||||
|
return FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write a value to a BCM4500 indirect register.
|
||||||
|
* Protocol:
|
||||||
|
* 1. Write page/register to 0xA6
|
||||||
|
* 2. Write data to 0xA7
|
||||||
|
* 3. Write 0x03 to 0xA8 (execute write)
|
||||||
|
*/
|
||||||
|
static BOOL bcm_indirect_write(BYTE reg, BYTE val) {
|
||||||
|
if (!bcm_direct_write(BCM_REG_PAGE, reg))
|
||||||
|
return FALSE;
|
||||||
|
if (!bcm_direct_write(BCM_REG_DATA, val))
|
||||||
|
return FALSE;
|
||||||
|
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_WRITE))
|
||||||
|
return FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read a value from a BCM4500 indirect register.
|
||||||
|
* Protocol:
|
||||||
|
* 1. Write page/register to 0xA6
|
||||||
|
* 2. Write 0x01 to 0xA8 (execute read)
|
||||||
|
* 3. Read data from 0xA7
|
||||||
|
*/
|
||||||
|
static BOOL bcm_indirect_read(BYTE reg, BYTE *val) {
|
||||||
|
if (!bcm_direct_write(BCM_REG_PAGE, reg))
|
||||||
|
return FALSE;
|
||||||
|
if (!bcm_direct_write(BCM_REG_CMD, BCM_CMD_READ))
|
||||||
|
return FALSE;
|
||||||
|
if (!bcm_direct_read(BCM_REG_DATA, val))
|
||||||
|
return FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
616
tools/fw_load.py
Executable file
616
tools/fw_load.py
Executable file
@ -0,0 +1,616 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Genpix SkyWalker-1 RAM firmware loader.
|
||||||
|
|
||||||
|
Loads firmware into the Cypress FX2 (CY7C68013A) internal/external RAM
|
||||||
|
via the standard 0xA0 vendor request. This does NOT touch the EEPROM --
|
||||||
|
power-cycling the device restores the factory-programmed firmware.
|
||||||
|
|
||||||
|
Use case: firmware development and testing. Load, test, power-cycle.
|
||||||
|
|
||||||
|
Loading sequence:
|
||||||
|
1. Halt CPU: write 0x01 to CPUCS register at 0xE600
|
||||||
|
2. Write code segments into RAM
|
||||||
|
3. Start CPU: write 0x00 to CPUCS at 0xE600
|
||||||
|
|
||||||
|
After starting, the FX2 runs the new firmware and typically
|
||||||
|
re-enumerates on USB with new VID/PID/descriptors.
|
||||||
|
|
||||||
|
Supports Intel HEX (.ihx/.hex) and raw binary (.bix/.bin) formats.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
import usb.core
|
||||||
|
import usb.util
|
||||||
|
except ImportError:
|
||||||
|
print("pyusb required: pip install pyusb")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Genpix SkyWalker-1
|
||||||
|
SKYWALKER_VID = 0x09C0
|
||||||
|
SKYWALKER_PID = 0x0203
|
||||||
|
|
||||||
|
# Bare/unprogrammed Cypress FX2 (no EEPROM or blank EEPROM)
|
||||||
|
CYPRESS_VID = 0x04B4
|
||||||
|
CYPRESS_PID = 0x8613
|
||||||
|
|
||||||
|
# FX2 vendor request for RAM access (built into silicon boot ROM)
|
||||||
|
FX2_RAM_REQUEST = 0xA0
|
||||||
|
|
||||||
|
# CPUCS register -- controls 8051 run/halt state
|
||||||
|
CPUCS_ADDR = 0xE600
|
||||||
|
|
||||||
|
# Max bytes per control transfer. The FX2 TRM says 64 bytes for
|
||||||
|
# the control endpoint buffer, so we stay conservative.
|
||||||
|
CHUNK_SIZE = 64
|
||||||
|
|
||||||
|
|
||||||
|
def find_device(force=False):
|
||||||
|
"""Find a SkyWalker-1 or bare FX2 device on USB."""
|
||||||
|
# Try SkyWalker-1 first
|
||||||
|
dev = usb.core.find(idVendor=SKYWALKER_VID, idProduct=SKYWALKER_PID)
|
||||||
|
if dev is not None:
|
||||||
|
print(f"Found SkyWalker-1: Bus {dev.bus} Addr {dev.address} "
|
||||||
|
f"(VID 0x{SKYWALKER_VID:04X} PID 0x{SKYWALKER_PID:04X})")
|
||||||
|
return dev
|
||||||
|
|
||||||
|
# Try bare Cypress FX2
|
||||||
|
dev = usb.core.find(idVendor=CYPRESS_VID, idProduct=CYPRESS_PID)
|
||||||
|
if dev is not None:
|
||||||
|
print(f"Found bare Cypress FX2: Bus {dev.bus} Addr {dev.address} "
|
||||||
|
f"(VID 0x{CYPRESS_VID:04X} PID 0x{CYPRESS_PID:04X})")
|
||||||
|
return dev
|
||||||
|
|
||||||
|
if force:
|
||||||
|
# Last resort: scan for any device the user might want
|
||||||
|
print("No SkyWalker-1 or bare FX2 found. --force is set but no "
|
||||||
|
"target device discovered.")
|
||||||
|
else:
|
||||||
|
print("No SkyWalker-1 (09C0:0203) or bare FX2 (04B4:8613) found.")
|
||||||
|
print("Is the device plugged in?")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def detach_driver(dev):
|
||||||
|
"""Detach kernel driver if attached. Returns interface number or None."""
|
||||||
|
intf_num = None
|
||||||
|
for cfg in dev:
|
||||||
|
for intf in cfg:
|
||||||
|
if dev.is_kernel_driver_active(intf.bInterfaceNumber):
|
||||||
|
try:
|
||||||
|
dev.detach_kernel_driver(intf.bInterfaceNumber)
|
||||||
|
intf_num = intf.bInterfaceNumber
|
||||||
|
except usb.core.USBError as e:
|
||||||
|
print(f"Cannot detach driver: {e}")
|
||||||
|
print("Try: sudo modprobe -r dvb_usb_gp8psk")
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
dev.set_configuration()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return intf_num
|
||||||
|
|
||||||
|
|
||||||
|
def fx2_ram_write(dev, addr, data):
|
||||||
|
"""Write bytes to FX2 RAM at the given address via vendor request 0xA0."""
|
||||||
|
return dev.ctrl_transfer(
|
||||||
|
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_OUT,
|
||||||
|
FX2_RAM_REQUEST, addr, 0, data, 2000)
|
||||||
|
|
||||||
|
|
||||||
|
def fx2_ram_read(dev, addr, length):
|
||||||
|
"""Read bytes from FX2 RAM at the given address via vendor request 0xA0."""
|
||||||
|
try:
|
||||||
|
data = dev.ctrl_transfer(
|
||||||
|
usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_IN,
|
||||||
|
FX2_RAM_REQUEST, addr, 0, length, 2000)
|
||||||
|
return bytes(data)
|
||||||
|
except usb.core.USBError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def cpu_halt(dev):
|
||||||
|
"""Halt the FX2 8051 CPU by writing 0x01 to CPUCS."""
|
||||||
|
fx2_ram_write(dev, CPUCS_ADDR, bytes([0x01]))
|
||||||
|
|
||||||
|
|
||||||
|
def cpu_start(dev):
|
||||||
|
"""Start the FX2 8051 CPU by writing 0x00 to CPUCS."""
|
||||||
|
fx2_ram_write(dev, CPUCS_ADDR, bytes([0x00]))
|
||||||
|
|
||||||
|
|
||||||
|
# -- Intel HEX parser --
|
||||||
|
|
||||||
|
def parse_ihx(data):
|
||||||
|
"""
|
||||||
|
Parse an Intel HEX file. Returns list of (address, bytes) segments.
|
||||||
|
|
||||||
|
Record types:
|
||||||
|
00 = data
|
||||||
|
01 = EOF
|
||||||
|
02 = extended segment address (shifts base by 16)
|
||||||
|
04 = extended linear address (shifts base by 16)
|
||||||
|
"""
|
||||||
|
segments = []
|
||||||
|
base_addr = 0
|
||||||
|
line_num = 0
|
||||||
|
|
||||||
|
for raw_line in data.splitlines():
|
||||||
|
line_num += 1
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if isinstance(line, bytes):
|
||||||
|
line = line.decode('ascii', errors='replace')
|
||||||
|
|
||||||
|
if not line.startswith(':'):
|
||||||
|
raise ValueError(f"Line {line_num}: missing start code ':'")
|
||||||
|
|
||||||
|
# Strip the colon and decode hex
|
||||||
|
hex_str = line[1:]
|
||||||
|
if len(hex_str) < 10:
|
||||||
|
raise ValueError(f"Line {line_num}: too short")
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw = bytes.fromhex(hex_str)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f"Line {line_num}: invalid hex")
|
||||||
|
|
||||||
|
byte_count = raw[0]
|
||||||
|
addr = (raw[1] << 8) | raw[2]
|
||||||
|
rec_type = raw[3]
|
||||||
|
rec_data = raw[4:4 + byte_count]
|
||||||
|
checksum = raw[4 + byte_count]
|
||||||
|
|
||||||
|
# Verify checksum (two's complement of sum of all bytes before it)
|
||||||
|
calc_sum = sum(raw[:4 + byte_count]) & 0xFF
|
||||||
|
calc_check = (~calc_sum + 1) & 0xFF
|
||||||
|
if checksum != calc_check:
|
||||||
|
raise ValueError(
|
||||||
|
f"Line {line_num}: checksum mismatch "
|
||||||
|
f"(expected 0x{calc_check:02X}, got 0x{checksum:02X})")
|
||||||
|
|
||||||
|
if len(rec_data) != byte_count:
|
||||||
|
raise ValueError(
|
||||||
|
f"Line {line_num}: data length mismatch "
|
||||||
|
f"(header says {byte_count}, got {len(rec_data)})")
|
||||||
|
|
||||||
|
if rec_type == 0x00:
|
||||||
|
# Data record
|
||||||
|
full_addr = base_addr + addr
|
||||||
|
segments.append((full_addr, bytes(rec_data)))
|
||||||
|
|
||||||
|
elif rec_type == 0x01:
|
||||||
|
# EOF
|
||||||
|
break
|
||||||
|
|
||||||
|
elif rec_type == 0x02:
|
||||||
|
# Extended segment address
|
||||||
|
if byte_count != 2:
|
||||||
|
raise ValueError(
|
||||||
|
f"Line {line_num}: type 02 record must have 2 data bytes")
|
||||||
|
base_addr = ((rec_data[0] << 8) | rec_data[1]) << 4
|
||||||
|
|
||||||
|
elif rec_type == 0x04:
|
||||||
|
# Extended linear address
|
||||||
|
if byte_count != 2:
|
||||||
|
raise ValueError(
|
||||||
|
f"Line {line_num}: type 04 record must have 2 data bytes")
|
||||||
|
base_addr = ((rec_data[0] << 8) | rec_data[1]) << 16
|
||||||
|
|
||||||
|
# Silently ignore unknown record types (03, 05, etc.)
|
||||||
|
|
||||||
|
return segments
|
||||||
|
|
||||||
|
|
||||||
|
def coalesce_segments(segments):
|
||||||
|
"""
|
||||||
|
Merge adjacent/overlapping segments into contiguous blocks.
|
||||||
|
Returns list of (address, bytes) with no gaps.
|
||||||
|
"""
|
||||||
|
if not segments:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Sort by address
|
||||||
|
sorted_segs = sorted(segments, key=lambda s: s[0])
|
||||||
|
|
||||||
|
merged = []
|
||||||
|
cur_addr, cur_data = sorted_segs[0]
|
||||||
|
cur_data = bytearray(cur_data)
|
||||||
|
|
||||||
|
for addr, data in sorted_segs[1:]:
|
||||||
|
cur_end = cur_addr + len(cur_data)
|
||||||
|
if addr <= cur_end:
|
||||||
|
# Overlapping or adjacent -- extend or overwrite
|
||||||
|
overlap = cur_end - addr
|
||||||
|
if overlap >= 0:
|
||||||
|
cur_data.extend(data[overlap:] if overlap < len(data) else b'')
|
||||||
|
else:
|
||||||
|
# Gap -- pad with zeros (shouldn't happen after sort, but safe)
|
||||||
|
cur_data.extend(b'\x00' * (-overlap))
|
||||||
|
cur_data.extend(data)
|
||||||
|
else:
|
||||||
|
merged.append((cur_addr, bytes(cur_data)))
|
||||||
|
cur_addr = addr
|
||||||
|
cur_data = bytearray(data)
|
||||||
|
|
||||||
|
merged.append((cur_addr, bytes(cur_data)))
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
|
def load_firmware_file(path):
|
||||||
|
"""
|
||||||
|
Load firmware from .ihx/.hex (Intel HEX) or .bix/.bin (raw binary).
|
||||||
|
Returns list of (address, bytes) segments.
|
||||||
|
"""
|
||||||
|
ext = os.path.splitext(path)[1].lower()
|
||||||
|
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
raw = f.read()
|
||||||
|
|
||||||
|
if ext in ('.ihx', '.hex'):
|
||||||
|
segments = parse_ihx(raw)
|
||||||
|
segments = coalesce_segments(segments)
|
||||||
|
return segments
|
||||||
|
|
||||||
|
elif ext in ('.bix', '.bin'):
|
||||||
|
# Raw binary loads at address 0x0000
|
||||||
|
if not raw:
|
||||||
|
print(f"Empty file: {path}")
|
||||||
|
sys.exit(1)
|
||||||
|
return [(0x0000, raw)]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Try to auto-detect: if it starts with ':', assume Intel HEX
|
||||||
|
if raw.startswith(b':'):
|
||||||
|
segments = parse_ihx(raw)
|
||||||
|
segments = coalesce_segments(segments)
|
||||||
|
return segments
|
||||||
|
else:
|
||||||
|
# Treat as raw binary
|
||||||
|
return [(0x0000, raw)]
|
||||||
|
|
||||||
|
|
||||||
|
def write_segments(dev, segments, verbose=False):
|
||||||
|
"""
|
||||||
|
Write firmware segments to FX2 RAM in CHUNK_SIZE pieces.
|
||||||
|
Returns total bytes written.
|
||||||
|
"""
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
for seg_addr, seg_data in segments:
|
||||||
|
seg_len = len(seg_data)
|
||||||
|
seg_end = seg_addr + seg_len - 1
|
||||||
|
print(f" 0x{seg_addr:04X}-0x{seg_end:04X} ({seg_len} bytes)")
|
||||||
|
|
||||||
|
offset = 0
|
||||||
|
while offset < seg_len:
|
||||||
|
chunk_len = min(CHUNK_SIZE, seg_len - offset)
|
||||||
|
chunk = seg_data[offset:offset + chunk_len]
|
||||||
|
addr = seg_addr + offset
|
||||||
|
|
||||||
|
try:
|
||||||
|
written = fx2_ram_write(dev, addr, chunk)
|
||||||
|
if written != chunk_len:
|
||||||
|
print(f"\n Short write at 0x{addr:04X}: "
|
||||||
|
f"sent {chunk_len}, wrote {written}")
|
||||||
|
except usb.core.USBError as e:
|
||||||
|
print(f"\n Write error at 0x{addr:04X}: {e}")
|
||||||
|
return total
|
||||||
|
|
||||||
|
if verbose and offset % 0x400 == 0:
|
||||||
|
pct = offset * 100 // seg_len
|
||||||
|
print(f"\r 0x{addr:04X} [{pct:3d}%]", end="", flush=True)
|
||||||
|
|
||||||
|
total += chunk_len
|
||||||
|
offset += chunk_len
|
||||||
|
|
||||||
|
if verbose and seg_len > CHUNK_SIZE:
|
||||||
|
print(f"\r 0x{seg_addr + seg_len - 1:04X} [100%] ")
|
||||||
|
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
# -- Subcommand handlers --
|
||||||
|
|
||||||
|
def cmd_load(args):
|
||||||
|
"""Load firmware into FX2 RAM."""
|
||||||
|
if not os.path.exists(args.file):
|
||||||
|
print(f"File not found: {args.file}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Parse firmware file
|
||||||
|
segments = load_firmware_file(args.file)
|
||||||
|
if not segments:
|
||||||
|
print("No code segments found in firmware file")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
total_bytes = sum(len(d) for _, d in segments)
|
||||||
|
min_addr = min(a for a, _ in segments)
|
||||||
|
max_addr = max(a + len(d) - 1 for a, d in segments)
|
||||||
|
|
||||||
|
print(f"SkyWalker-1 RAM Firmware Loader")
|
||||||
|
print(f"{'=' * 40}")
|
||||||
|
print(f"\nFirmware: {args.file}")
|
||||||
|
print(f" Segments: {len(segments)}")
|
||||||
|
print(f" Total size: {total_bytes} bytes")
|
||||||
|
print(f" Address: 0x{min_addr:04X} - 0x{max_addr:04X}")
|
||||||
|
|
||||||
|
# Check for CPUCS region overlap (warn but don't block)
|
||||||
|
for addr, data in segments:
|
||||||
|
seg_end = addr + len(data) - 1
|
||||||
|
if addr <= CPUCS_ADDR <= seg_end:
|
||||||
|
print(f"\n WARNING: Segment at 0x{addr:04X}-0x{seg_end:04X} "
|
||||||
|
f"overlaps CPUCS (0x{CPUCS_ADDR:04X})")
|
||||||
|
print(f" The CPU halt/start writes to 0xE600 will clobber "
|
||||||
|
f"this region")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Connect
|
||||||
|
dev = find_device(force=args.force)
|
||||||
|
|
||||||
|
# Check VID/PID if it's not a known device
|
||||||
|
vid = dev.idVendor
|
||||||
|
pid = dev.idProduct
|
||||||
|
is_skywalker = (vid == SKYWALKER_VID and pid == SKYWALKER_PID)
|
||||||
|
is_bare_fx2 = (vid == CYPRESS_VID and pid == CYPRESS_PID)
|
||||||
|
|
||||||
|
if not is_skywalker and not is_bare_fx2 and not args.force:
|
||||||
|
print(f"\n Unknown device VID 0x{vid:04X} PID 0x{pid:04X}")
|
||||||
|
print(f" Expected SkyWalker-1 (09C0:0203) or bare FX2 (04B4:8613)")
|
||||||
|
print(f" Use --force to override")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
intf = detach_driver(dev)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Step 1: Halt CPU
|
||||||
|
if not args.no_reset:
|
||||||
|
print("\n[1/3] Halting CPU (CPUCS = 0x01)...")
|
||||||
|
cpu_halt(dev)
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
# Verify halt
|
||||||
|
readback = fx2_ram_read(dev, CPUCS_ADDR, 1)
|
||||||
|
if readback and readback[0] & 0x01:
|
||||||
|
print(" CPU halted")
|
||||||
|
else:
|
||||||
|
val = f"0x{readback[0]:02X}" if readback else "read failed"
|
||||||
|
print(f" WARNING: CPUCS readback = {val} (expected 0x01)")
|
||||||
|
print(" Proceeding anyway...")
|
||||||
|
else:
|
||||||
|
print("\n[1/3] Skipping CPU reset (--no-reset)")
|
||||||
|
|
||||||
|
# Step 2: Load segments
|
||||||
|
step = "2/3" if not args.no_reset else "2/2"
|
||||||
|
print(f"\n[{step}] Loading {len(segments)} segment(s) into RAM...")
|
||||||
|
written = write_segments(dev, segments, verbose=args.verbose)
|
||||||
|
print(f"\n {written} bytes loaded")
|
||||||
|
|
||||||
|
if written != total_bytes:
|
||||||
|
print(f" WARNING: expected {total_bytes}, wrote {written}")
|
||||||
|
|
||||||
|
# Step 3: Start CPU
|
||||||
|
if not args.no_reset:
|
||||||
|
print(f"\n[3/3] Starting CPU (CPUCS = 0x00)...")
|
||||||
|
cpu_start(dev)
|
||||||
|
print(" CPU released")
|
||||||
|
print(f"\n Firmware is running. The device will re-enumerate")
|
||||||
|
print(f" with new USB descriptors if the firmware does so.")
|
||||||
|
|
||||||
|
if args.wait:
|
||||||
|
_wait_for_reenumeration(args.wait)
|
||||||
|
else:
|
||||||
|
print(f"\n Segments loaded (CPU not reset)")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Only re-attach if we didn't just start new firmware
|
||||||
|
# (the device may have already re-enumerated away)
|
||||||
|
if args.no_reset and intf is not None:
|
||||||
|
try:
|
||||||
|
usb.util.release_interface(dev, intf)
|
||||||
|
dev.attach_kernel_driver(intf)
|
||||||
|
print("\nRe-attached kernel driver")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_for_reenumeration(timeout):
|
||||||
|
"""Wait for a USB device to re-appear after firmware load."""
|
||||||
|
print(f"\n Waiting up to {timeout}s for re-enumeration...")
|
||||||
|
deadline = time.time() + timeout
|
||||||
|
time.sleep(1.0) # Give the device a moment to disconnect
|
||||||
|
|
||||||
|
while time.time() < deadline:
|
||||||
|
# Check for SkyWalker-1 with potentially new VID/PID
|
||||||
|
# After loading custom firmware, VID/PID may differ
|
||||||
|
dev = usb.core.find(idVendor=SKYWALKER_VID, idProduct=SKYWALKER_PID)
|
||||||
|
if dev is not None:
|
||||||
|
print(f" Device re-appeared: Bus {dev.bus} Addr {dev.address} "
|
||||||
|
f"(0x{SKYWALKER_VID:04X}:0x{SKYWALKER_PID:04X})")
|
||||||
|
return
|
||||||
|
|
||||||
|
dev = usb.core.find(idVendor=CYPRESS_VID, idProduct=CYPRESS_PID)
|
||||||
|
if dev is not None:
|
||||||
|
print(f" Device re-appeared: Bus {dev.bus} Addr {dev.address} "
|
||||||
|
f"(0x{CYPRESS_VID:04X}:0x{CYPRESS_PID:04X})")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(".", end="", flush=True)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
print(f"\n Timeout -- device did not re-enumerate within {timeout}s")
|
||||||
|
print(f" The firmware may use different VID/PID. Check 'lsusb'.")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_reset(args):
|
||||||
|
"""Reset the FX2 CPU (halt then start)."""
|
||||||
|
print(f"SkyWalker-1 CPU Reset")
|
||||||
|
print(f"{'=' * 40}")
|
||||||
|
|
||||||
|
dev = find_device(force=args.force)
|
||||||
|
intf = detach_driver(dev)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("\nHalting CPU...")
|
||||||
|
cpu_halt(dev)
|
||||||
|
time.sleep(0.05)
|
||||||
|
print(" CPUCS = 0x01 (halted)")
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
print("Starting CPU...")
|
||||||
|
cpu_start(dev)
|
||||||
|
print(" CPUCS = 0x00 (running)")
|
||||||
|
print("\nCPU reset complete. Device will re-enumerate.")
|
||||||
|
|
||||||
|
if args.wait:
|
||||||
|
_wait_for_reenumeration(args.wait)
|
||||||
|
finally:
|
||||||
|
pass # Device is likely gone after reset
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_read(args):
|
||||||
|
"""Read and hex-dump FX2 RAM contents."""
|
||||||
|
addr = args.addr
|
||||||
|
length = args.length
|
||||||
|
|
||||||
|
print(f"SkyWalker-1 RAM Read")
|
||||||
|
print(f"{'=' * 40}")
|
||||||
|
|
||||||
|
dev = find_device(force=args.force)
|
||||||
|
intf = detach_driver(dev)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"\nReading {length} bytes from 0x{addr:04X}...\n")
|
||||||
|
|
||||||
|
data = bytearray()
|
||||||
|
offset = 0
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
while offset < length:
|
||||||
|
chunk_len = min(CHUNK_SIZE, length - offset)
|
||||||
|
chunk = fx2_ram_read(dev, addr + offset, chunk_len)
|
||||||
|
if chunk is None:
|
||||||
|
errors += 1
|
||||||
|
data.extend(b'\xff' * chunk_len)
|
||||||
|
else:
|
||||||
|
data.extend(chunk)
|
||||||
|
offset += chunk_len
|
||||||
|
|
||||||
|
# Hex dump output
|
||||||
|
for i in range(0, len(data), 16):
|
||||||
|
row = data[i:i + 16]
|
||||||
|
hex_part = ' '.join(f'{b:02X}' for b in row)
|
||||||
|
ascii_part = ''.join(chr(b) if 0x20 <= b < 0x7F else '.' for b in row)
|
||||||
|
print(f" {addr + i:04X}: {hex_part:<48s} {ascii_part}")
|
||||||
|
|
||||||
|
print(f"\n {len(data)} bytes read, {errors} chunk errors")
|
||||||
|
|
||||||
|
if args.output:
|
||||||
|
with open(args.output, 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
print(f" Saved to: {args.output}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if intf is not None:
|
||||||
|
try:
|
||||||
|
usb.util.release_interface(dev, intf)
|
||||||
|
dev.attach_kernel_driver(intf)
|
||||||
|
print("\nRe-attached kernel driver")
|
||||||
|
except:
|
||||||
|
print("\nNote: run 'sudo modprobe dvb_usb_gp8psk' to reload")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="SkyWalker-1 RAM firmware loader (FX2 vendor request 0xA0)",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""\
|
||||||
|
examples:
|
||||||
|
%(prog)s load firmware.ihx
|
||||||
|
%(prog)s load firmware.bix --wait 5
|
||||||
|
%(prog)s load firmware.ihx --no-reset
|
||||||
|
%(prog)s reset
|
||||||
|
%(prog)s read --addr 0x0000 --len 256
|
||||||
|
%(prog)s read --addr 0xe600 --len 1
|
||||||
|
|
||||||
|
This tool loads firmware into RAM only -- the EEPROM is never touched.
|
||||||
|
Power-cycle the device to restore the factory-programmed firmware.
|
||||||
|
""")
|
||||||
|
parser.add_argument('-v', '--verbose', action='store_true',
|
||||||
|
help="Show detailed transfer progress")
|
||||||
|
parser.add_argument('--force', action='store_true',
|
||||||
|
help="Allow loading to unknown VID/PID devices")
|
||||||
|
|
||||||
|
sub = parser.add_subparsers(dest='command')
|
||||||
|
|
||||||
|
# load (default)
|
||||||
|
p_load = sub.add_parser('load',
|
||||||
|
help='Load firmware into FX2 RAM')
|
||||||
|
p_load.add_argument('file', help='Firmware file (.ihx, .hex, .bix, .bin)')
|
||||||
|
p_load.add_argument('--no-reset', action='store_true',
|
||||||
|
help="Load without halting/starting the CPU")
|
||||||
|
p_load.add_argument('--wait', type=float, default=0, metavar='SECONDS',
|
||||||
|
help="Wait for USB re-enumeration after load")
|
||||||
|
p_load.add_argument('-v', '--verbose', action='store_true',
|
||||||
|
help="Show detailed transfer progress")
|
||||||
|
p_load.add_argument('--force', action='store_true',
|
||||||
|
help="Allow loading to unknown VID/PID devices")
|
||||||
|
|
||||||
|
# reset
|
||||||
|
p_reset = sub.add_parser('reset',
|
||||||
|
help='Reset the FX2 CPU (halt then start)')
|
||||||
|
p_reset.add_argument('--wait', type=float, default=0, metavar='SECONDS',
|
||||||
|
help="Wait for USB re-enumeration after reset")
|
||||||
|
p_reset.add_argument('--force', action='store_true',
|
||||||
|
help="Allow reset on unknown VID/PID devices")
|
||||||
|
|
||||||
|
# read
|
||||||
|
p_read = sub.add_parser('read',
|
||||||
|
help='Read and hex-dump FX2 RAM')
|
||||||
|
p_read.add_argument('--addr', type=lambda x: int(x, 0), default=0x0000,
|
||||||
|
help="Start address (default: 0x0000)")
|
||||||
|
p_read.add_argument('--len', dest='length', type=lambda x: int(x, 0),
|
||||||
|
default=256,
|
||||||
|
help="Number of bytes to read (default: 256)")
|
||||||
|
p_read.add_argument('-o', '--output', metavar='FILE',
|
||||||
|
help="Save raw bytes to file")
|
||||||
|
p_read.add_argument('--force', action='store_true',
|
||||||
|
help="Allow read on unknown VID/PID devices")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Default to 'load' if a positional arg is given but no subcommand
|
||||||
|
if not args.command:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Propagate top-level flags to subcommands
|
||||||
|
if hasattr(args, 'verbose') and not args.verbose:
|
||||||
|
args.verbose = parser.parse_args().verbose
|
||||||
|
if hasattr(args, 'force') and not args.force:
|
||||||
|
args.force = parser.parse_args().force
|
||||||
|
|
||||||
|
dispatch = {
|
||||||
|
'load': cmd_load,
|
||||||
|
'reset': cmd_reset,
|
||||||
|
'read': cmd_read,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = dispatch.get(args.command)
|
||||||
|
if handler is None:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
handler(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user