Files
mayhem-firmware/firmware/chibios-portapack/boards/PORTAPACK_APPLICATION/fpga_bridge.c
T
gullradriel 705e4f125f Update submodule, fix warnings for PRALINE (#3012)
* update submodule
* remove pragma, remove unused variable
* fix bad external decl, signed vs unsigned comparison
* fix unused: display computed flags
* fix max2881 info mess
* code format
* fix gui positions
* use constexpr for min and max lo_frequencies
2026-02-20 11:08:41 +00:00

471 lines
17 KiB
C

// This bridge allows calls for functions in their native C context
// HackRF headers here - C for use within the C++ board.cpp context
// Check if PRALINE was passed from CMake
#ifdef PRALINE
// Necessary headers
#include "lz4_blk.h"
// LIBOPENCM3 Headers (only CGU for clock setup)
#include <libopencm3/lpc43xx/cgu.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
// 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
// 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
};
// 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 (TX_CTRL): NCO_EN(b0)
// Reg 4 (TX_INTRP): Interpolation ratio [2:0]
// Reg 5 (TX_PSTEP): NCO phase step [7:0]
//
// SPI Protocol:
// Read: Send [reg & 0x7F, 0x00, 0x00] -> value in byte 3
// Write: Send [(reg | 0x80), value, 0x00]
// 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
}
// 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();
}
// Initialize FPGA registers after bitstream load
// This is equivalent to fpga_init() in the reference HackRF firmware
static void fpga_register_init(void) {
// Already in iCE40 mode after programming, so we can directly access registers
// Register 1 (CTRL): Enable DC block (bit 0), disable everything else
// DC_BLOCK is CRITICAL for RX to work!
fpga_spi_write(1, 0x01); // DC_BLOCK = 1
// Register 2 (RX_DECIM): No decimation
fpga_spi_write(2, 0x00);
// Register 3 (TX_CTRL): Disable NCO
fpga_spi_write(3, 0x00);
// Register 4 (TX_INTRP): No interpolation
fpga_spi_write(4, 0x00);
// Register 5 (TX_PSTEP): Zero phase step
fpga_spi_write(5, 0x00);
}
// 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};
// 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;
}
// 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;
}
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;
}
#else
#warning "Building for HackRF_One with CPLD."
#endif