mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-11 09:09:29 +00:00
39424632bb
* Initial commit and pr for HackRF Pro (praline) arch-port to mayhem-firmware. Please see https://github.com/portapack-mayhem/mayhem-firmware/issues/2957. Added flash specifics for -DBOARD=PRALINE. This firmware only builds with toolchain v9.2.1 if hackrf codebase has -B arm in firmware/hackrf_usb/CMakeLists.txt. * Updated CMakeLists.txt per coordination with @HtoToo. For -DBOARD=PRALINE FLASH_MB_SIZE and FLASH_MB_LIMIT_SIZE are now 4. Removed praline specific variable for FLASH limits. * Updated chibios-portapack's board.cpp to support initialization of the HachRF-Pro (praline) FPGA. Added append_fpga_bitstream.py tool to ensure that praline_fgpa.bin bitstream can be appended to -DBOARD=PRALINE produced firmware. In order to ensure successful execution of append_fpga_bitstream.py to append the fpga bitstream we should expect that the bistsream will be located at 0x180000 in flash. This requires that FLASH_MB_LIMIT_SIZE must be 1.5, and FLASH_BYTES_LIMIT_SIZE must be 1535 * 1024. If we want to allow more or less space for the base firmware image sans the fpga bitstream the location of the bistream must be moved to a location other than 0x180000. * Updated location of praline_fpga.bin bitstream to 0x380000 to allow more room for firmware. Firmware now has 3.5MB, or 2MB more available than before as coordinated with @HTotoo. * Expanded #ifndef PRALINE to include og and r9 gpio and pin setup as coordinated with @HTotoo. * Added note for PRALINE FLASH_MB_LIMIT_SIZE and FLASH_BYTES_LIMIT_SIZE to explain why we are using the 3.5 and 3584 values respectively as coordinated with @HTotoo. * Next round of modifications derived heavily, if not entirely from work done by @banandana at https://github.com/Banandana/mayhem-firmware. This commit should power on the HackRF Pro (praline) display, power on the fpga, and enable gpio, and provide debug utilties. There is still a lot of work to be done to fully enable the new praline board with this build and firmware architectural porting effort. However, hackrf-one boards do not seem to be adversely impacted by the #ifdef PRALINE statements, and CMakeLists updates, as far as I have been able to test. * Ran format-code.sh. Updates for this commit are only due to formatting. Tested builds and they seem to work as exptected. * Addressed fixes in firmware/application and firmware/baseband. Stream now flows to capture and looking glass. Issues were related to thread management. Issues were originally addressed by @banandana. * Ran format-code.sh to allow for consistency with autoamted clang checks. * Update hackrf ref repo to mayhem-portapack-hackrf next from https://github.com/portapack-mayhem/hackrf * Addressed format edits necessary to pass clang-format check. * Starting addressing Si5351 Clocks for radio sampling. These updates correctly set the Si5351 clock at start up. There appears to be an issue during runtime when testing with RX Test Init, Capture and Looking glass. * Updated clock_manager.cpp to restore correct function introduced by @banandana when testing with Rx Test Init. * Switched to using decimation for setting the sample rate without changing the Si5351 clock. This assumes that for the praline board Si5351 CLK0 runs at fixed 8 MHz (constant) and the FPGA decimates to get the desired sample rate. For example, for a 1 MHz sample rate -> Si5351 outputs 8 MHz, FPGA decimates by 8. There is still more work needed here, and potential verification that this is the correct way to operate with this new archteitecture. * After deliberating on hackrf_usb hackrf_core.c and radio.c, and reviewing firmware/application/hw/si5351.cpp the original approach of using the aproach detailed in hackrf_core.c sample_rate_frac_set() lines 580-582, via the implementation in firmware/application/hw/si5351.cpp seems like the best place to continue testing efforts. * Tested at ~2.4GHz (2.3 - 2.5) with lookgin glass and was able to receive signals. Added a Signal Path debug app to test gains, and readio mode (receive/transmit). * Added two debug apps for the RFFC507x. Status View and Tuning View. This helped debug some of the potential issues with tuning. * update submodule * format code * Small touch up merging latest next and ensuring build for HackRF One. * Reverted edits to re: firmware/baseband/sd_over_usb/scsi.c and firmware/application/portapack.cpp. Source now builds, had to pull latest hackrf submodule. * Skipped detect hardware for praline board to avoid backscreen in HackRF Pro praline board. --------- Co-authored-by: gullradriel <gullradriel@users.noreply.github.com>
473 lines
17 KiB
C
473 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
|
|
#warning "Building for HackRF_PRO with FPGA."
|
|
|
|
// 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};
|
|
static uint8_t fpga_reg_cache_valid = 0;
|
|
|
|
// 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
|