diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp index 67f4faf8d..fc28ae97a 100644 --- a/firmware/application/portapack.cpp +++ b/firmware/application/portapack.cpp @@ -636,7 +636,7 @@ init_status_t init() { // This function returns LD_SUCCESS (0) if the FPGA confirms the bitstream // Call fpga_bridge_init and continue boot regardless of result // (Watchdog was resetting device when we halted with while(1)) - int load_result = fpga_bridge_init(); + int load_result = fpga_bridge_init(&shared_memory.bb_data.data[0]); (void)load_result; // Ignore result for now, just let boot continue /* RELEASE FPGA RESET */ diff --git a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c index d11c2ee40..3b08b2eda 100644 --- a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c +++ b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c @@ -3,620 +3,624 @@ // Check if PRALINE was passed from CMake #ifdef PRALINE - #include "fpga_bridge.h" - - // Necessary headers - #include "lz4_blk.h" - - // LIBOPENCM3 Headers (only CGU for clock setup) - #include - - #include - #include - #include - #include - - // SPIFI memory-mapped base address - // Flash is mapped starting at 0x14000000 - // FPGA bitstream at flash address 0x380000 = memory address 0x14380000 - // PRALINE: Moved to 1.5MB offset to allow larger base firmware - #define SPIFI_DATA_BASE 0x14000000 - #define FPGA_BITSTREAM_FLASH_ADDR 0x380000 // Was 0x100000 (1MB), then 0x180000 (1.5MB), now 0x380000 (3.5MB) - #define FPGA_BITSTREAM_MEM_ADDR (SPIFI_DATA_BASE + FPGA_BITSTREAM_FLASH_ADDR) - - // MMIO32 direct register access - #define MMIO32_LOCAL(addr) (*(volatile uint32_t*)(addr)) - - // SSP1 base address - #define SSP1_BASE_LOCAL 0x400C5000 - - // SSP register offsets - #define SSP_CR0_OFF 0x000 - #define SSP_CR1_OFF 0x004 - #define SSP_DR_OFF 0x008 - #define SSP_SR_OFF 0x00C - #define SSP_CPSR_OFF 0x010 - - // SSP register access - #define SSP1_CR0_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_CR0_OFF) - #define SSP1_CR1_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_CR1_OFF) - #define SSP1_DR_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_DR_OFF) - #define SSP1_SR_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_SR_OFF) - #define SSP1_CPSR_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_CPSR_OFF) - - // SSP status bits - #define SSP_SR_TNF_LOCAL (1 << 1) // TX FIFO not full - #define SSP_SR_RNE_LOCAL (1 << 2) // RX FIFO not empty - #define SSP_SR_BSY_LOCAL (1 << 4) // Busy - - // SSP CR0 bits - #define SSP_CR0_DSS_8BIT (0x7) // 8-bit data - #define SSP_CR0_FRF_SPI (0x0) // SPI frame format - #define SSP_CR0_CPOL (1 << 6) // Clock polarity - #define SSP_CR0_CPHA (1 << 7) // Clock phase - - // SSP CR1 bits - #define SSP_CR1_SSE (1 << 1) // SSP enable - - // SCU pin configuration registers - #define PERIPH_BASE_APB0_LOCAL 0x40080000 - #define SCU_BASE_LOCAL (PERIPH_BASE_APB0_LOCAL + 0x06000) - #define PIN_GROUP1_LOCAL (SCU_BASE_LOCAL + 0x080) - #define PIN_GROUP4_LOCAL (SCU_BASE_LOCAL + 0x200) - #define PIN_GROUP5_LOCAL (SCU_BASE_LOCAL + 0x280) - #define PIN3_LOCAL 0x00C - #define PIN4_LOCAL 0x010 - #define PIN1_LOCAL 0x004 - #define PIN2_LOCAL 0x008 - #define PIN10_LOCAL 0x028 - #define PIN19_LOCAL 0x04C - - // SCU configuration flags - #define SCU_CONF_EPUN_DIS_PULLUP_LOCAL (1 << 4) - #define SCU_CONF_EHS_FAST_LOCAL (1 << 5) - #define SCU_CONF_EZI_EN_IN_BUFFER_LOCAL (1 << 6) - #define SCU_CONF_ZIF_DIS_IN_GLITCH_FILT_LOCAL (1 << 7) - #define SCU_GPIO_FAST_LOCAL (SCU_CONF_EPUN_DIS_PULLUP_LOCAL | \ - SCU_CONF_EHS_FAST_LOCAL | \ - SCU_CONF_EZI_EN_IN_BUFFER_LOCAL | \ - SCU_CONF_ZIF_DIS_IN_GLITCH_FILT_LOCAL) - #define SCU_SSP_IO_LOCAL SCU_GPIO_FAST_LOCAL - - // Function select values - #define SCU_CONF_FUNCTION0_LOCAL (0x0) - #define SCU_CONF_FUNCTION1_LOCAL (0x1) - #define SCU_CONF_FUNCTION4_LOCAL (0x4) - #define SCU_CONF_FUNCTION5_LOCAL (0x5) - #define SCU_GPIO_NOPULL_LOCAL (SCU_CONF_EZI_EN_IN_BUFFER_LOCAL | SCU_CONF_ZIF_DIS_IN_GLITCH_FILT_LOCAL) - #define SCU_GPIO_PUP_LOCAL (SCU_CONF_EZI_EN_IN_BUFFER_LOCAL) - - // SSP1 pins (for FPGA programming) - #define SCU_SSP1_CIPO_LOCAL (PIN_GROUP1_LOCAL + PIN3_LOCAL) // P1_3 - #define SCU_SSP1_COPI_LOCAL (PIN_GROUP1_LOCAL + PIN4_LOCAL) // P1_4 - #define SCU_SSP1_SCK_LOCAL (PIN_GROUP1_LOCAL + PIN19_LOCAL) // P1_19 - - // FPGA control pins - #define SCU_FPGA_CRESET_LOCAL (PIN_GROUP5_LOCAL + PIN2_LOCAL) // P5_2 GPIO2[11] - #define SCU_FPGA_CDONE_LOCAL (PIN_GROUP4_LOCAL + PIN10_LOCAL) // P4_10 GPIO5[14] - #define SCU_FPGA_SPI_CS_LOCAL (PIN_GROUP5_LOCAL + PIN1_LOCAL) // P5_1 GPIO2[10] - - // GPIO register addresses for direct MMIO access - #define GPIO_LPC_BASE_LOCAL 0x400F4000 - #define GPIO_DIR_BASE (GPIO_LPC_BASE_LOCAL + 0x2000) // Direction registers - #define GPIO_SET_BASE (GPIO_LPC_BASE_LOCAL + 0x2200) // Set registers - #define GPIO_CLR_BASE (GPIO_LPC_BASE_LOCAL + 0x2280) // Clear registers - #define GPIO_PIN_BASE (GPIO_LPC_BASE_LOCAL + 0x2100) // Pin read registers - - // GPIO port access macros - #define GPIO_DIR(port) MMIO32_LOCAL(GPIO_DIR_BASE + (port)*4) - #define GPIO_SET(port) MMIO32_LOCAL(GPIO_SET_BASE + (port)*4) - #define GPIO_CLR(port) MMIO32_LOCAL(GPIO_CLR_BASE + (port)*4) - #define GPIO_PIN(port) MMIO32_LOCAL(GPIO_PIN_BASE + (port)*4) - - // FPGA control GPIO pins - // GPIO2[11] = CRESET, GPIO5[14] = CDONE, GPIO2[10] = SPI_CS - #define FPGA_CRESET_PORT 2 - #define FPGA_CRESET_PIN 11 - #define FPGA_CDONE_PORT 5 - #define FPGA_CDONE_PIN 14 - #define FPGA_SPI_CS_PORT 2 - #define FPGA_SPI_CS_PIN 10 - - // ============================================================================ - // Canonical Default Values - SINGLE SOURCE OF TRUTH - // ============================================================================ - /* Define the canonical RX defaults in ONE place */ - #define FPGA_RX_DEFAULT_DC_WIDTH 0x04 /* Typical for 40MHz stability */ - #define FPGA_RX_DEFAULT_ADAPT_RATE 0x08 /* Typical for 40MHz stability */ - #define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00 /* No shift initially */ - - /* Define TX defaults */ - #define FPGA_TX_DEFAULT_NCO_CTRL 0x00 /* NCO disabled */ - #define FPGA_TX_DEFAULT_INTERP 0x00 /* No interpolation */ - #define FPGA_TX_DEFAULT_PHASE_STEP 0x00 /* Zero phase step */ - - - // ============================================================================ - // Static Variables - Declare BEFORE use - // ============================================================================ - static fpga_mode_t current_mode = FPGA_MODE_OFF; - // Cached register values for debug reads (since reads may require mode switch) - static uint8_t fpga_reg_cache[6] = {0, 0x01, 0x00, 0x00, 0x00, 0x00}; - - // Context structure for SPIFI-based reading - struct spifi_fpga_read_ctx { - const uint8_t* mem_ptr; // Current read position in SPIFI memory - size_t next_block_sz; - uint8_t init_flag; - uint8_t buffer[4096 + 2]; // Compressed block + next size - }; - - // ============================================================================ - // Low-Level Helper Functions - // ============================================================================ - - // Simple delay loop - static void delay_cycles(volatile uint32_t count) { - while (count--) { - __asm__ volatile ("nop"); - } - } - - // Microsecond delay (approximate, assuming ~200MHz clock) - static void delay_us(uint32_t us) { - // ~50 cycles per microsecond at 200MHz - delay_cycles(us * 50); - } - - // SSP1 transfer one byte - static uint8_t ssp1_transfer_byte(uint8_t data) { - // Wait for TX FIFO not full - while ((SSP1_SR_LOCAL & SSP_SR_TNF_LOCAL) == 0) {} - SSP1_DR_LOCAL = data; - // Wait for not busy - while (SSP1_SR_LOCAL & SSP_SR_BSY_LOCAL) {} - // Wait for RX FIFO not empty - while ((SSP1_SR_LOCAL & SSP_SR_RNE_LOCAL) == 0) {} - return SSP1_DR_LOCAL; - } - - // Configure SSP1 for iCE40 programming (SPI mode 3: CPOL=1, CPHA=1) - static void ssp1_init_ice40(void) { - // Disable SSP1 first - SSP1_CR1_LOCAL = 0; - - // Configure: 8-bit, SPI mode 3 (CPOL=1, CPHA=1), master mode - // SCR=21 for ~4MHz at 200MHz clock - SSP1_CR0_LOCAL = SSP_CR0_DSS_8BIT | SSP_CR0_FRF_SPI | SSP_CR0_CPOL | SSP_CR0_CPHA | (21 << 8); - - // Clock prescaler = 2 (divide by 2) - SSP1_CPSR_LOCAL = 2; - - // Enable SSP1 - SSP1_CR1_LOCAL = SSP_CR1_SSE; - } - - // Configure SSP1 pins via SCU - static void configure_ssp1_pins(void) { - // P1_3 = SSP1_MISO (function 5) - MMIO32_LOCAL(SCU_SSP1_CIPO_LOCAL) = SCU_SSP_IO_LOCAL | SCU_CONF_FUNCTION5_LOCAL; - // P1_4 = SSP1_MOSI (function 5) - MMIO32_LOCAL(SCU_SSP1_COPI_LOCAL) = SCU_SSP_IO_LOCAL | SCU_CONF_FUNCTION5_LOCAL; - // P1_19 = SSP1_SCK (function 1) - MMIO32_LOCAL(SCU_SSP1_SCK_LOCAL) = SCU_SSP_IO_LOCAL | SCU_CONF_FUNCTION1_LOCAL; - } - - // Configure FPGA control pins via SCU and GPIO - static void configure_fpga_control_pins(void) { - // P5_2 = GPIO2[11] = CRESET (function 0, output) - MMIO32_LOCAL(SCU_FPGA_CRESET_LOCAL) = SCU_GPIO_NOPULL_LOCAL | SCU_CONF_FUNCTION0_LOCAL; - // P4_10 = GPIO5[14] = CDONE (function 4, input with pullup) - MMIO32_LOCAL(SCU_FPGA_CDONE_LOCAL) = SCU_GPIO_PUP_LOCAL | SCU_CONF_FUNCTION4_LOCAL; - // P5_1 = GPIO2[10] = SPI_CS (function 0, output) - MMIO32_LOCAL(SCU_FPGA_SPI_CS_LOCAL) = SCU_GPIO_NOPULL_LOCAL | SCU_CONF_FUNCTION0_LOCAL; - - // Set CRESET and SPI_CS as outputs (GPIO2[11] and GPIO2[10]) - GPIO_DIR(FPGA_CRESET_PORT) |= (1 << FPGA_CRESET_PIN) | (1 << FPGA_SPI_CS_PIN); - // Clear both initially - GPIO_CLR(FPGA_CRESET_PORT) = (1 << FPGA_CRESET_PIN) | (1 << FPGA_SPI_CS_PIN); - - // CDONE is input (GPIO5[14]) - GPIO_DIR(FPGA_CDONE_PORT) &= ~(1 << FPGA_CDONE_PIN); - } - - // GPIO control helpers - static void fpga_creset_low(void) { - GPIO_CLR(FPGA_CRESET_PORT) = (1 << FPGA_CRESET_PIN); - } - static void fpga_creset_high(void) { - GPIO_SET(FPGA_CRESET_PORT) = (1 << FPGA_CRESET_PIN); - } - static void fpga_cs_low(void) { - GPIO_CLR(FPGA_SPI_CS_PORT) = (1 << FPGA_SPI_CS_PIN); - } - static void fpga_cs_high(void) { - GPIO_SET(FPGA_SPI_CS_PORT) = (1 << FPGA_SPI_CS_PIN); - } - static bool fpga_cdone_read(void) { - return (GPIO_PIN(FPGA_CDONE_PORT) & (1 << FPGA_CDONE_PIN)) != 0; - } - - // ============================================================================ - // FPGA Register Access via SPI (iCE40) - // ============================================================================ - // These functions allow reading/writing FPGA internal registers via SPI. - // The FPGA bitstream implements a simple SPI register interface. - // - // FPGA Register Map: - // Reg 1 (CTRL): DC_BLOCK(b0), QUARTER_SHIFT_EN(b1), QUARTER_SHIFT_UP(b2), PRBS(b6), TRIGGER_EN(b7) - // Reg 2 (RX_DECIM): Decimation ratio [2:0] - // Reg 3 (RX/TX): RX Digital Shift OR TX NCO Control - // Reg 4 (RX_DC_BLOCK_WIDTH/TX_INTERP) [2:0] - // Reg 5 (RX_DC_ADAPT_RATE/TX_PSTEP) [7:0] - // - // SPI Protocol: - // Read: Send [reg & 0x7F, 0x00, 0x00] -> value in byte 3 - // Write: Send [(reg | 0x80), value, 0x00] - - - // ============================================================================ - // SPI Mode Switching - // ============================================================================ - - // Configure SSP1 for iCE40 FPGA register access (Mode 3, 8-bit) - static void ssp1_set_mode_ice40(void) { - SSP1_CR1_LOCAL = 0; // Disable SSP1 - SSP1_CR0_LOCAL = SSP_CR0_DSS_8BIT | SSP_CR0_FRF_SPI | SSP_CR0_CPOL | SSP_CR0_CPHA | (21 << 8); - SSP1_CPSR_LOCAL = 2; - SSP1_CR1_LOCAL = SSP_CR1_SSE; // Enable SSP1 - } - - // Configure SSP1 back to MAX2831 mode (Mode 0, 9-bit) - static void ssp1_set_mode_max2831(void) { - SSP1_CR1_LOCAL = 0; // Disable SSP1 - SSP1_CR0_LOCAL = (0x08) | // 9-bit data (DSS = 0x08) - (0x00) | // SPI frame format - (0 << 6) | // CPOL = 0 (Mode 0) - (0 << 7) | // CPHA = 0 (Mode 0) - (21 << 8); // SCR = 21 - SSP1_CPSR_LOCAL = 2; - SSP1_CR1_LOCAL = SSP_CR1_SSE; // Enable SSP1 - } - - - // ============================================================================ - // Low-Level SPI Register Access (internal, no mode switch) - // ============================================================================ - - // Read an FPGA register via SPI - static uint8_t fpga_spi_read(uint8_t reg) { - uint8_t value; - fpga_cs_low(); - ssp1_transfer_byte(reg & 0x7F); // Clear MSB for read - ssp1_transfer_byte(0x00); // Dummy byte - value = ssp1_transfer_byte(0x00); // Read value - fpga_cs_high(); - return value; - } - - // Write an FPGA register via SPI - static void fpga_spi_write(uint8_t reg, uint8_t value) { - fpga_cs_low(); - ssp1_transfer_byte((reg & 0x7F) | 0x80); // Set MSB for write - ssp1_transfer_byte(value); - ssp1_transfer_byte(0x00); // Dummy byte - fpga_cs_high(); - } - - // ============================================================================ - // Public Debug Functions (switch SPI mode, access register, switch back) - // ============================================================================ - - // Public function to read FPGA register (callable from C++ application code) - // Switches SPI mode, reads register, switches back - uint8_t fpga_debug_register_read(uint8_t reg) { - if (reg == 0 || reg > 5) return 0xFF; - - uint8_t value; - ssp1_set_mode_ice40(); - value = fpga_spi_read(reg); - ssp1_set_mode_max2831(); - - fpga_reg_cache[reg] = value; - return value; - } - - // Public function to write FPGA register (callable from C++ application code) - void fpga_debug_register_write(uint8_t reg, uint8_t value) { - if (reg == 0 || reg > 5) return; - - ssp1_set_mode_ice40(); - fpga_spi_write(reg, value); - ssp1_set_mode_max2831(); - - fpga_reg_cache[reg] = value; - } - - // ============================================================================ - // Public Register Access (wraps debug functions) - // ============================================================================ - - uint8_t fpga_register_read(uint8_t reg) { - if (reg == 0 || reg > 5) return 0xFF; - - ssp1_set_mode_ice40(); - uint8_t val = fpga_spi_read(reg); - ssp1_set_mode_max2831(); - - fpga_reg_cache[reg] = val; - return val; - } - - void fpga_register_write(uint8_t reg, uint8_t value) { - if (reg == 0 || reg > 5) return; - - ssp1_set_mode_ice40(); - fpga_spi_write(reg, value); - ssp1_set_mode_max2831(); - - fpga_reg_cache[reg] = value; - } - - // ============================================================================ - // Mode Management - // ============================================================================ - - fpga_mode_t fpga_get_mode(void) { - return current_mode; - } - - /* fpga_set_mode with consistent values */ - void fpga_set_mode(fpga_mode_t mode) { - current_mode = mode; - } - - // ============================================================================ - // RX Mode Functions (with mode assertion) - // ============================================================================ - - /* RX decimation */ - void fpga_rx_set_decimation(uint8_t ratio) { - if (current_mode != FPGA_MODE_RX) return; - fpga_register_write(FPGA_REG_DECIM, ratio & 0x07); - } - - /* RX DC block enable (bit 0 of register 1) */ - void fpga_rx_enable_dc_block(bool enable) { - if (current_mode != FPGA_MODE_RX) return; - uint8_t ctrl = fpga_register_read(FPGA_REG_CTRL); - if (enable) - ctrl |= FPGA_CTRL_DC_BLOCK_EN; - else - ctrl &= ~FPGA_CTRL_DC_BLOCK_EN; - fpga_register_write(FPGA_REG_CTRL, ctrl); - } - - /* RX Functions with mode assertion */ - void fpga_rx_set_digital_gain(uint8_t shift) { - if (current_mode != FPGA_MODE_RX) { - /* Log error or assert - wrong mode! */ - return; - } - fpga_register_write(FPGA_REG_SHARED_3, shift & FPGA_RX_GAIN_SHIFT_MASK); - } - - void fpga_rx_set_dc_block_width(uint8_t width) { - if (current_mode != FPGA_MODE_RX) return; - fpga_register_write(FPGA_REG_SHARED_4, width & FPGA_RX_DC_WIDTH_MASK); - } - - void fpga_rx_set_dc_adapt_rate(uint8_t rate) { - if (current_mode != FPGA_MODE_RX) return; - fpga_register_write(FPGA_REG_SHARED_5, rate); - } - - // ============================================================================ - // TX Mode Functions (with mode assertion) - // ============================================================================ - - /* TX Functions with mode assertion */ - void fpga_tx_set_nco_enable(bool enable) { - if (current_mode != FPGA_MODE_TX) return; - uint8_t val = fpga_register_read(FPGA_REG_SHARED_3); - if (enable) - val |= FPGA_TX_NCO_EN; - else - val &= ~FPGA_TX_NCO_EN; - fpga_register_write(FPGA_REG_SHARED_3, val); - } - - void fpga_tx_set_interpolation(uint8_t ratio) { - if (current_mode != FPGA_MODE_TX) return; - fpga_register_write(FPGA_REG_SHARED_4, ratio & FPGA_TX_INTERP_MASK); - } - - void fpga_tx_set_phase_step(uint8_t step) { - if (current_mode != FPGA_MODE_TX) return; - fpga_register_write(FPGA_REG_SHARED_5, step); - } - - // ============================================================================ - // FPGA Register Initialization (called after bitstream load) - // ============================================================================ - - // Initialize FPGA registers after bitstream load - // This is equivalent to fpga_init() in the reference HackRF firmware - /* fpga_register_init with consistent values */ - static void fpga_register_init(void) { - - /* Boot into RX mode */ - current_mode = FPGA_MODE_RX; - - fpga_spi_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); - fpga_spi_write(FPGA_REG_DECIM, 0x00); - fpga_spi_write(FPGA_REG_SHARED_3, FPGA_RX_DEFAULT_DIGITAL_GAIN); - fpga_spi_write(FPGA_REG_SHARED_4, FPGA_RX_DEFAULT_DC_WIDTH); - fpga_spi_write(FPGA_REG_SHARED_5, FPGA_RX_DEFAULT_ADAPT_RATE); - - /* Update cache */ - fpga_reg_cache[1] = FPGA_CTRL_DC_BLOCK_EN; - fpga_reg_cache[2] = 0x00; - fpga_reg_cache[3] = FPGA_RX_DEFAULT_DIGITAL_GAIN; - fpga_reg_cache[4] = FPGA_RX_DEFAULT_DC_WIDTH; - fpga_reg_cache[5] = FPGA_RX_DEFAULT_ADAPT_RATE; - } - - // ============================================================================ - // LZ4 Decompression for FPGA Bitstream - // ============================================================================ - - // SPIFI-based read callback for LZ4 decompression - // Reads from SPIFI memory-mapped address instead of using SPI flash driver - static size_t spifi_fpga_read_block_cb(void* _ctx, uint8_t* out_buffer) { - struct spifi_fpga_read_ctx* ctx = (struct spifi_fpga_read_ctx*)_ctx; - size_t block_sz = ctx->next_block_sz; - - // First iteration: read first block size from SPIFI memory - if (ctx->init_flag == 0) { - block_sz = ctx->mem_ptr[0] | (ctx->mem_ptr[1] << 8); - ctx->mem_ptr += 2; - ctx->init_flag = 1; - } - - // Finish at end marker (block_sz == 0) - if (block_sz == 0) return 0; - - // Read compressed block from SPIFI memory - memcpy(ctx->buffer, ctx->mem_ptr, block_sz + 2); - ctx->mem_ptr += block_sz + 2; - - // Extract next block size - ctx->next_block_sz = ctx->buffer[block_sz] | (ctx->buffer[block_sz + 1] << 8); - - // Decompress block using LZ4 - return lz4_blk_decompress(ctx->buffer, out_buffer, block_sz); - } - - // Program iCE40 FPGA using SPIFI memory-mapped data - // Based on ice40_spi_syscfg_program() from ice40_spi.c - static bool program_fpga_from_spifi(const uint8_t* bitstream_start) { - // Drive CRESET_B = 0, SPI_SS = 0 - fpga_creset_low(); - fpga_cs_low(); - - // Wait minimum 200ns - delay_us(1); - - // Release CRESET_B (drive high) - fpga_creset_high(); - - // Wait minimum 1200us (we wait 1800us to be safe) - delay_us(1800); - - // Set SPI_SS = 1, send 8 dummy clocks - fpga_cs_high(); - ssp1_transfer_byte(0); - - // Send configuration image - // Use static buffers to avoid stack overflow (~8KB would be needed) - static uint8_t out_buffer[4096]; - static struct spifi_fpga_read_ctx ctx; - ctx.mem_ptr = bitstream_start; - ctx.next_block_sz = 0; - ctx.init_flag = 0; - - fpga_cs_low(); - // Full LZ4 decompress and send all bytes - for (;;) { - size_t read_sz = spifi_fpga_read_block_cb(&ctx, out_buffer); - if (read_sz == 0) break; - for (size_t j = 0; j < read_sz; j++) { - ssp1_transfer_byte(out_buffer[j]); - } - } - - // Wait for 100 clock cycles for CDONE to go high - fpga_cs_high(); - for (size_t j = 0; j < 13; j++) { - ssp1_transfer_byte(0); - } - - // Check CDONE status - bool success = fpga_cdone_read(); - - // NOTE: FPGA register initialization is done later in radio::init() - // The FPGA needs time to stabilize after configuration before accepting register writes - - // CRITICAL: Reconfigure SSP1 for MAX2831 (PRALINE RF chip) after FPGA programming - // iCE40 uses Mode 3 (CPOL=1, CPHA=1), 8-bit - // MAX2831 (PRALINE) uses Mode 0 (CPOL=0, CPHA=0), 9-bit (vs 16-bit for MAX283x on HackRF One) - // Without this, RF communication will fail! - /*SSP1_CR1_LOCAL = 0; // Disable SSP1 - SSP1_CR0_LOCAL = (0x08) | // 9-bit data (DSS = 0x08) for MAX2831/PRALINE - (0x00) | // SPI frame format - (0 << 6) | // CPOL = 0 (Mode 0) - (0 << 7) | // CPHA = 0 (Mode 0) - (21 << 8); // SCR = 21 (same as ssp_config_max283x for PRALINE) - SSP1_CPSR_LOCAL = 2; // Clock prescaler - SSP1_CR1_LOCAL = SSP_CR1_SSE; // Re-enable SSP1*/ - - return success; - } - - // ============================================================================ - // Main Initialization Entry Point - // ============================================================================ - - int fpga_bridge_init(void) { - // Enable SSP1 clock for FPGA programming - // Use PLL1 (204MHz) to match original HackRF - IRC (12MHz) is 17x too slow - CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | - CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); - - // Configure SSP1 pins - configure_ssp1_pins(); - - // Configure FPGA control pins - configure_fpga_control_pins(); - - // Initialize SSP1 for iCE40 programming - ssp1_init_ice40(); - - // Read FPGA bitstream header from SPIFI memory - const uint8_t* fpga_header = (const uint8_t*)FPGA_BITSTREAM_MEM_ADDR; - uint32_t num_bitstreams = fpga_header[0] | (fpga_header[1] << 8) | - (fpga_header[2] << 16) | (fpga_header[3] << 24); - - // Check if header looks valid - if (num_bitstreams == 0 || num_bitstreams > 16 || num_bitstreams == 0xFFFFFFFF) { - // No valid FPGA bitstream - skip programming but continue boot - return 1; - } - - // Get offset of first bitstream (index 0 = standard_fpga) - uint32_t bitstream_offset = fpga_header[4] | (fpga_header[5] << 8) | - (fpga_header[6] << 16) | (fpga_header[7] << 24); - - // Calculate start address of first bitstream in SPIFI memory - const uint8_t* bitstream_start = (const uint8_t*)(FPGA_BITSTREAM_MEM_ADDR + bitstream_offset); - - // Full FPGA programming - bool success = program_fpga_from_spifi(bitstream_start); - - // Initialize FPGA registers immediately after programming - if (success) { - // Give FPGA 100us to stabilize after configuration - delay_us(100); - - // Initialize FPGA registers (DC_BLOCK, etc.) - fpga_register_init(); - - // Now switch to MAX2831 mode - ssp1_set_mode_max2831(); - } - - return success ? 0 : 2; - } +#include "fpga_bridge.h" + +// Necessary headers +#include "lz4_blk.h" + +// LIBOPENCM3 Headers (only CGU for clock setup) +#include + +#include +#include +#include +#include + +// SPIFI memory-mapped base address +// Flash is mapped starting at 0x14000000 +// FPGA bitstream at flash address 0x380000 = memory address 0x14380000 +// PRALINE: Moved to 1.5MB offset to allow larger base firmware +#define SPIFI_DATA_BASE 0x14000000 +#define FPGA_BITSTREAM_FLASH_ADDR 0x380000 // Was 0x100000 (1MB), then 0x180000 (1.5MB), now 0x380000 (3.5MB) +#define FPGA_BITSTREAM_MEM_ADDR (SPIFI_DATA_BASE + FPGA_BITSTREAM_FLASH_ADDR) + +// MMIO32 direct register access +#define MMIO32_LOCAL(addr) (*(volatile uint32_t*)(addr)) + +// SSP1 base address +#define SSP1_BASE_LOCAL 0x400C5000 + +// SSP register offsets +#define SSP_CR0_OFF 0x000 +#define SSP_CR1_OFF 0x004 +#define SSP_DR_OFF 0x008 +#define SSP_SR_OFF 0x00C +#define SSP_CPSR_OFF 0x010 + +// SSP register access +#define SSP1_CR0_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_CR0_OFF) +#define SSP1_CR1_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_CR1_OFF) +#define SSP1_DR_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_DR_OFF) +#define SSP1_SR_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_SR_OFF) +#define SSP1_CPSR_LOCAL MMIO32_LOCAL(SSP1_BASE_LOCAL + SSP_CPSR_OFF) + +// SSP status bits +#define SSP_SR_TNF_LOCAL (1 << 1) // TX FIFO not full +#define SSP_SR_RNE_LOCAL (1 << 2) // RX FIFO not empty +#define SSP_SR_BSY_LOCAL (1 << 4) // Busy + +// SSP CR0 bits +#define SSP_CR0_DSS_8BIT (0x7) // 8-bit data +#define SSP_CR0_FRF_SPI (0x0) // SPI frame format +#define SSP_CR0_CPOL (1 << 6) // Clock polarity +#define SSP_CR0_CPHA (1 << 7) // Clock phase + +// SSP CR1 bits +#define SSP_CR1_SSE (1 << 1) // SSP enable + +// SCU pin configuration registers +#define PERIPH_BASE_APB0_LOCAL 0x40080000 +#define SCU_BASE_LOCAL (PERIPH_BASE_APB0_LOCAL + 0x06000) +#define PIN_GROUP1_LOCAL (SCU_BASE_LOCAL + 0x080) +#define PIN_GROUP4_LOCAL (SCU_BASE_LOCAL + 0x200) +#define PIN_GROUP5_LOCAL (SCU_BASE_LOCAL + 0x280) +#define PIN3_LOCAL 0x00C +#define PIN4_LOCAL 0x010 +#define PIN1_LOCAL 0x004 +#define PIN2_LOCAL 0x008 +#define PIN10_LOCAL 0x028 +#define PIN19_LOCAL 0x04C + +// SCU configuration flags +#define SCU_CONF_EPUN_DIS_PULLUP_LOCAL (1 << 4) +#define SCU_CONF_EHS_FAST_LOCAL (1 << 5) +#define SCU_CONF_EZI_EN_IN_BUFFER_LOCAL (1 << 6) +#define SCU_CONF_ZIF_DIS_IN_GLITCH_FILT_LOCAL (1 << 7) +#define SCU_GPIO_FAST_LOCAL (SCU_CONF_EPUN_DIS_PULLUP_LOCAL | \ + SCU_CONF_EHS_FAST_LOCAL | \ + SCU_CONF_EZI_EN_IN_BUFFER_LOCAL | \ + SCU_CONF_ZIF_DIS_IN_GLITCH_FILT_LOCAL) +#define SCU_SSP_IO_LOCAL SCU_GPIO_FAST_LOCAL + +// Function select values +#define SCU_CONF_FUNCTION0_LOCAL (0x0) +#define SCU_CONF_FUNCTION1_LOCAL (0x1) +#define SCU_CONF_FUNCTION4_LOCAL (0x4) +#define SCU_CONF_FUNCTION5_LOCAL (0x5) +#define SCU_GPIO_NOPULL_LOCAL (SCU_CONF_EZI_EN_IN_BUFFER_LOCAL | SCU_CONF_ZIF_DIS_IN_GLITCH_FILT_LOCAL) +#define SCU_GPIO_PUP_LOCAL (SCU_CONF_EZI_EN_IN_BUFFER_LOCAL) + +// SSP1 pins (for FPGA programming) +#define SCU_SSP1_CIPO_LOCAL (PIN_GROUP1_LOCAL + PIN3_LOCAL) // P1_3 +#define SCU_SSP1_COPI_LOCAL (PIN_GROUP1_LOCAL + PIN4_LOCAL) // P1_4 +#define SCU_SSP1_SCK_LOCAL (PIN_GROUP1_LOCAL + PIN19_LOCAL) // P1_19 + +// FPGA control pins +#define SCU_FPGA_CRESET_LOCAL (PIN_GROUP5_LOCAL + PIN2_LOCAL) // P5_2 GPIO2[11] +#define SCU_FPGA_CDONE_LOCAL (PIN_GROUP4_LOCAL + PIN10_LOCAL) // P4_10 GPIO5[14] +#define SCU_FPGA_SPI_CS_LOCAL (PIN_GROUP5_LOCAL + PIN1_LOCAL) // P5_1 GPIO2[10] + +// GPIO register addresses for direct MMIO access +#define GPIO_LPC_BASE_LOCAL 0x400F4000 +#define GPIO_DIR_BASE (GPIO_LPC_BASE_LOCAL + 0x2000) // Direction registers +#define GPIO_SET_BASE (GPIO_LPC_BASE_LOCAL + 0x2200) // Set registers +#define GPIO_CLR_BASE (GPIO_LPC_BASE_LOCAL + 0x2280) // Clear registers +#define GPIO_PIN_BASE (GPIO_LPC_BASE_LOCAL + 0x2100) // Pin read registers + +// GPIO port access macros +#define GPIO_DIR(port) MMIO32_LOCAL(GPIO_DIR_BASE + (port) * 4) +#define GPIO_SET(port) MMIO32_LOCAL(GPIO_SET_BASE + (port) * 4) +#define GPIO_CLR(port) MMIO32_LOCAL(GPIO_CLR_BASE + (port) * 4) +#define GPIO_PIN(port) MMIO32_LOCAL(GPIO_PIN_BASE + (port) * 4) + +// FPGA control GPIO pins +// GPIO2[11] = CRESET, GPIO5[14] = CDONE, GPIO2[10] = SPI_CS +#define FPGA_CRESET_PORT 2 +#define FPGA_CRESET_PIN 11 +#define FPGA_CDONE_PORT 5 +#define FPGA_CDONE_PIN 14 +#define FPGA_SPI_CS_PORT 2 +#define FPGA_SPI_CS_PIN 10 + +// ============================================================================ +// Canonical Default Values - SINGLE SOURCE OF TRUTH +// ============================================================================ +/* Define the canonical RX defaults in ONE place */ +#define FPGA_RX_DEFAULT_DC_WIDTH 0x04 /* Typical for 40MHz stability */ +#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08 /* Typical for 40MHz stability */ +#define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00 /* No shift initially */ + +/* Define TX defaults */ +#define FPGA_TX_DEFAULT_NCO_CTRL 0x00 /* NCO disabled */ +#define FPGA_TX_DEFAULT_INTERP 0x00 /* No interpolation */ +#define FPGA_TX_DEFAULT_PHASE_STEP 0x00 /* Zero phase step */ + +// ============================================================================ +// Static Variables - Declare BEFORE use +// ============================================================================ +static fpga_mode_t current_mode = FPGA_MODE_OFF; +// Cached register values for debug reads (since reads may require mode switch) +static uint8_t fpga_reg_cache[6] = {0, 0x01, 0x00, 0x00, 0x00, 0x00}; + +// Context structure for SPIFI-based reading +struct spifi_fpga_read_ctx { + const uint8_t* mem_ptr; // Current read position in SPIFI memory + size_t next_block_sz; + uint8_t init_flag; + uint8_t buffer[4128 + 2]; // Compressed block + next size +}; + +// ============================================================================ +// Low-Level Helper Functions +// ============================================================================ + +// Simple delay loop +static void delay_cycles(volatile uint32_t count) { + while (count--) { + __asm__ volatile("nop"); + } +} + +// Microsecond delay (approximate, assuming ~200MHz clock) +static void delay_us(uint32_t us) { + // ~50 cycles per microsecond at 200MHz + delay_cycles(us * 50); +} + +// SSP1 transfer one byte +static uint8_t ssp1_transfer_byte(uint8_t data) { + // Wait for TX FIFO not full + while ((SSP1_SR_LOCAL & SSP_SR_TNF_LOCAL) == 0) { + } + SSP1_DR_LOCAL = data; + // Wait for not busy + while (SSP1_SR_LOCAL & SSP_SR_BSY_LOCAL) { + } + // Wait for RX FIFO not empty + while ((SSP1_SR_LOCAL & SSP_SR_RNE_LOCAL) == 0) { + } + return SSP1_DR_LOCAL; +} + +// Configure SSP1 for iCE40 programming (SPI mode 3: CPOL=1, CPHA=1) +static void ssp1_init_ice40(void) { + // Disable SSP1 first + SSP1_CR1_LOCAL = 0; + + // Configure: 8-bit, SPI mode 3 (CPOL=1, CPHA=1), master mode + // SCR=21 for ~4MHz at 200MHz clock + SSP1_CR0_LOCAL = SSP_CR0_DSS_8BIT | SSP_CR0_FRF_SPI | SSP_CR0_CPOL | SSP_CR0_CPHA | (21 << 8); + + // Clock prescaler = 2 (divide by 2) + SSP1_CPSR_LOCAL = 2; + + // Enable SSP1 + SSP1_CR1_LOCAL = SSP_CR1_SSE; +} + +// Configure SSP1 pins via SCU +static void configure_ssp1_pins(void) { + // P1_3 = SSP1_MISO (function 5) + MMIO32_LOCAL(SCU_SSP1_CIPO_LOCAL) = SCU_SSP_IO_LOCAL | SCU_CONF_FUNCTION5_LOCAL; + // P1_4 = SSP1_MOSI (function 5) + MMIO32_LOCAL(SCU_SSP1_COPI_LOCAL) = SCU_SSP_IO_LOCAL | SCU_CONF_FUNCTION5_LOCAL; + // P1_19 = SSP1_SCK (function 1) + MMIO32_LOCAL(SCU_SSP1_SCK_LOCAL) = SCU_SSP_IO_LOCAL | SCU_CONF_FUNCTION1_LOCAL; +} + +// Configure FPGA control pins via SCU and GPIO +static void configure_fpga_control_pins(void) { + // P5_2 = GPIO2[11] = CRESET (function 0, output) + MMIO32_LOCAL(SCU_FPGA_CRESET_LOCAL) = SCU_GPIO_NOPULL_LOCAL | SCU_CONF_FUNCTION0_LOCAL; + // P4_10 = GPIO5[14] = CDONE (function 4, input with pullup) + MMIO32_LOCAL(SCU_FPGA_CDONE_LOCAL) = SCU_GPIO_PUP_LOCAL | SCU_CONF_FUNCTION4_LOCAL; + // P5_1 = GPIO2[10] = SPI_CS (function 0, output) + MMIO32_LOCAL(SCU_FPGA_SPI_CS_LOCAL) = SCU_GPIO_NOPULL_LOCAL | SCU_CONF_FUNCTION0_LOCAL; + + // Set CRESET and SPI_CS as outputs (GPIO2[11] and GPIO2[10]) + GPIO_DIR(FPGA_CRESET_PORT) |= (1 << FPGA_CRESET_PIN) | (1 << FPGA_SPI_CS_PIN); + // Clear both initially + GPIO_CLR(FPGA_CRESET_PORT) = (1 << FPGA_CRESET_PIN) | (1 << FPGA_SPI_CS_PIN); + + // CDONE is input (GPIO5[14]) + GPIO_DIR(FPGA_CDONE_PORT) &= ~(1 << FPGA_CDONE_PIN); +} + +// GPIO control helpers +static void fpga_creset_low(void) { + GPIO_CLR(FPGA_CRESET_PORT) = (1 << FPGA_CRESET_PIN); +} +static void fpga_creset_high(void) { + GPIO_SET(FPGA_CRESET_PORT) = (1 << FPGA_CRESET_PIN); +} +static void fpga_cs_low(void) { + GPIO_CLR(FPGA_SPI_CS_PORT) = (1 << FPGA_SPI_CS_PIN); +} +static void fpga_cs_high(void) { + GPIO_SET(FPGA_SPI_CS_PORT) = (1 << FPGA_SPI_CS_PIN); +} +static bool fpga_cdone_read(void) { + return (GPIO_PIN(FPGA_CDONE_PORT) & (1 << FPGA_CDONE_PIN)) != 0; +} + +// ============================================================================ +// FPGA Register Access via SPI (iCE40) +// ============================================================================ +// These functions allow reading/writing FPGA internal registers via SPI. +// The FPGA bitstream implements a simple SPI register interface. +// +// FPGA Register Map: +// Reg 1 (CTRL): DC_BLOCK(b0), QUARTER_SHIFT_EN(b1), QUARTER_SHIFT_UP(b2), PRBS(b6), TRIGGER_EN(b7) +// Reg 2 (RX_DECIM): Decimation ratio [2:0] +// Reg 3 (RX/TX): RX Digital Shift OR TX NCO Control +// Reg 4 (RX_DC_BLOCK_WIDTH/TX_INTERP) [2:0] +// Reg 5 (RX_DC_ADAPT_RATE/TX_PSTEP) [7:0] +// +// SPI Protocol: +// Read: Send [reg & 0x7F, 0x00, 0x00] -> value in byte 3 +// Write: Send [(reg | 0x80), value, 0x00] + +// ============================================================================ +// SPI Mode Switching +// ============================================================================ + +// Configure SSP1 for iCE40 FPGA register access (Mode 3, 8-bit) +static void ssp1_set_mode_ice40(void) { + SSP1_CR1_LOCAL = 0; // Disable SSP1 + SSP1_CR0_LOCAL = SSP_CR0_DSS_8BIT | SSP_CR0_FRF_SPI | SSP_CR0_CPOL | SSP_CR0_CPHA | (21 << 8); + SSP1_CPSR_LOCAL = 2; + SSP1_CR1_LOCAL = SSP_CR1_SSE; // Enable SSP1 +} + +// Configure SSP1 back to MAX2831 mode (Mode 0, 9-bit) +static void ssp1_set_mode_max2831(void) { + SSP1_CR1_LOCAL = 0; // Disable SSP1 + SSP1_CR0_LOCAL = (0x08) | // 9-bit data (DSS = 0x08) + (0x00) | // SPI frame format + (0 << 6) | // CPOL = 0 (Mode 0) + (0 << 7) | // CPHA = 0 (Mode 0) + (21 << 8); // SCR = 21 + SSP1_CPSR_LOCAL = 2; + SSP1_CR1_LOCAL = SSP_CR1_SSE; // Enable SSP1 +} + +// ============================================================================ +// Low-Level SPI Register Access (internal, no mode switch) +// ============================================================================ + +// Read an FPGA register via SPI +static uint8_t fpga_spi_read(uint8_t reg) { + uint8_t value; + fpga_cs_low(); + ssp1_transfer_byte(reg & 0x7F); // Clear MSB for read + ssp1_transfer_byte(0x00); // Dummy byte + value = ssp1_transfer_byte(0x00); // Read value + fpga_cs_high(); + return value; +} + +// Write an FPGA register via SPI +static void fpga_spi_write(uint8_t reg, uint8_t value) { + fpga_cs_low(); + ssp1_transfer_byte((reg & 0x7F) | 0x80); // Set MSB for write + ssp1_transfer_byte(value); + ssp1_transfer_byte(0x00); // Dummy byte + fpga_cs_high(); +} + +// ============================================================================ +// Public Debug Functions (switch SPI mode, access register, switch back) +// ============================================================================ + +// Public function to read FPGA register (callable from C++ application code) +// Switches SPI mode, reads register, switches back +uint8_t fpga_debug_register_read(uint8_t reg) { + if (reg == 0 || reg > 5) return 0xFF; + + uint8_t value; + ssp1_set_mode_ice40(); + value = fpga_spi_read(reg); + ssp1_set_mode_max2831(); + + fpga_reg_cache[reg] = value; + return value; +} + +// Public function to write FPGA register (callable from C++ application code) +void fpga_debug_register_write(uint8_t reg, uint8_t value) { + if (reg == 0 || reg > 5) return; + + ssp1_set_mode_ice40(); + fpga_spi_write(reg, value); + ssp1_set_mode_max2831(); + + fpga_reg_cache[reg] = value; +} + +// ============================================================================ +// Public Register Access (wraps debug functions) +// ============================================================================ + +uint8_t fpga_register_read(uint8_t reg) { + if (reg == 0 || reg > 5) return 0xFF; + + ssp1_set_mode_ice40(); + uint8_t val = fpga_spi_read(reg); + ssp1_set_mode_max2831(); + + fpga_reg_cache[reg] = val; + return val; +} + +void fpga_register_write(uint8_t reg, uint8_t value) { + if (reg == 0 || reg > 5) return; + + ssp1_set_mode_ice40(); + fpga_spi_write(reg, value); + ssp1_set_mode_max2831(); + + fpga_reg_cache[reg] = value; +} + +// ============================================================================ +// Mode Management +// ============================================================================ + +fpga_mode_t fpga_get_mode(void) { + return current_mode; +} + +/* fpga_set_mode with consistent values */ +void fpga_set_mode(fpga_mode_t mode) { + current_mode = mode; +} + +// ============================================================================ +// RX Mode Functions (with mode assertion) +// ============================================================================ + +/* RX decimation */ +void fpga_rx_set_decimation(uint8_t ratio) { + if (current_mode != FPGA_MODE_RX) return; + fpga_register_write(FPGA_REG_DECIM, ratio & 0x07); +} + +/* RX DC block enable (bit 0 of register 1) */ +void fpga_rx_enable_dc_block(bool enable) { + if (current_mode != FPGA_MODE_RX) return; + uint8_t ctrl = fpga_register_read(FPGA_REG_CTRL); + if (enable) + ctrl |= FPGA_CTRL_DC_BLOCK_EN; + else + ctrl &= ~FPGA_CTRL_DC_BLOCK_EN; + fpga_register_write(FPGA_REG_CTRL, ctrl); +} + +/* RX Functions with mode assertion */ +void fpga_rx_set_digital_gain(uint8_t shift) { + if (current_mode != FPGA_MODE_RX) { + /* Log error or assert - wrong mode! */ + return; + } + fpga_register_write(FPGA_REG_SHARED_3, shift & FPGA_RX_GAIN_SHIFT_MASK); +} + +void fpga_rx_set_dc_block_width(uint8_t width) { + if (current_mode != FPGA_MODE_RX) return; + fpga_register_write(FPGA_REG_SHARED_4, width & FPGA_RX_DC_WIDTH_MASK); +} + +void fpga_rx_set_dc_adapt_rate(uint8_t rate) { + if (current_mode != FPGA_MODE_RX) return; + fpga_register_write(FPGA_REG_SHARED_5, rate); +} + +// ============================================================================ +// TX Mode Functions (with mode assertion) +// ============================================================================ + +/* TX Functions with mode assertion */ +void fpga_tx_set_nco_enable(bool enable) { + if (current_mode != FPGA_MODE_TX) return; + uint8_t val = fpga_register_read(FPGA_REG_SHARED_3); + if (enable) + val |= FPGA_TX_NCO_EN; + else + val &= ~FPGA_TX_NCO_EN; + fpga_register_write(FPGA_REG_SHARED_3, val); +} + +void fpga_tx_set_interpolation(uint8_t ratio) { + if (current_mode != FPGA_MODE_TX) return; + fpga_register_write(FPGA_REG_SHARED_4, ratio & FPGA_TX_INTERP_MASK); +} + +void fpga_tx_set_phase_step(uint8_t step) { + if (current_mode != FPGA_MODE_TX) return; + fpga_register_write(FPGA_REG_SHARED_5, step); +} + +// ============================================================================ +// FPGA Register Initialization (called after bitstream load) +// ============================================================================ + +// Initialize FPGA registers after bitstream load +// This is equivalent to fpga_init() in the reference HackRF firmware +/* fpga_register_init with consistent values */ +static void fpga_register_init(void) { + /* Boot into RX mode */ + current_mode = FPGA_MODE_RX; + + fpga_spi_write(FPGA_REG_CTRL, FPGA_CTRL_DC_BLOCK_EN); + fpga_spi_write(FPGA_REG_DECIM, 0x00); + fpga_spi_write(FPGA_REG_SHARED_3, FPGA_RX_DEFAULT_DIGITAL_GAIN); + fpga_spi_write(FPGA_REG_SHARED_4, FPGA_RX_DEFAULT_DC_WIDTH); + fpga_spi_write(FPGA_REG_SHARED_5, FPGA_RX_DEFAULT_ADAPT_RATE); + + /* Update cache */ + fpga_reg_cache[1] = FPGA_CTRL_DC_BLOCK_EN; + fpga_reg_cache[2] = 0x00; + fpga_reg_cache[3] = FPGA_RX_DEFAULT_DIGITAL_GAIN; + fpga_reg_cache[4] = FPGA_RX_DEFAULT_DC_WIDTH; + fpga_reg_cache[5] = FPGA_RX_DEFAULT_ADAPT_RATE; +} + +// ============================================================================ +// LZ4 Decompression for FPGA Bitstream +// ============================================================================ + +// SPIFI-based read callback for LZ4 decompression +// Reads from SPIFI memory-mapped address instead of using SPI flash driver +static size_t spifi_fpga_read_block_cb(void* _ctx, uint8_t* out_buffer) { + struct spifi_fpga_read_ctx* ctx = (struct spifi_fpga_read_ctx*)_ctx; + size_t block_sz = ctx->next_block_sz; + + // First iteration: read first block size from SPIFI memory + if (ctx->init_flag == 0) { + block_sz = ctx->mem_ptr[0] | (ctx->mem_ptr[1] << 8); + ctx->mem_ptr += 2; + ctx->init_flag = 1; + } + + // Finish at end marker (block_sz == 0) + if (block_sz == 0) return 0; + if (block_sz > 4128) { + // Flash corruption detected! Return 0 to abort programming safely. + return 0; + } + + // Read compressed block from SPIFI memory + memcpy(ctx->buffer, ctx->mem_ptr, block_sz + 2); + ctx->mem_ptr += block_sz + 2; + + // Extract next block size + ctx->next_block_sz = ctx->buffer[block_sz] | (ctx->buffer[block_sz + 1] << 8); + + // Decompress block using LZ4 + return lz4_blk_decompress(ctx->buffer, out_buffer, block_sz); +} + +// Program iCE40 FPGA using SPIFI memory-mapped data +// Based on ice40_spi_syscfg_program() from ice40_spi.c +static bool program_fpga_from_spifi(const uint8_t* bitstream_start, uint8_t* mem_base) { + // Drive CRESET_B = 0, SPI_SS = 0 + fpga_creset_low(); + fpga_cs_low(); + + // Wait minimum 200ns + delay_us(1); + + // Release CRESET_B (drive high) + fpga_creset_high(); + + // Wait minimum 1200us (we wait 1800us to be safe) + delay_us(1800); + + // Set SPI_SS = 1, send 8 dummy clocks + fpga_cs_high(); + ssp1_transfer_byte(0); + + // Send configuration image + // Use buffers to avoid stack overflow (~8KB would be needed) + uint8_t* out_buffer = mem_base; + struct spifi_fpga_read_ctx* ctx = (struct spifi_fpga_read_ctx*)(out_buffer + 4096); + memset(ctx, 0, sizeof(struct spifi_fpga_read_ctx)); + memset(out_buffer, 0, 4096); + ctx->mem_ptr = bitstream_start; + ctx->next_block_sz = 0; + ctx->init_flag = 0; + + fpga_cs_low(); + // Full LZ4 decompress and send all bytes + for (;;) { + size_t read_sz = spifi_fpga_read_block_cb(ctx, out_buffer); + if (read_sz == 0) break; + for (size_t j = 0; j < read_sz; j++) { + ssp1_transfer_byte(out_buffer[j]); + } + } + + // Wait for 100 clock cycles for CDONE to go high + fpga_cs_high(); + for (size_t j = 0; j < 13; j++) { + ssp1_transfer_byte(0); + } + + // Check CDONE status + bool success = fpga_cdone_read(); + + // NOTE: FPGA register initialization is done later in radio::init() + // The FPGA needs time to stabilize after configuration before accepting register writes + + // CRITICAL: Reconfigure SSP1 for MAX2831 (PRALINE RF chip) after FPGA programming + // iCE40 uses Mode 3 (CPOL=1, CPHA=1), 8-bit + // MAX2831 (PRALINE) uses Mode 0 (CPOL=0, CPHA=0), 9-bit (vs 16-bit for MAX283x on HackRF One) + // Without this, RF communication will fail! + /*SSP1_CR1_LOCAL = 0; // Disable SSP1 + SSP1_CR0_LOCAL = (0x08) | // 9-bit data (DSS = 0x08) for MAX2831/PRALINE + (0x00) | // SPI frame format + (0 << 6) | // CPOL = 0 (Mode 0) + (0 << 7) | // CPHA = 0 (Mode 0) + (21 << 8); // SCR = 21 (same as ssp_config_max283x for PRALINE) + SSP1_CPSR_LOCAL = 2; // Clock prescaler + SSP1_CR1_LOCAL = SSP_CR1_SSE; // Re-enable SSP1*/ + + return success; +} + +// ============================================================================ +// Main Initialization Entry Point +// ============================================================================ + +int fpga_bridge_init(uint8_t* mem_base) { + // Enable SSP1 clock for FPGA programming + // Use PLL1 (204MHz) to match original HackRF - IRC (12MHz) is 17x too slow + CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | + CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); + + // Configure SSP1 pins + configure_ssp1_pins(); + + // Configure FPGA control pins + configure_fpga_control_pins(); + + // Initialize SSP1 for iCE40 programming + ssp1_init_ice40(); + + // Read FPGA bitstream header from SPIFI memory + const uint8_t* fpga_header = (const uint8_t*)FPGA_BITSTREAM_MEM_ADDR; + uint32_t num_bitstreams = fpga_header[0] | (fpga_header[1] << 8) | + (fpga_header[2] << 16) | (fpga_header[3] << 24); + + // Check if header looks valid + if (num_bitstreams == 0 || num_bitstreams > 16 || num_bitstreams == 0xFFFFFFFF) { + // No valid FPGA bitstream - skip programming but continue boot + return 1; + } + + // Get offset of first bitstream (index 0 = standard_fpga) + uint32_t bitstream_offset = fpga_header[4] | (fpga_header[5] << 8) | + (fpga_header[6] << 16) | (fpga_header[7] << 24); + + // Calculate start address of first bitstream in SPIFI memory + const uint8_t* bitstream_start = (const uint8_t*)(FPGA_BITSTREAM_MEM_ADDR + bitstream_offset); + + // Full FPGA programming + bool success = program_fpga_from_spifi(bitstream_start, mem_base); + + // Initialize FPGA registers immediately after programming + if (success) { + // Give FPGA 100us to stabilize after configuration + delay_us(100); + + // Initialize FPGA registers (DC_BLOCK, etc.) + fpga_register_init(); + + // Now switch to MAX2831 mode + ssp1_set_mode_max2831(); + } + return success ? 0 : 2; +} #else #warning "Building for HackRF_One with CPLD." #endif diff --git a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h index e7fb0bf6c..ee561e029 100644 --- a/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h +++ b/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.h @@ -17,16 +17,16 @@ extern "C" { #ifdef PRALINE /* FPGA Register Map Address 0x03 (Dual Purpose) */ -#define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */ -#define FPGA_REG_TX_CONTROL 0x03 /* NCO_EN and TX flags (TX Mode) */ +#define FPGA_REG_RX_DIGITAL_GAIN 0x03 /* Digital Shift / scaling (RX Mode) */ +#define FPGA_REG_TX_CONTROL 0x03 /* NCO_EN and TX flags (TX Mode) */ /* FPGA Register Map Address 0x04 (Shared) */ -#define FPGA_REG_RX_DC_BLOCK_WIDTH 0x04 /* Notch filter cutoff (RX Mode) */ -#define FPGA_REG_TX_INTERP 0x04 /* Interpolation ratio (TX Mode) */ +#define FPGA_REG_RX_DC_BLOCK_WIDTH 0x04 /* Notch filter cutoff (RX Mode) */ +#define FPGA_REG_TX_INTERP 0x04 /* Interpolation ratio (TX Mode) */ /* FPGA Register Map Address 0x05 (Shared) */ -#define FPGA_REG_RX_DC_ADAPT_RATE 0x05 /* Settle time/Integration (RX Mode) */ -#define FPGA_REG_TX_PHASE_STEP 0x05 /* NCO frequency step (TX Mode) */ +#define FPGA_REG_RX_DC_ADAPT_RATE 0x05 /* Settle time/Integration (RX Mode) */ +#define FPGA_REG_TX_PHASE_STEP 0x05 /* NCO frequency step (TX Mode) */ /* * FPGA Operating Mode @@ -41,60 +41,60 @@ typedef enum { * FPGA Register Addresses * Note: Registers 3-5 are dual-purpose (meaning depends on RX/TX mode) */ -#define FPGA_REG_CTRL 0x01 /* Control register */ -#define FPGA_REG_DECIM 0x02 /* Decimation (RX) / unused (TX) */ -#define FPGA_REG_SHARED_3 0x03 /* Dual-purpose register */ -#define FPGA_REG_SHARED_4 0x04 /* Dual-purpose register */ -#define FPGA_REG_SHARED_5 0x05 /* Dual-purpose register */ +#define FPGA_REG_CTRL 0x01 /* Control register */ +#define FPGA_REG_DECIM 0x02 /* Decimation (RX) / unused (TX) */ +#define FPGA_REG_SHARED_3 0x03 /* Dual-purpose register */ +#define FPGA_REG_SHARED_4 0x04 /* Dual-purpose register */ +#define FPGA_REG_SHARED_5 0x05 /* Dual-purpose register */ /* * Register 1 (CTRL) Bit Definitions */ -#define FPGA_CTRL_DC_BLOCK_EN (1 << 0) /* DC block enable */ -#define FPGA_CTRL_QUARTER_SHIFT_EN (1 << 1) /* Quarter-rate shift enable */ -#define FPGA_CTRL_QUARTER_SHIFT_UP (1 << 2) /* Shift direction: 1=up, 0=down */ -#define FPGA_CTRL_TX_MODE (1 << 5) /* TX mode indicator (if applicable) */ -#define FPGA_CTRL_PRBS_EN (1 << 6) /* PRBS test mode */ -#define FPGA_CTRL_TRIGGER_EN (1 << 7) /* External trigger enable */ +#define FPGA_CTRL_DC_BLOCK_EN (1 << 0) /* DC block enable */ +#define FPGA_CTRL_QUARTER_SHIFT_EN (1 << 1) /* Quarter-rate shift enable */ +#define FPGA_CTRL_QUARTER_SHIFT_UP (1 << 2) /* Shift direction: 1=up, 0=down */ +#define FPGA_CTRL_TX_MODE (1 << 5) /* TX mode indicator (if applicable) */ +#define FPGA_CTRL_PRBS_EN (1 << 6) /* PRBS test mode */ +#define FPGA_CTRL_TRIGGER_EN (1 << 7) /* External trigger enable */ /* * Register 3 Dual-Purpose Definitions */ /* RX Mode: Digital gain/shift */ -#define FPGA_REG3_RX_DIGITAL_GAIN 0x03 -#define FPGA_RX_GAIN_SHIFT_MASK 0x0F /* Bits [3:0] - shift amount */ +#define FPGA_REG3_RX_DIGITAL_GAIN 0x03 +#define FPGA_RX_GAIN_SHIFT_MASK 0x0F /* Bits [3:0] - shift amount */ /* TX Mode: NCO control */ -#define FPGA_REG3_TX_NCO_CTRL 0x03 -#define FPGA_TX_NCO_EN (1 << 0) /* NCO enable */ -#define FPGA_TX_NCO_INVERT (1 << 1) /* Invert spectrum */ +#define FPGA_REG3_TX_NCO_CTRL 0x03 +#define FPGA_TX_NCO_EN (1 << 0) /* NCO enable */ +#define FPGA_TX_NCO_INVERT (1 << 1) /* Invert spectrum */ /* * Register 4 Dual-Purpose Definitions */ /* RX Mode: DC block notch width */ -#define FPGA_REG4_RX_DC_WIDTH 0x04 -#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */ +#define FPGA_REG4_RX_DC_WIDTH 0x04 +#define FPGA_RX_DC_WIDTH_MASK 0x07 /* Bits [2:0] */ /* TX Mode: Interpolation ratio */ -#define FPGA_REG4_TX_INTERP 0x04 -#define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */ +#define FPGA_REG4_TX_INTERP 0x04 +#define FPGA_TX_INTERP_MASK 0x07 /* Bits [2:0] */ /* * Register 5 Dual-Purpose Definitions */ /* RX Mode: DC block adaptation rate */ -#define FPGA_REG5_RX_DC_RATE 0x05 -#define FPGA_RX_DC_RATE_MASK 0xFF /* Bits [7:0] */ +#define FPGA_REG5_RX_DC_RATE 0x05 +#define FPGA_RX_DC_RATE_MASK 0xFF /* Bits [7:0] */ /* TX Mode: NCO phase step (frequency) */ -#define FPGA_REG5_TX_PHASE_STEP 0x05 -#define FPGA_TX_PHASE_STEP_MASK 0xFF /* Bits [7:0] */ +#define FPGA_REG5_TX_PHASE_STEP 0x05 +#define FPGA_TX_PHASE_STEP_MASK 0xFF /* Bits [7:0] */ /* Export default values so other methods can use them */ #define FPGA_RX_DEFAULT_DIGITAL_GAIN 0x00 -#define FPGA_RX_DEFAULT_DC_WIDTH 0x04 -#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08 +#define FPGA_RX_DEFAULT_DC_WIDTH 0x04 +#define FPGA_RX_DEFAULT_ADAPT_RATE 0x08 /* * Core Functions @@ -102,7 +102,7 @@ typedef enum { /* Initialize the FPGA - loads bitstream from SPIFI flash * Returns: 0 on success, non-zero on failure */ -int fpga_bridge_init(void); +int fpga_bridge_init(uint8_t* mem_base); /* Set operating mode - MUST be called before using mode-specific functions * This ensures registers 3-5 are interpreted correctly */