From 59e8670c51f91a1779e730a23e447bc0dffcec6e Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Thu, 9 Apr 2026 15:41:12 -0400 Subject: [PATCH 001/161] Ensure correct gains displayed in PortaPack UI Previously it was possible, though rare, for an update of an RX gain setting to be displayed during TX mode or for an update of a TX gain setting to be displayed during RX mode. --- firmware/common/ui_portapack.c | 111 +++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 48 deletions(-) diff --git a/firmware/common/ui_portapack.c b/firmware/common/ui_portapack.c index 4e862ca4..8912777f 100644 --- a/firmware/common/ui_portapack.c +++ b/firmware/common/ui_portapack.c @@ -495,6 +495,9 @@ static void portapack_radio_path_item_update( static rf_path_direction_t portapack_direction = RF_PATH_DIRECTION_OFF; static bool portapack_lna_on = false; +static uint32_t tx_if_gain = 0; +static uint32_t rx_if_gain = 0; +static uint32_t rx_bb_gain = 0; static void portapack_radio_path_redraw(void) { @@ -569,51 +572,6 @@ static void portapack_ui_set_sample_rate(uint32_t sample_rate) #endif } -static void portapack_ui_set_direction(const rf_path_direction_t direction) -{ - switch (direction) { - case RF_PATH_DIRECTION_TX: - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_WAVES, - &bitmap_waves_tx); - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_RF_AMP, - portapack_lna_on ? &bitmap_amp_tx : &bitmap_wire_24); - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_BB_LNA_AMP, - &bitmap_amp_tx); - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_BB_VGA_AMP, - &bitmap_wire_24); - portapack_ui_draw_string(RADIO_DRAW_LIST_ITEM_BB_VGA_AMP, " "); - break; - - case RF_PATH_DIRECTION_RX: - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_WAVES, - &bitmap_waves_rx); - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_RF_AMP, - portapack_lna_on ? &bitmap_amp_rx : &bitmap_wire_24); - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_BB_LNA_AMP, - &bitmap_amp_rx); - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_BB_VGA_AMP, - &bitmap_amp_rx); - break; - - case RF_PATH_DIRECTION_OFF: - default: - portapack_radio_path_item_update( - RADIO_DRAW_LIST_ITEM_WAVES, - &bitmap_blank_24); - break; - } - - portapack_direction = direction; -} - static void portapack_ui_set_filter_bw(uint32_t bandwidth) { portapack_ui_draw_bw_mhz(RADIO_DRAW_LIST_ITEM_BB_FILTER, bandwidth); @@ -634,12 +592,18 @@ static void portapack_ui_set_lna_power(bool lna_on) static void portapack_ui_set_bb_lna_gain(const uint32_t gain_db) { - portapack_ui_draw_db(RADIO_DRAW_LIST_ITEM_BB_LNA_AMP, gain_db); + rx_if_gain = gain_db; + if (portapack_direction == RF_PATH_DIRECTION_RX) { + portapack_ui_draw_db(RADIO_DRAW_LIST_ITEM_BB_LNA_AMP, gain_db); + } } static void portapack_ui_set_bb_vga_gain(const uint32_t gain_db) { - portapack_ui_draw_db(RADIO_DRAW_LIST_ITEM_BB_VGA_AMP, gain_db); + rx_bb_gain = gain_db; + if (portapack_direction == RF_PATH_DIRECTION_RX) { + portapack_ui_draw_db(RADIO_DRAW_LIST_ITEM_BB_VGA_AMP, gain_db); + } } static void portapack_ui_set_bb_tx_vga_gain(const uint32_t gain_db) @@ -648,7 +612,10 @@ static void portapack_ui_set_bb_tx_vga_gain(const uint32_t gain_db) * According to the MAX2837 datasheet diagram, there is no baseband gain in the TX path. * This gets called when the TX IF gain is changed. */ - portapack_ui_draw_db(RADIO_DRAW_LIST_ITEM_BB_LNA_AMP, gain_db); + tx_if_gain = gain_db; + if (portapack_direction == RF_PATH_DIRECTION_TX) { + portapack_ui_draw_db(RADIO_DRAW_LIST_ITEM_BB_LNA_AMP, gain_db); + } } static void portapack_ui_set_first_if_frequency(const uint64_t frequency) @@ -724,6 +691,54 @@ static bool portapack_ui_operacake_gpio_compatible(void) return false; } +static void portapack_ui_set_direction(const rf_path_direction_t direction) +{ + portapack_direction = direction; + + switch (direction) { + case RF_PATH_DIRECTION_TX: + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_WAVES, + &bitmap_waves_tx); + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_RF_AMP, + portapack_lna_on ? &bitmap_amp_tx : &bitmap_wire_24); + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_BB_LNA_AMP, + &bitmap_amp_tx); + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_BB_VGA_AMP, + &bitmap_wire_24); + portapack_ui_draw_string(RADIO_DRAW_LIST_ITEM_BB_VGA_AMP, " "); + portapack_ui_set_bb_tx_vga_gain(tx_if_gain); + break; + + case RF_PATH_DIRECTION_RX: + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_WAVES, + &bitmap_waves_rx); + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_RF_AMP, + portapack_lna_on ? &bitmap_amp_rx : &bitmap_wire_24); + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_BB_LNA_AMP, + &bitmap_amp_rx); + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_BB_VGA_AMP, + &bitmap_amp_rx); + portapack_ui_set_bb_vga_gain(rx_bb_gain); + portapack_ui_set_bb_lna_gain(rx_if_gain); + break; + + case RF_PATH_DIRECTION_OFF: + default: + portapack_radio_path_item_update( + RADIO_DRAW_LIST_ITEM_WAVES, + &bitmap_blank_24); + break; + } +} + const hackrf_ui_t portapack_hackrf_ui = { &portapack_ui_init, &portapack_ui_deinit, From 57285507d1c34e64b6e975bdd3a8a4f71d60d001 Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Thu, 9 Apr 2026 18:44:18 -0400 Subject: [PATCH 002/161] Add radio configuration update callback Co-authored-by: Martin Ling --- firmware/common/radio.c | 208 +++++++++------------- firmware/common/radio.h | 22 +++ firmware/common/rf_path.c | 17 +- firmware/common/rf_path.h | 6 +- firmware/hackrf_usb/hackrf_usb.c | 122 +++++++++++++ firmware/hackrf_usb/usb_api_transceiver.c | 8 - 6 files changed, 233 insertions(+), 150 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 0692274c..ac150ca1 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -27,21 +27,16 @@ #include "fixed_point.h" #include "hackrf_core.h" -#include "hackrf_ui.h" #include "mixer.h" #include "max283x.h" #include "platform_detect.h" #include "rf_path.h" -#include "sgpio.h" #include "transceiver_mode.h" #include "tuning.h" #if defined(PRALINE) #include "fpga.h" #include "tune_config.h" #endif -#if defined(HACKRF_ONE) || defined(PRALINE) - #include "operacake.h" -#endif #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) @@ -106,12 +101,12 @@ uint64_t radio_reg_read( return radio->config[bank][reg]; } -static bool radio_update_direction(radio_t* const radio, uint64_t* bank) +static uint32_t radio_update_direction(radio_t* const radio, uint64_t* bank) { const uint64_t requested = bank[RADIO_OPMODE]; if (requested == RADIO_UNSET) { - return false; + return 0; } rf_path_direction_t direction; @@ -129,7 +124,7 @@ static bool radio_update_direction(radio_t* const radio, uint64_t* bank) } radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE] = requested; rf_path_set_direction(&rf_path, direction); - return true; + return (1 << RADIO_OPMODE); } #define MAX_AFE_RATE_HZ (40000000UL) @@ -160,7 +155,7 @@ static inline uint8_t compute_resample_log( #define MAX_MCU_RATE SR_FP_KHZ(21800) #define DEFAULT_MCU_RATE SR_FP_KHZ(10000) -static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank) +static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) { fp_28_36_t rate, afe_rate, previous_rate, previous_afe_rate; uint64_t previous_n; @@ -231,21 +226,15 @@ static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank) afe_rate = radio->sample_rate_cb(afe_rate, true); } } else { - return false; + return 0; } rate = afe_rate >> n; new_rate = (rate != previous_rate); if (new_rate) { radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE] = rate; - if (rate != RADIO_UNSET) { - /* Round to the nearest Hz for display. */ - const uint32_t rate_hz = - (rate + (SR_FP_ONE_HZ >> 1)) / SR_FP_ONE_HZ; - hackrf_ui()->set_sample_rate(rate_hz); - } } - return (new_afe_rate || new_rate || new_n); + return ((new_afe_rate || new_rate || new_n) << RADIO_SAMPLE_RATE); } /* @@ -352,11 +341,10 @@ static const tune_config_t* select_tune_config(uint64_t opmode, fp_40_24_t freq_ } #endif -static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) +static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) { - bool new_freq = false; + uint32_t changed = 0; bool high_lo = false; - bool invert_spectrum = false; const uint64_t requested_rf = bank[RADIO_FREQUENCY_RF]; const uint64_t requested_if = bank[RADIO_FREQUENCY_IF]; @@ -497,24 +485,24 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) if ((freq_if != applied_if) && (freq_if != RADIO_UNSET)) { freq_if = max283x_set_frequency(&max283x, freq_if, true); radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_IF] = freq_if; - new_freq = true; + changed |= (1 << RADIO_FREQUENCY_IF); } if ((freq_lo != applied_lo) && (freq_lo != RADIO_UNSET)) { freq_lo = mixer_set_frequency(&mixer, freq_lo, true); radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_LO] = freq_lo; - new_freq = true; + changed |= (1 << RADIO_FREQUENCY_LO); } if ((img_reject != applied_img_reject) && (img_reject != RADIO_UNSET)) { - rf_path_set_filter(&rf_path, img_reject, opmode); + rf_path_set_filter(&rf_path, img_reject); radio->config[RADIO_BANK_APPLIED][RADIO_IMAGE_REJECT] = img_reject; - new_freq = true; + changed |= (1 << RADIO_IMAGE_REJECT); } if ((rotation != applied_rotation) && (rotation != RADIO_UNSET)) { #ifdef PRALINE fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); #endif radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION] = rotation; - new_freq = true; + changed |= (1 << RADIO_ROTATION); } /* Compute precise RF. */ @@ -525,7 +513,6 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) case RF_PATH_FILTER_LOW_PASS: if (freq_lo > freq_if) { analog_rf = freq_lo - freq_if; - invert_spectrum = true; } else { analog_rf = freq_if - freq_lo; } @@ -540,19 +527,10 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) } if ((freq_rf != applied_rf) && (freq_rf != RADIO_UNSET)) { radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_RF] = freq_rf; - new_freq = true; + changed |= (1 << RADIO_FREQUENCY_RF); } - if (new_freq) { - sgpio_cpld_set_mixer_invert(&sgpio_config, invert_spectrum); - if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { - hackrf_ui()->set_frequency(freq_rf / FP_ONE_MHZ); - } -#if defined(PRALINE) || defined(HACKRF_ONE) - operacake_set_range(freq_rf / FP_ONE_MHZ); -#endif - } - return new_freq; + return changed; } static uint32_t auto_bandwidth(radio_t* const radio, uint64_t opmode) @@ -578,9 +556,9 @@ static uint32_t auto_bandwidth(radio_t* const radio, uint64_t opmode) return lpf_bandwidth; } -static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) +static uint32_t radio_update_bandwidth(radio_t* const radio, uint64_t* bank) { - bool new_bw = false; + uint32_t changed = 0; uint64_t opmode = bank[RADIO_OPMODE]; if (opmode == RADIO_UNSET) { @@ -595,12 +573,12 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; - new_bw = true; + changed |= (1 << RADIO_XCVR_TX_LPF); } if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] != lpf_bandwidth) { max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_RX, lpf_bandwidth); radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; - new_bw = true; + changed |= (1 << RADIO_XCVR_RX_LPF); } bool narrow_lpf_enable = false; bool applied_narrow_lpf_enable = @@ -612,14 +590,14 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) narrowband_filter_set(narrow_lpf_enable); radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF] = narrow_lpf_enable; - new_bw = true; + changed |= (1 << RADIO_RX_NARROW_LPF); } /* Always set HPF bandwidth to 30 kHz for now. */ const max283x_rx_hpf_freq_t hpf_bandwidth = MAX283x_RX_HPF_30_KHZ; if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] != hpf_bandwidth) { max283x_set_rx_hpf_frequency(&max283x, hpf_bandwidth); radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] = hpf_bandwidth; - new_bw = true; + changed |= (1 << RADIO_XCVR_RX_HPF); } #else uint64_t lpf_bandwidth; @@ -646,20 +624,22 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_TX] = lpf_bandwidth; radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; - new_bw = true; + changed |= + ((1 << RADIO_BB_BANDWIDTH_RX) | (1 << RADIO_BB_BANDWIDTH_TX) | + (1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF)); } #endif - return new_bw; + return changed; } #define DEFAULT_GAIN_RF (0) #define DEFAULT_GAIN_IF (16) #define DEFAULT_GAIN_BB (12) -static bool radio_update_gain(radio_t* const radio, uint64_t* bank) +static uint32_t radio_update_gain(radio_t* const radio, uint64_t* bank) { - bool new_gain = false; - uint64_t gain; + uint32_t changed = 0; + uint64_t gain, tx_rf_gain, rx_rf_gain; uint64_t opmode = bank[RADIO_OPMODE]; if (opmode == RADIO_UNSET) { opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE]; @@ -673,34 +653,34 @@ static bool radio_update_gain(radio_t* const radio, uint64_t* bank) switch (opmode) { case TRANSCEIVER_MODE_TX: case TRANSCEIVER_MODE_SS: - gain = bank[RADIO_GAIN_TX_RF]; - if (gain == RADIO_UNSET) { - gain = DEFAULT_GAIN_RF; + tx_rf_gain = bank[RADIO_GAIN_TX_RF]; + if (tx_rf_gain == RADIO_UNSET) { + tx_rf_gain = DEFAULT_GAIN_RF; } - rf_path_set_lna(&rf_path, gain); - if (radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_RF] != gain) { - new_gain = true; - } - radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_RF] = gain; - radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_RF] = 0; + rf_path_set_lna(&rf_path, tx_rf_gain); + rx_rf_gain = 0; break; case TRANSCEIVER_MODE_RX: case TRANSCEIVER_MODE_RX_SWEEP: - gain = bank[RADIO_GAIN_RX_RF]; - if (gain == RADIO_UNSET) { - gain = DEFAULT_GAIN_RF; + rx_rf_gain = bank[RADIO_GAIN_RX_RF]; + if (rx_rf_gain == RADIO_UNSET) { + rx_rf_gain = DEFAULT_GAIN_RF; } - rf_path_set_lna(&rf_path, gain); - if (radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_RF] != gain) { - new_gain = true; - } - radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_RF] = gain; - radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_RF] = 0; + rf_path_set_lna(&rf_path, rx_rf_gain); + tx_rf_gain = 0; break; default: rf_path_set_lna(&rf_path, 0); - radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_RF] = 0; - radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_RF] = 0; + tx_rf_gain = 0; + rx_rf_gain = 0; + } + if (radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_RF] != tx_rf_gain) { + radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_RF] = tx_rf_gain; + changed |= (1 << RADIO_GAIN_TX_RF); + } + if (radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_RF] != rx_rf_gain) { + radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_RF] = rx_rf_gain; + changed |= (1 << RADIO_GAIN_RX_RF); } gain = bank[RADIO_GAIN_TX_IF]; @@ -708,11 +688,11 @@ static bool radio_update_gain(radio_t* const radio, uint64_t* bank) (gain != radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_IF])) { max283x_set_txvga_gain(&max283x, gain); radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_IF] = gain; - new_gain = true; + changed |= (1 << RADIO_GAIN_TX_IF); } else if (radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_IF] == RADIO_UNSET) { max283x_set_txvga_gain(&max283x, DEFAULT_GAIN_IF); radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_TX_IF] = DEFAULT_GAIN_IF; - new_gain = true; + changed |= (1 << RADIO_GAIN_TX_IF); } gain = bank[RADIO_GAIN_RX_IF]; @@ -720,11 +700,11 @@ static bool radio_update_gain(radio_t* const radio, uint64_t* bank) (gain != radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_IF])) { max283x_set_lna_gain(&max283x, gain); radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_IF] = gain; - new_gain = true; + changed |= (1 << RADIO_GAIN_RX_IF); } else if (radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_IF] == RADIO_UNSET) { max283x_set_lna_gain(&max283x, DEFAULT_GAIN_IF); radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_IF] = DEFAULT_GAIN_IF; - new_gain = true; + changed |= (1 << RADIO_GAIN_RX_IF); } gain = bank[RADIO_GAIN_RX_BB]; @@ -732,39 +712,39 @@ static bool radio_update_gain(radio_t* const radio, uint64_t* bank) (gain != radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_BB])) { max283x_set_vga_gain(&max283x, gain); radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_BB] = gain; - new_gain = true; + changed |= (1 << RADIO_GAIN_RX_BB); } else if (radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_BB] == RADIO_UNSET) { max283x_set_vga_gain(&max283x, DEFAULT_GAIN_BB); radio->config[RADIO_BANK_APPLIED][RADIO_GAIN_RX_BB] = DEFAULT_GAIN_BB; - new_gain = true; + changed |= (1 << RADIO_GAIN_RX_BB); } - return new_gain; + return changed; } -static bool radio_update_bias_tee(radio_t* const radio, uint64_t* bank) +static uint32_t radio_update_bias_tee(radio_t* const radio, uint64_t* bank) { if (detected_platform() == BOARD_ID_JAWBREAKER) { - return false; + return 0; } const uint64_t requested = bank[RADIO_BIAS_TEE]; const bool enable = requested; if (requested == RADIO_UNSET) { - return false; + return 0; } if (radio->config[RADIO_BANK_APPLIED][RADIO_BIAS_TEE] == (uint64_t) enable) { - return false; + return 0; } rf_path_set_antenna(&rf_path, enable); radio->config[RADIO_BANK_APPLIED][RADIO_BIAS_TEE] = enable; - return true; + return (1 << RADIO_BIAS_TEE); } -static bool radio_update_trigger(radio_t* const radio, uint64_t* bank) +static uint32_t radio_update_trigger(radio_t* const radio, uint64_t* bank) { const uint64_t requested = bank[RADIO_TRIGGER]; bool enable = requested; @@ -774,20 +754,20 @@ static bool radio_update_trigger(radio_t* const radio, uint64_t* bank) } if (radio->config[RADIO_BANK_APPLIED][RADIO_TRIGGER] == (uint64_t) enable) { - return false; + return 0; } trigger_enable(enable); radio->config[RADIO_BANK_APPLIED][RADIO_TRIGGER] = enable; - return true; + return (1 << RADIO_TRIGGER); } -static bool radio_update_dc_block(radio_t* const radio, uint64_t* bank) +static uint32_t radio_update_dc_block(radio_t* const radio, uint64_t* bank) { #ifndef PRALINE (void) radio; (void) bank; - return false; + return 0; #else const uint64_t requested = bank[RADIO_DC_BLOCK]; bool enable = requested; @@ -797,12 +777,12 @@ static bool radio_update_dc_block(radio_t* const radio, uint64_t* bank) } if (radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] == (uint64_t) enable) { - return false; + return 0; } fpga_set_rx_dc_block_enable(&fpga, enable); radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; - return true; + return (1 << RADIO_DC_BLOCK); #endif } @@ -811,6 +791,7 @@ bool radio_update(radio_t* const radio) uint64_t tmp_bank[RADIO_NUM_REGS]; nvic_disable_irq(NVIC_USB0_IRQ); uint32_t dirty = radio->regs_dirty; + uint32_t changed = 0; if (dirty == 0) { nvic_enable_irq(NVIC_USB0_IRQ); return false; @@ -819,56 +800,41 @@ bool radio_update(radio_t* const radio) memcpy(&tmp_bank[0], &(radio->config[RADIO_BANK_ACTIVE][0]), sizeof(tmp_bank)); nvic_enable_irq(NVIC_USB0_IRQ); - bool dir = false; - bool rate = false; - bool freq = false; - bool bw = false; - bool gain = false; - bool bias = false; - bool trig = false; - bool dc = false; - - if ((dirty & - ((1 << RADIO_SAMPLE_RATE) | (1 << RADIO_RESAMPLE_TX) | - (1 << RADIO_RESAMPLE_RX))) || + if ((dirty & RADIO_REG_GROUP_RATE) || ((detected_platform() == BOARD_ID_PRALINE) && (dirty & (1 << RADIO_OPMODE)))) { - rate = radio_update_sample_rate(radio, &tmp_bank[0]); + changed |= radio_update_sample_rate(radio, &tmp_bank[0]); } - if ((dirty & - ((1 << RADIO_FREQUENCY_RF) | (1 << RADIO_FREQUENCY_IF) | - (1 << RADIO_FREQUENCY_LO) | (1 << RADIO_IMAGE_REJECT) | - (1 << RADIO_ROTATION))) || + if ((dirty & RADIO_REG_GROUP_FREQ) || ((detected_platform() == BOARD_ID_PRALINE) && - (rate || (dirty & (1 << RADIO_OPMODE))))) { - freq = radio_update_frequency(radio, &tmp_bank[0]); + ((changed & RADIO_REG_GROUP_RATE) || (dirty & (1 << RADIO_OPMODE))))) { + changed |= radio_update_frequency(radio, &tmp_bank[0]); } - if ((dirty & - ((1 << RADIO_BB_BANDWIDTH_TX) | (1 << RADIO_BB_BANDWIDTH_RX) | - (1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF) | - (1 << RADIO_XCVR_RX_HPF) | (1 << RADIO_RX_NARROW_LPF))) || - ((detected_platform() == BOARD_ID_PRALINE) && (rate || freq))) { - bw = radio_update_bandwidth(radio, &tmp_bank[0]); + if ((dirty & RADIO_REG_GROUP_BW) || + ((detected_platform() == BOARD_ID_PRALINE) && + (changed & (RADIO_REG_GROUP_RATE | RADIO_REG_GROUP_FREQ)))) { + changed |= radio_update_bandwidth(radio, &tmp_bank[0]); } - if (dirty & - ((1 << RADIO_GAIN_TX_RF) | (1 << RADIO_GAIN_TX_IF) | (1 << RADIO_GAIN_RX_RF) | - (1 << RADIO_GAIN_RX_IF) | (1 << RADIO_GAIN_RX_BB) | (1 << RADIO_OPMODE))) { - gain = radio_update_gain(radio, &tmp_bank[0]); + if (dirty & (RADIO_REG_GROUP_GAIN | (1 << RADIO_OPMODE))) { + changed |= radio_update_gain(radio, &tmp_bank[0]); } if (dirty & ((1 << RADIO_BIAS_TEE) | (1 << RADIO_OPMODE))) { - bias = radio_update_bias_tee(radio, &tmp_bank[0]); + changed |= radio_update_bias_tee(radio, &tmp_bank[0]); } if (dirty & (1 << RADIO_TRIGGER)) { - trig = radio_update_trigger(radio, &tmp_bank[0]); + changed |= radio_update_trigger(radio, &tmp_bank[0]); } if (dirty & (1 << RADIO_DC_BLOCK)) { - dc = radio_update_dc_block(radio, &tmp_bank[0]); + changed |= radio_update_dc_block(radio, &tmp_bank[0]); } if (dirty & (1 << RADIO_OPMODE)) { - dir = radio_update_direction(radio, &tmp_bank[0]); + changed |= radio_update_direction(radio, &tmp_bank[0]); } - return trig || dir || rate || freq || bw || gain || bias || dc; + if (radio->update_cb) { + radio->update_cb(changed); + } + return (changed != 0); } void radio_switch_opmode(radio_t* const radio, const transceiver_mode_t mode) diff --git a/firmware/common/radio.h b/firmware/common/radio.h index a0e40730..83f72441 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -177,6 +177,20 @@ typedef enum { #define RADIO_NUM_REGS (23) #define RADIO_UNSET (0xffffffffffffffff) +/* register groups for bitfield convenience */ +#define RADIO_REG_GROUP_RATE \ + ((1 << RADIO_SAMPLE_RATE) | (1 << RADIO_RESAMPLE_TX) | (1 << RADIO_RESAMPLE_RX)) +#define RADIO_REG_GROUP_FREQ \ + ((1 << RADIO_FREQUENCY_RF) | (1 << RADIO_FREQUENCY_IF) | \ + (1 << RADIO_FREQUENCY_LO) | (1 << RADIO_IMAGE_REJECT) | (1 << RADIO_ROTATION)) +#define RADIO_REG_GROUP_BW \ + ((1 << RADIO_BB_BANDWIDTH_TX) | (1 << RADIO_BB_BANDWIDTH_RX) | \ + (1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF) | \ + (1 << RADIO_XCVR_RX_HPF) | (1 << RADIO_RX_NARROW_LPF)) +#define RADIO_REG_GROUP_GAIN \ + ((1 << RADIO_GAIN_TX_RF) | (1 << RADIO_GAIN_TX_IF) | (1 << RADIO_GAIN_RX_RF) | \ + (1 << RADIO_GAIN_RX_IF) | (1 << RADIO_GAIN_RX_BB)) + /** * Register bank RADIO_BANK_ACTIVE stores the active configuration. Active * register settings are copied to the applied register when applied. @@ -207,11 +221,19 @@ typedef enum { */ typedef fp_28_36_t (*sample_rate_fn)(const fp_28_36_t sample_rate, const bool program); +/** + * An optional callback may be provided that is called after each time the + * radio configuration has been updated. The single argument is a bitfield + * indicating registers that have been changed. + */ +typedef void (*update_fn)(const uint32_t changed_regs); + typedef struct { radio_config_mode_t config_mode; uint64_t config[RADIO_NUM_BANKS][RADIO_NUM_REGS]; volatile uint32_t regs_dirty; sample_rate_fn sample_rate_cb; + update_fn update_cb; } radio_t; void radio_init(radio_t* const radio); diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index cf77f70c..9fdb20de 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -28,13 +28,11 @@ #endif #include "hackrf_core.h" -#include "hackrf_ui.h" #include "max283x.h" #include "max5864.h" #include "mixer.h" #include "platform_detect.h" #include "sgpio.h" -#include "transceiver_mode.h" #if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) #include "platform_scu.h" #endif @@ -534,14 +532,9 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d } switchctrl_set(rf_path, rf_path->switchctrl); - - hackrf_ui()->set_direction(direction); } -void rf_path_set_filter( - rf_path_t* const rf_path, - const rf_path_filter_t filter, - const transceiver_mode_t opmode) +void rf_path_set_filter(rf_path_t* const rf_path, const rf_path_filter_t filter) { switch (filter) { default: @@ -563,10 +556,6 @@ void rf_path_set_filter( } switchctrl_set(rf_path, rf_path->switchctrl); - - if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { - hackrf_ui()->set_filter(filter); - } } void rf_path_set_lna(rf_path_t* const rf_path, const uint_fast8_t enable) @@ -590,8 +579,6 @@ void rf_path_set_lna(rf_path_t* const rf_path, const uint_fast8_t enable) } switchctrl_set(rf_path, rf_path->switchctrl); - - hackrf_ui()->set_lna_power(enable); } /* antenna port power control */ @@ -604,6 +591,4 @@ void rf_path_set_antenna(rf_path_t* const rf_path, const uint_fast8_t enable) } switchctrl_set(rf_path, rf_path->switchctrl); - - hackrf_ui()->set_antenna_bias(enable); } diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index d9f45a30..1ac0e243 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -25,7 +25,6 @@ #include -#include "transceiver_mode.h" #if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) #include "gpio.h" #endif @@ -92,10 +91,7 @@ void rf_path_init(rf_path_t* const rf_path); void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t direction); -void rf_path_set_filter( - rf_path_t* const rf_path, - const rf_path_filter_t filter, - const transceiver_mode_t opmode); +void rf_path_set_filter(rf_path_t* const rf_path, const rf_path_filter_t filter); void rf_path_set_lna(rf_path_t* const rf_path, const uint_fast8_t enable); void rf_path_set_antenna(rf_path_t* const rf_path, const uint_fast8_t enable); diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index ee8f897d..8a165e19 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -300,6 +302,125 @@ struct fpga_loader_t fpga_loader = { }; #endif +void radio_changed(const uint32_t changed) +{ + const uint64_t opmode = radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_OPMODE); + + if (changed & (1 << RADIO_OPMODE)) { + switch (opmode) { + case TRANSCEIVER_MODE_TX: + case TRANSCEIVER_MODE_SS: + hackrf_ui()->set_direction(RF_PATH_DIRECTION_TX); + break; + case TRANSCEIVER_MODE_RX: + case TRANSCEIVER_MODE_RX_SWEEP: + hackrf_ui()->set_direction(RF_PATH_DIRECTION_RX); + break; + default: + hackrf_ui()->set_direction(RF_PATH_DIRECTION_OFF); + break; + } + } + if (changed & (1 << RADIO_SAMPLE_RATE)) { + const uint64_t rate = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_SAMPLE_RATE); + hackrf_ui()->set_sample_rate(rate / SR_FP_ONE_HZ); + } + if (changed & (RADIO_REG_GROUP_FREQ)) { + const uint64_t img_reject = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_IMAGE_REJECT); + const uint64_t freq_lo = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_FREQUENCY_LO); + const uint64_t freq_if = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_FREQUENCY_IF); + const bool invert_spectrum = + ((img_reject == RF_PATH_FILTER_LOW_PASS) && (freq_lo > freq_if)); + sgpio_cpld_set_mixer_invert(&sgpio_config, invert_spectrum); + } + if (changed & (1 << RADIO_FREQUENCY_RF)) { + const uint64_t freq = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_FREQUENCY_RF); + if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { + hackrf_ui()->set_frequency(freq / FP_ONE_HZ); + } +#if defined(HACKRF_ONE) || defined(PRALINE) + operacake_set_range(freq / FP_ONE_MHZ); +#endif + } + if (changed & (1 << RADIO_IMAGE_REJECT)) { + if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { + const uint64_t img_reject = radio_reg_read( + &radio, + RADIO_BANK_APPLIED, + RADIO_IMAGE_REJECT); + hackrf_ui()->set_filter(img_reject); + } + } + if (changed & + ((1 << RADIO_BB_BANDWIDTH_TX) | (1 << RADIO_BB_BANDWIDTH_RX) | + (1 << RADIO_OPMODE))) { + uint64_t bw; + + switch (opmode) { + case TRANSCEIVER_MODE_TX: + case TRANSCEIVER_MODE_SS: + bw = radio_reg_read( + &radio, + RADIO_BANK_APPLIED, + RADIO_BB_BANDWIDTH_TX); + break; + default: + bw = radio_reg_read( + &radio, + RADIO_BANK_APPLIED, + RADIO_BB_BANDWIDTH_RX); + } + hackrf_ui()->set_filter_bw(bw); + } + if (changed & + ((1 << RADIO_GAIN_TX_RF) | (1 << RADIO_GAIN_RX_RF) | (1 << RADIO_OPMODE))) { + const uint64_t tx_rf = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_GAIN_TX_RF); + const uint64_t rx_rf = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_GAIN_RX_RF); + bool enable; + + switch (opmode) { + case TRANSCEIVER_MODE_TX: + case TRANSCEIVER_MODE_SS: + enable = tx_rf; + break; + case TRANSCEIVER_MODE_RX: + case TRANSCEIVER_MODE_RX_SWEEP: + enable = rx_rf; + break; + default: + enable = false; + } + hackrf_ui()->set_lna_power(enable); + } + if (changed & (1 << RADIO_GAIN_TX_IF)) { + const uint64_t tx_if = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_GAIN_TX_IF); + hackrf_ui()->set_bb_tx_vga_gain(tx_if); + } + if (changed & (1 << RADIO_GAIN_RX_IF)) { + const uint64_t rx_if = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_GAIN_RX_IF); + hackrf_ui()->set_bb_lna_gain(rx_if); + } + if (changed & (1 << RADIO_GAIN_RX_BB)) { + const uint64_t rx_bb = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_GAIN_RX_BB); + hackrf_ui()->set_bb_vga_gain(rx_bb); + } + if (changed & (1 << RADIO_BIAS_TEE)) { + const uint64_t enable = + radio_reg_read(&radio, RADIO_BANK_APPLIED, RADIO_BIAS_TEE); + hackrf_ui()->set_antenna_bias(enable); + } +} + int main(void) { // Copy M0 image from ROM before SPIFI is disabled @@ -369,6 +490,7 @@ int main(void) fpga_spi_selftest(); fpga_sgpio_selftest(); #endif + radio.update_cb = radio_changed; radio_init(&radio); #if (defined HACKRF_ONE || defined PRALINE) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index d4b47f9f..12c516c9 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -83,7 +82,6 @@ usb_request_status_t usb_vendor_request_set_baseband_filter_bandwidth( RADIO_BANK_ACTIVE, RADIO_BB_BANDWIDTH_RX, bandwidth); - hackrf_ui()->set_filter_bw(bandwidth); usb_transfer_schedule_ack(endpoint->in); } return USB_REQUEST_STATUS_OK; @@ -231,7 +229,6 @@ usb_request_status_t usb_vendor_request_set_lna_gain( uint8_t gain = endpoint->setup.index; radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_GAIN_RX_IF, gain); endpoint->buffer[0] = RADIO_OK; - hackrf_ui()->set_bb_lna_gain(gain); usb_transfer_schedule_block( endpoint->in, &endpoint->buffer, @@ -251,7 +248,6 @@ usb_request_status_t usb_vendor_request_set_vga_gain( uint8_t gain = endpoint->setup.index; radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_GAIN_RX_BB, gain); endpoint->buffer[0] = RADIO_OK; - hackrf_ui()->set_bb_vga_gain(gain); usb_transfer_schedule_block( endpoint->in, &endpoint->buffer, @@ -271,7 +267,6 @@ usb_request_status_t usb_vendor_request_set_txvga_gain( uint8_t gain = endpoint->setup.index; radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_GAIN_TX_IF, gain); endpoint->buffer[0] = RADIO_OK; - hackrf_ui()->set_bb_tx_vga_gain(gain); usb_transfer_schedule_block( endpoint->in, &endpoint->buffer, @@ -333,7 +328,6 @@ void transceiver_shutdown(void) void transceiver_startup(const transceiver_mode_t mode) { radio_switch_opmode(&radio, mode); - hackrf_ui()->set_transceiver_mode(mode); switch (mode) { case TRANSCEIVER_MODE_RX_SWEEP: @@ -486,8 +480,6 @@ void tx_mode(uint32_t seq) void off_mode(uint32_t seq) { - hackrf_ui()->set_transceiver_mode(TRANSCEIVER_MODE_OFF); - while (transceiver_request.seq == seq) { radio_update(&radio); } From 43454dd512c5582cce2629341a3259c0291642dc Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 12 Mar 2026 02:02:59 +0000 Subject: [PATCH 003/161] Move sgpio_configure calls to radio_changed callback. --- firmware/common/rf_path.c | 5 ----- firmware/hackrf_usb/hackrf_usb.c | 3 +++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 9fdb20de..4407960a 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -32,7 +32,6 @@ #include "max5864.h" #include "mixer.h" #include "platform_detect.h" -#include "sgpio.h" #if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) #include "platform_scu.h" #endif @@ -479,7 +478,6 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d max5864_tx(&max5864); ssp1_set_mode_max283x(); max283x_tx(&max283x); - sgpio_configure(&sgpio_config, SGPIO_DIRECTION_TX); break; case RF_PATH_DIRECTION_RX: @@ -497,7 +495,6 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d max5864_rx(&max5864); ssp1_set_mode_max283x(); max283x_rx(&max283x); - sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX); break; #ifdef PRALINE @@ -513,7 +510,6 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d } else { max283x_rx_calibration(&max283x); } - sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX); break; #endif @@ -527,7 +523,6 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d max5864_standby(&max5864); ssp1_set_mode_max283x(); max283x_set_mode(&max283x, MAX283x_MODE_STANDBY); - sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX); break; } diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 8a165e19..c365e9bc 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -310,13 +310,16 @@ void radio_changed(const uint32_t changed) switch (opmode) { case TRANSCEIVER_MODE_TX: case TRANSCEIVER_MODE_SS: + sgpio_configure(&sgpio_config, SGPIO_DIRECTION_TX); hackrf_ui()->set_direction(RF_PATH_DIRECTION_TX); break; case TRANSCEIVER_MODE_RX: case TRANSCEIVER_MODE_RX_SWEEP: + sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX); hackrf_ui()->set_direction(RF_PATH_DIRECTION_RX); break; default: + sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX); hackrf_ui()->set_direction(RF_PATH_DIRECTION_OFF); break; } From 697d10fa5364a395752e5f53991ec15d158ee622 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 20:48:53 +0100 Subject: [PATCH 004/161] Universalize: platform_gpio.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/platform_gpio.c | 381 ++++++++++++++++++++------------ firmware/common/platform_gpio.h | 29 ++- 2 files changed, 255 insertions(+), 155 deletions(-) diff --git a/firmware/common/platform_gpio.c b/firmware/common/platform_gpio.c index 3e0db2c6..f9a44903 100644 --- a/firmware/common/platform_gpio.c +++ b/firmware/common/platform_gpio.c @@ -23,9 +23,7 @@ #include -#if defined(HACKRF_ONE) - #include "platform_detect.h" -#endif +#include "platform_detect.h" // clang-format off @@ -36,180 +34,287 @@ const platform_gpio_t* platform_gpio(void) return _platform_gpio; } + board_id_t board_id = detected_platform(); static platform_gpio_t gpio; /* LEDs */ gpio.led[0] = &GPIO2_1; gpio.led[1] = &GPIO2_2; gpio.led[2] = &GPIO2_8; + switch (board_id) { + case BOARD_ID_RAD1O: #if defined(RAD1O) - gpio.led[3] = &GPIO5_26; -#elif defined(PRALINE) - gpio.led[3] = &GPIO4_6; + gpio.led[3] = &GPIO5_26; #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.led[3] = &GPIO4_6; +#endif + break; + default: + break; + } /* Power Supply Control */ -#if defined(PRALINE) - gpio.gpio_1v2_enable = &GPIO4_7; - gpio.gpio_3v3aux_enable_n = &GPIO5_15; -#else - gpio.gpio_1v8_enable = &GPIO3_6; + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.gpio_1v2_enable = &GPIO4_7; + gpio.gpio_3v3aux_enable_n = &GPIO5_15; #endif + break; + default: + gpio.gpio_1v8_enable = &GPIO3_6; + break; + } /* MAX283x GPIO (XCVR_CTL / CS_XCVR) PinMux */ -#if defined(PRALINE) - gpio.max283x_select = &GPIO6_28; - gpio.max283x_enable = &GPIO7_1; - gpio.max283x_rx_enable = &GPIO7_2; - gpio.max2831_rxhp = &GPIO6_29; - gpio.max2831_ld = &GPIO4_11; -#else - gpio.max283x_select = &GPIO0_15; - gpio.max283x_enable = &GPIO2_6; - gpio.max283x_rx_enable = &GPIO2_5; - gpio.max283x_tx_enable = &GPIO2_4; + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.max283x_select = &GPIO6_28; + gpio.max283x_enable = &GPIO7_1; + gpio.max283x_rx_enable = &GPIO7_2; + gpio.max2831_rxhp = &GPIO6_29; + gpio.max2831_ld = &GPIO4_11; #endif + break; + default: + gpio.max283x_select = &GPIO0_15; + gpio.max283x_enable = &GPIO2_6; + gpio.max283x_rx_enable = &GPIO2_5; + gpio.max283x_tx_enable = &GPIO2_4; + break; + } /* MAX5864 SPI chip select (AD_CS / CS_AD) GPIO PinMux */ -#if defined(PRALINE) - gpio.max5864_select = &GPIO6_30; -#else - gpio.max5864_select = &GPIO2_7; + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.max5864_select = &GPIO6_30; #endif + break; + default: + gpio.max5864_select = &GPIO2_7; + break; + } /* RF supply (VAA) control */ -#if defined(HACKRF_ONE) - gpio.vaa_disable = &GPIO2_9; -#elif defined(PRALINE) - gpio.vaa_disable = &GPIO4_1; -#elif defined(RAD1O) - gpio.vaa_enable = &GPIO2_9; + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio.vaa_disable = &GPIO2_9; #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.vaa_disable = &GPIO4_1; +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + gpio.vaa_enable = &GPIO2_9; +#endif + break; + default: + break; + } /* W25Q80BV Flash */ - gpio.w25q80bv_hold = &GPIO1_14; - gpio.w25q80bv_wp = &GPIO1_15; - gpio.w25q80bv_select = &GPIO5_11; + gpio.w25q80bv_hold = &GPIO1_14; + gpio.w25q80bv_wp = &GPIO1_15; + gpio.w25q80bv_select = &GPIO5_11; /* RF switch control */ -#if defined(HACKRF_ONE) - gpio.hp = &GPIO2_0; - gpio.lp = &GPIO2_10; - gpio.tx_mix_bp = &GPIO2_11; - gpio.no_mix_bypass = &GPIO1_0; - gpio.rx_mix_bp = &GPIO2_12; - gpio.tx_amp = &GPIO2_15; - gpio.tx = &GPIO5_15; - gpio.mix_bypass = &GPIO5_16; - gpio.rx = &GPIO5_5; - gpio.no_tx_amp_pwr = &GPIO3_5; - gpio.amp_bypass = &GPIO0_14; - gpio.rx_amp = &GPIO1_11; - gpio.no_rx_amp_pwr = &GPIO1_12; - // HackRF One rev 9 - gpio.h1r9_rx = &GPIO0_7; - gpio.h1r9_no_ant_pwr = &GPIO2_4; -#elif defined(RAD1O) - gpio.tx_rx_n = &GPIO1_11; - gpio.tx_rx = &GPIO0_14; - gpio.by_mix = &GPIO1_12; - gpio.by_mix_n = &GPIO2_10; - gpio.by_amp = &GPIO1_0; - gpio.by_amp_n = &GPIO5_5; - gpio.mixer_en = &GPIO5_16; - gpio.low_high_filt = &GPIO2_11; - gpio.low_high_filt_n = &GPIO2_12; - gpio.tx_amp = &GPIO2_15; - gpio.rx_lna = &GPIO5_15; -#elif defined(PRALINE) - gpio.tx_en = &GPIO3_4; - gpio.mix_en_n = &GPIO3_2; - gpio.mix_en_n_r1_0 = &GPIO5_6; - gpio.lpf_en = &GPIO4_8; - gpio.rf_amp_en = &GPIO4_9; - gpio.ant_bias_en_n = &GPIO1_12; + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio.hp = &GPIO2_0; + gpio.lp = &GPIO2_10; + gpio.tx_mix_bp = &GPIO2_11; + gpio.no_mix_bypass = &GPIO1_0; + gpio.rx_mix_bp = &GPIO2_12; + gpio.tx_amp = &GPIO2_15; + gpio.tx = &GPIO5_15; + gpio.mix_bypass = &GPIO5_16; + gpio.rx = &GPIO5_5; + gpio.no_tx_amp_pwr = &GPIO3_5; + gpio.amp_bypass = &GPIO0_14; + gpio.rx_amp = &GPIO1_11; + gpio.no_rx_amp_pwr = &GPIO1_12; + // HackRF One rev 9 + gpio.h1r9_rx = &GPIO0_7; + gpio.h1r9_no_ant_pwr = &GPIO2_4; #endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + gpio.tx_rx_n = &GPIO1_11; + gpio.tx_rx = &GPIO0_14; + gpio.by_mix = &GPIO1_12; + gpio.by_mix_n = &GPIO2_10; + gpio.by_amp = &GPIO1_0; + gpio.by_amp_n = &GPIO5_5; + gpio.mixer_en = &GPIO5_16; + gpio.low_high_filt = &GPIO2_11; + gpio.low_high_filt_n = &GPIO2_12; + gpio.tx_amp = &GPIO2_15; + gpio.rx_lna = &GPIO5_15; +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.tx_en = &GPIO3_4; + gpio.mix_en_n = &GPIO3_2; + gpio.mix_en_n_r1_0 = &GPIO5_6; + gpio.lpf_en = &GPIO4_8; + gpio.rf_amp_en = &GPIO4_9; + gpio.ant_bias_en_n = &GPIO1_12; +#endif + break; + case BOARD_ID_JELLYBEAN: + case BOARD_ID_JAWBREAKER: + break; + default: + break; + } /* CPLD JTAG interface GPIO pins_FPGA config pins in Praline */ - gpio.cpld_tck = &GPIO3_0; -#if defined(PRALINE) - gpio.fpga_cfg_creset = &GPIO2_11; - gpio.fpga_cfg_cdone = &GPIO5_14; - gpio.fpga_cfg_spi_cs = &GPIO2_10; -#else - gpio.cpld_tdo = &GPIO5_18; + gpio.cpld_tck = &GPIO3_0; + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.fpga_cfg_creset = &GPIO2_11; + gpio.fpga_cfg_cdone = &GPIO5_14; + gpio.fpga_cfg_spi_cs = &GPIO2_10; #endif -#if defined(HACKRF_ONE) || defined(RAD1O) - gpio.cpld_tms = &GPIO3_4; - gpio.cpld_tdi = &GPIO3_1; + break; + default: +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio.cpld_tdo = &GPIO5_18; + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_RAD1O: + gpio.cpld_tms = &GPIO3_4; + gpio.cpld_tdi = &GPIO3_1; + break; + case BOARD_ID_JAWBREAKER: + gpio.cpld_tms = &GPIO3_1; + gpio.cpld_tdi = &GPIO3_4; + break; + default: + break; + } #endif -#if defined(JAWBREAKER) - gpio.cpld_tms = &GPIO3_1; - gpio.cpld_tdi = &GPIO3_4; -#endif -#if defined(HACKRF_ONE) || defined(PRALINE) - gpio.cpld_pp_tms = &GPIO1_1; - gpio.cpld_pp_tdo = &GPIO1_8; + break; + } + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + gpio.cpld_pp_tms = &GPIO1_1; + gpio.cpld_pp_tdo = &GPIO1_8; #endif + break; + default: + break; + } /* Other CPLD interface GPIO pins */ -#if !defined(PRALINE) - gpio.trigger_enable = &GPIO5_12; + switch (board_id) { + case BOARD_ID_PRALINE: + break; + default: +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio.trigger_enable = &GPIO5_12; #endif - gpio.q_invert = &GPIO0_13; + break; + } + gpio.q_invert = &GPIO0_13; /* RFFC5071 GPIO serial interface PinMux */ -#if defined(JAWBREAKER) || defined(HACKRF_ONE) - gpio.rffc5072_select = &GPIO2_13; - gpio.rffc5072_clock = &GPIO5_6; - gpio.rffc5072_data = &GPIO3_3; - gpio.rffc5072_reset = &GPIO2_14; -#elif defined(RAD1O) - gpio.vco_ce = &GPIO2_13; - gpio.vco_sclk = &GPIO5_6; - gpio.vco_sdata = &GPIO3_3; - gpio.vco_le = &GPIO2_14; - gpio.vco_mux = &GPIO5_25; - gpio.synt_rfout_en = &GPIO3_5; -#elif defined(PRALINE) - gpio.rffc5072_select = &GPIO2_13; - gpio.rffc5072_clock = &GPIO5_18; - gpio.rffc5072_data = &GPIO4_14; - gpio.rffc5072_reset = &GPIO2_14; - gpio.rffc5072_ld = &GPIO6_25; + switch (board_id) { + case BOARD_ID_JAWBREAKER: + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio.rffc5072_select = &GPIO2_13; + gpio.rffc5072_clock = &GPIO5_6; + gpio.rffc5072_data = &GPIO3_3; + gpio.rffc5072_reset = &GPIO2_14; #endif - - /* Praline */ -#if defined(PRALINE) - gpio.p2_ctrl0 = &GPIO7_3; - gpio.p2_ctrl1 = &GPIO7_4; - gpio.p1_ctrl0 = &GPIO0_14; - gpio.p1_ctrl1 = &GPIO5_16; - gpio.p1_ctrl2 = &GPIO3_5; - gpio.clkin_ctrl = &GPIO0_15; - gpio.aa_en = &GPIO1_7; - gpio.trigger_in = &GPIO6_26; - gpio.trigger_out = &GPIO5_6; - gpio.pps_out = &GPIO5_5; + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + gpio.vco_ce = &GPIO2_13; + gpio.vco_sclk = &GPIO5_6; + gpio.vco_sdata = &GPIO3_3; + gpio.vco_le = &GPIO2_14; + gpio.vco_mux = &GPIO5_25; + gpio.synt_rfout_en = &GPIO3_5; #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.rffc5072_select = &GPIO2_13; + gpio.rffc5072_clock = &GPIO5_18; + gpio.rffc5072_data = &GPIO4_14; + gpio.rffc5072_reset = &GPIO2_14; + gpio.rffc5072_ld = &GPIO6_25; +#endif + break; + default: + break; + } - /* HackRF One r9 */ -#if defined(HACKRF_ONE) - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - /* HackRF One r9 clock control */ - gpio.h1r9_clkin_en = &GPIO5_15; - gpio.h1r9_clkout_en = &GPIO0_9; - gpio.h1r9_mcu_clk_en = &GPIO0_8; - - /* HackRF One r9 power control */ - gpio.h1r9_1v8_enable = &GPIO2_9; - gpio.h1r9_vaa_disable = &GPIO3_6; - - gpio.h1r9_trigger_enable = &GPIO5_5; +#if defined(PRALINE) || defined(UNIVERSAL) + switch (board_id) { + case BOARD_ID_PRALINE: + gpio.p2_ctrl0 = &GPIO7_3; + gpio.p2_ctrl1 = &GPIO7_4; + gpio.p1_ctrl0 = &GPIO0_14; + gpio.p1_ctrl1 = &GPIO5_16; + gpio.p1_ctrl2 = &GPIO3_5; + gpio.clkin_ctrl = &GPIO0_15; + gpio.aa_en = &GPIO1_7; + gpio.trigger_in = &GPIO6_26; + gpio.trigger_out = &GPIO5_6; + gpio.pps_out = &GPIO5_5; + break; + default: + break; } #endif - /* RAD1O */ +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + /* HackRF One r9 clock control */ + switch (board_id) { + case BOARD_ID_HACKRF1_R9: + gpio.h1r9_clkin_en = &GPIO5_15; + gpio.h1r9_clkout_en = &GPIO0_9; + gpio.h1r9_mcu_clk_en = &GPIO0_8; + break; + default: + break; + } + + switch (board_id) { + case BOARD_ID_HACKRF1_R9: + gpio.h1r9_1v8_enable = &GPIO2_9; + gpio.h1r9_vaa_disable = &GPIO3_6; + gpio.h1r9_trigger_enable = &GPIO5_5; + break; + default: + break; + } +#endif + + /* rad1o LCD */ #if defined(RAD1O) gpio.lcd_cs = &GPIO4_12; /* P9_0 */ gpio.lcd_bl_en = &GPIO0_8; /* P1_1 */ @@ -217,11 +322,9 @@ const platform_gpio_t* platform_gpio(void) #endif /* Portapack */ -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) gpio.io_stbx = &GPIO5_0; /* P2_0 */ gpio.addr = &GPIO5_1; /* P2_1 */ - gpio.lcd_te = &GPIO5_3; /* P2_3 */ - gpio.unused = &GPIO5_7; /* P2_8 */ gpio.lcd_rdx = &GPIO5_4; /* P2_4 */ gpio.lcd_wrx = &GPIO1_10; /* P2_9 */ gpio.dir = &GPIO1_13; /* P2_13 */ diff --git a/firmware/common/platform_gpio.h b/firmware/common/platform_gpio.h index 9c32925c..16767136 100644 --- a/firmware/common/platform_gpio.h +++ b/firmware/common/platform_gpio.h @@ -30,7 +30,7 @@ extern "C" { typedef struct { /* LEDs */ -#if defined(PRALINE) || defined(RAD1O) +#if defined(PRALINE) || defined(UNIVERSAL) || defined(RAD1O) gpio_t led[4]; #else gpio_t led[3]; @@ -38,7 +38,7 @@ typedef struct { /* Power Supply Control */ gpio_t gpio_1v8_enable; -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_1v2_enable; gpio_t gpio_3v3aux_enable_n; #endif @@ -48,7 +48,7 @@ typedef struct { gpio_t max283x_enable; gpio_t max283x_rx_enable; gpio_t max283x_tx_enable; -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t max2831_rxhp; gpio_t max2831_ld; #endif @@ -68,7 +68,7 @@ typedef struct { gpio_t w25q80bv_select; /* RF switch control */ -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) gpio_t hp; gpio_t lp; gpio_t tx_mix_bp; @@ -98,7 +98,7 @@ typedef struct { gpio_t tx_amp; gpio_t rx_lna; #endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t tx_en; gpio_t mix_en_n; gpio_t mix_en_n_r1_0; @@ -109,23 +109,23 @@ typedef struct { /* CPLD JTAG interface GPIO pins, FPGA config pins in Praline */ gpio_t cpld_tck; -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(JAWBREAKER) - gpio_t cpld_tdo; +#if !defined(PRALINE) || defined(UNIVERSAL) gpio_t cpld_tms; gpio_t cpld_tdi; + gpio_t cpld_tdo; #endif -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) gpio_t cpld_pp_tms; gpio_t cpld_pp_tdo; #endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t fpga_cfg_creset; gpio_t fpga_cfg_cdone; gpio_t fpga_cfg_spi_cs; #endif /* Other CPLD interface GPIO pins */ -#if !defined(PRALINE) +#if !defined(PRALINE) || defined(UNIVERSAL) gpio_t trigger_enable; #endif gpio_t q_invert; @@ -144,7 +144,7 @@ typedef struct { gpio_t synt_rfout_en; // RAD1O /* Praline */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t p2_ctrl0; gpio_t p2_ctrl1; gpio_t p1_ctrl0; @@ -157,8 +157,7 @@ typedef struct { gpio_t pps_out; #endif - /* HackRF One r9 */ -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) /* HackRF One r9 clock control */ gpio_t h1r9_clkin_en; gpio_t h1r9_clkout_en; @@ -177,11 +176,9 @@ typedef struct { #endif /* Portapack */ -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) gpio_t io_stbx; gpio_t addr; - __attribute__((unused)) gpio_t lcd_te; - __attribute__((unused)) gpio_t unused; gpio_t lcd_rdx; gpio_t lcd_wrx; gpio_t dir; From 7e03135d49ea93131b9fbbc240d2af29a55de761 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 21:18:01 +0100 Subject: [PATCH 005/161] Universalize: platform_scu.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/hackrf_core.c | 2 +- firmware/common/platform_scu.c | 597 ++++++++++++++++++++------------- firmware/common/platform_scu.h | 76 +++-- 3 files changed, 405 insertions(+), 270 deletions(-) diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 1535e1e8..25e3b870 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -947,7 +947,7 @@ void pin_setup(void) #endif jtag_gpio_cpld.gpio_tck = gpio->cpld_tck; -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(JAWBREAKER) +#if !defined(PRALINE) || defined(UNIVERSAL) jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; diff --git a/firmware/common/platform_scu.c b/firmware/common/platform_scu.c index a4365635..edd6ee0e 100644 --- a/firmware/common/platform_scu.c +++ b/firmware/common/platform_scu.c @@ -25,6 +25,8 @@ #include +#include "platform_detect.h" + // clang-format off const platform_scu_t* platform_scu(void) @@ -34,44 +36,68 @@ const platform_scu_t* platform_scu(void) return _platform_scu; } + board_id_t board_id = detected_platform(); static platform_scu_t scu; /* LED PinMux */ scu.PINMUX_LED1 = SCU_PINMUX_LED1; /* GPIO2[1] on P4_1 */ scu.PINMUX_LED2 = SCU_PINMUX_LED2; /* GPIO2[2] on P4_2 */ scu.PINMUX_LED3 = SCU_PINMUX_LED3; /* GPIO2[8] on P6_12 */ -#if defined(PRALINE) - scu.PINMUX_LED4 = (P8_6); /* GPIO4[6] on P8_6 */ -#elif defined(RAD1O) - scu.PINMUX_LED4 = (PB_6); /* GPIO5[26] on PB_6 */ + switch (board_id) { + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.PINMUX_LED4 = (PB_6); /* GPIO5[26] on PB_6 */ #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_LED4 = (P8_6); /* GPIO4[6] on P8_6 */ +#endif + break; + default: + break; + } /* Power Supply PinMux */ - scu.PINMUX_EN1V8 = (P6_10); /* GPIO3[6] on P6_10 */ - scu.PINMUX_EN1V2 = (P8_7); /* GPIO4[7] on P8_7 */ -#if defined(PRALINE) - scu.PINMUX_EN3V3_AUX_N = (P6_7); /* GPIO5[15] on P6_7 */ - scu.PINMUX_EN3V3_OC_N = (P6_11); /* GPIO3[7] on P6_11 */ + scu.PINMUX_EN1V8 = (P6_10); /* GPIO3[6] on P6_10 */ + scu.PINMUX_EN1V2 = (P8_7); /* GPIO4[7] on P8_7 */ + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_EN3V3_AUX_N = (P6_7); /* GPIO5[15] on P6_7 */ + scu.PINMUX_EN3V3_OC_N = (P6_11); /* GPIO3[7] on P6_11 */ #endif + break; + default: + break; + } /* GPIO Input PinMux */ scu.PINMUX_BOOT0 = (P1_1); /* GPIO0[8] on P1_1 */ scu.PINMUX_BOOT1 = (P1_2); /* GPIO0[9] on P1_2 */ -#if !defined(HACKRF_ONE) - scu.PINMUX_BOOT2 = (P2_8); /* GPIO5[7] on P2_8 */ - scu.PINMUX_BOOT3 = (P2_9); /* GPIO1[10] on P2_9 */ + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + break; + default: +#if !defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.PINMUX_BOOT2 = (P2_8); /* GPIO5[7] on P2_8 */ + scu.PINMUX_BOOT3 = (P2_9); /* GPIO1[10] on P2_9 */ #endif - scu.PINMUX_PP_LCD_TE = (P2_3); /* GPIO5[3] on P2_3 */ - scu.PINMUX_PP_LCD_RDX = (P2_4); /* GPIO5[4] on P2_4 */ - scu.PINMUX_PP_UNUSED = (P2_8); /* GPIO5[7] on P2_8 */ - scu.PINMUX_PP_LCD_WRX = (P2_9); /* GPIO1[10] on P2_9 */ - scu.PINMUX_PP_DIR = (P2_13); /* GPIO1[13] on P2_13 */ + break; + } /* USB peripheral */ + switch (board_id) { + case BOARD_ID_JAWBREAKER: #if defined(JAWBREAKER) - scu.PINMUX_USB_LED0 = (P6_8); - scu.PINMUX_USB_LED1 = (P6_7); + scu.PINMUX_USB_LED0 = (P6_8); + scu.PINMUX_USB_LED1 = (P6_7); #endif + break; + default: + break; + } /* SSP1 Peripheral PinMux */ scu.SSP1_CIPO = (P1_3); /* P1_3 */ @@ -80,172 +106,242 @@ const platform_scu_t* platform_scu(void) scu.SSP1_CS = (P1_20); /* P1_20 */ /* CPLD JTAG interface */ - scu.PINMUX_CPLD_TCK = (P6_1); /* GPIO3[ 0] */ -#if defined(PRALINE) - scu.PINMUX_FPGA_CRESET = (P5_2); /* GPIO2[11] on P5_2 */ - scu.PINMUX_FPGA_CDONE = (P4_10); /* GPIO5[14] */ - scu.PINMUX_FPGA_SPI_CS = (P5_1); /* GPIO2[10] */ -#else - scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ + scu.PINMUX_CPLD_TCK = (P6_1); /* GPIO3[ 0] */ + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_FPGA_CRESET = (P5_2); /* GPIO2[11] on P5_2 */ + scu.PINMUX_FPGA_CDONE = (P4_10); /* GPIO5[14] */ + scu.PINMUX_FPGA_SPI_CS = (P5_1); /* GPIO2[10] */ #endif -#if defined(RAD1O) || defined(HACKRF_ONE) - scu.PINMUX_CPLD_TMS = (P6_5); /* GPIO3[ 4] */ - scu.PINMUX_CPLD_TDI = (P6_2); /* GPIO3[ 1] */ -#endif -#if defined(JAWBREAKER) - scu.PINMUX_CPLD_TMS = (P6_2); /* GPIO3[ 1] */ - scu.PINMUX_CPLD_TDI = (P6_5); /* GPIO3[ 4] */ + break; + default: + break; + } +#if !defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ + switch (board_id) { + case BOARD_ID_RAD1O: + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + scu.PINMUX_CPLD_TMS = (P6_5); /* GPIO3[ 4] */ + scu.PINMUX_CPLD_TDI = (P6_2); /* GPIO3[ 1] */ + break; + default: + scu.PINMUX_CPLD_TMS = (P6_2); /* GPIO3[ 1] */ + scu.PINMUX_CPLD_TDI = (P6_5); /* GPIO3[ 4] */ + break; + } #endif /* CPLD SGPIO interface */ -#if defined(PRALINE) - scu.PINMUX_SGPIO0 = (P0_0); - scu.PINMUX_SGPIO1 = (P0_1); - scu.PINMUX_SGPIO2 = (P1_15); - scu.PINMUX_SGPIO3 = (P1_16); - scu.PINMUX_SGPIO4 = (P9_4); - scu.PINMUX_SGPIO5 = (P6_6); - scu.PINMUX_SGPIO6 = (P2_2); - scu.PINMUX_SGPIO7 = (P1_0); - scu.PINMUX_SGPIO8 = (P8_0); - scu.PINMUX_SGPIO9 = (P9_3); - scu.PINMUX_SGPIO10 = (P8_2); - scu.PINMUX_SGPIO11 = (P1_17); - scu.PINMUX_SGPIO12 = (P1_18); - scu.PINMUX_SGPIO14 = (P1_18); - scu.PINMUX_SGPIO15 = (P1_18); + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_SGPIO0 = (P0_0); + scu.PINMUX_SGPIO1 = (P0_1); + scu.PINMUX_SGPIO2 = (P1_15); + scu.PINMUX_SGPIO3 = (P1_16); + scu.PINMUX_SGPIO4 = (P9_4); + scu.PINMUX_SGPIO5 = (P6_6); + scu.PINMUX_SGPIO6 = (P2_2); + scu.PINMUX_SGPIO7 = (P1_0); + scu.PINMUX_SGPIO8 = (P8_0); + scu.PINMUX_SGPIO9 = (P9_3); + scu.PINMUX_SGPIO10 = (P8_2); + scu.PINMUX_SGPIO11 = (P1_17); + scu.PINMUX_SGPIO12 = (P1_18); + scu.PINMUX_SGPIO14 = (P1_18); + scu.PINMUX_SGPIO15 = (P1_18); - scu.PINMUX_SGPIO0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); - scu.PINMUX_SGPIO1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); - scu.PINMUX_SGPIO2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO3_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO4_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO5_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO6_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO7_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO8_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.PINMUX_SGPIO9_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO10_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.PINMUX_SGPIO11_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#else - scu.PINMUX_SGPIO0 = (P0_0); - scu.PINMUX_SGPIO1 = (P0_1); - scu.PINMUX_SGPIO2 = (P1_15); - scu.PINMUX_SGPIO3 = (P1_16); - scu.PINMUX_SGPIO4 = (P6_3); - scu.PINMUX_SGPIO5 = (P6_6); - scu.PINMUX_SGPIO6 = (P2_2); - scu.PINMUX_SGPIO7 = (P1_0); - scu.PINMUX_SGPIO8 = (P9_6); - scu.PINMUX_SGPIO9 = (P4_3); - scu.PINMUX_SGPIO10 = (P1_14); - scu.PINMUX_SGPIO11 = (P1_17); - scu.PINMUX_SGPIO12 = (P1_18); - scu.PINMUX_SGPIO14 = (P4_9); - scu.PINMUX_SGPIO15 = (P4_10); - - scu.PINMUX_SGPIO0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); - scu.PINMUX_SGPIO1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); - scu.PINMUX_SGPIO2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO3_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO4_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO5_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO6_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO7_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO8_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO9_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION7); - scu.PINMUX_SGPIO10_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO11_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PINMUX_SGPIO0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); + scu.PINMUX_SGPIO1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); + scu.PINMUX_SGPIO2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO3_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO4_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO5_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO6_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO7_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO8_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PINMUX_SGPIO9_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO10_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PINMUX_SGPIO11_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); #endif + break; + default: +#if !defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_SGPIO0 = (P0_0); + scu.PINMUX_SGPIO1 = (P0_1); + scu.PINMUX_SGPIO2 = (P1_15); + scu.PINMUX_SGPIO3 = (P1_16); + scu.PINMUX_SGPIO4 = (P6_3); + scu.PINMUX_SGPIO5 = (P6_6); + scu.PINMUX_SGPIO6 = (P2_2); + scu.PINMUX_SGPIO7 = (P1_0); + scu.PINMUX_SGPIO8 = (P9_6); + scu.PINMUX_SGPIO9 = (P4_3); + scu.PINMUX_SGPIO10 = (P1_14); + scu.PINMUX_SGPIO11 = (P1_17); + scu.PINMUX_SGPIO12 = (P1_18); + scu.PINMUX_SGPIO14 = (P4_9); + scu.PINMUX_SGPIO15 = (P4_10); + + scu.PINMUX_SGPIO0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); + scu.PINMUX_SGPIO1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); + scu.PINMUX_SGPIO2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO3_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO4_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO5_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO6_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO7_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO8_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO9_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION7); + scu.PINMUX_SGPIO10_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO11_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); +#endif + break; + } scu.TRIGGER_EN = (P4_8); /* GPIO5[12] on P4_8 */ /* MAX283x GPIO (XCVR_CTL) PinMux */ -#if defined(PRALINE) - scu.XCVR_ENABLE = PE_1; /* GPIO7[1] on PE_1 */ - scu.XCVR_RXENABLE = PE_2; /* GPIO7[2] on PE_2 */ - scu.XCVR_CS = PD_14; /* GPIO6[28] on PD_14 */ - scu.XCVR_RXHP = PD_15; /* GPIO6[29] on PD_15 */ - scu.XCVR_LD = P9_6; /* GPIO4[11] on P9_6 */ - - scu.XCVR_ENABLE_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.XCVR_RXHP_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.XCVR_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | - SCU_CONF_EPD_EN_PULLDOWN | SCU_CONF_EPUN_DIS_PULLUP); -#elif defined(JAWBREAKER) || defined(HACKRF_ONE) - scu.XCVR_ENABLE = P4_6; /* GPIO2[6] on P4_6 */ - scu.XCVR_RXENABLE = P4_5; /* GPIO2[5] on P4_5 */ - scu.XCVR_TXENABLE = P4_4; /* GPIO2[4] on P4_4 */ - scu.XCVR_CS = P1_20; /* GPIO0[15] on P1_20 */ - - scu.XCVR_ENABLE_PINCFG = (SCU_GPIO_FAST); - scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST); - scu.XCVR_TXENABLE_PINCFG = (SCU_GPIO_FAST); - scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST); -#elif defined(RAD1O) - scu.XCVR_RXHP = P8_1; /* GPIO[] on P8_1 */ - scu.XCVR_B6 = P8_2; /* GPIO[] on P8_2 */ - scu.XCVR_B7 = P9_3; /* GPIO[] on P9_3 */ + switch (board_id) { + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.XCVR_RXHP = P8_1; /* GPIO[] on P8_1 */ + scu.XCVR_B6 = P8_2; /* GPIO[] on P8_2 */ + scu.XCVR_B7 = P9_3; /* GPIO[] on P9_3 */ #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.XCVR_ENABLE = PE_1; /* GPIO7[1] on PE_1 */ + scu.XCVR_RXENABLE = PE_2; /* GPIO7[2] on PE_2 */ + scu.XCVR_CS = PD_14; /* GPIO6[28] on PD_14 */ + scu.XCVR_RXHP = PD_15; /* GPIO6[29] on PD_15 */ + scu.XCVR_LD = P9_6; /* GPIO4[11] on P9_6 */ + + scu.XCVR_ENABLE_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.XCVR_RXHP_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.XCVR_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | + SCU_CONF_EPD_EN_PULLDOWN | SCU_CONF_EPUN_DIS_PULLUP); +#endif + break; + case BOARD_ID_JAWBREAKER: + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.XCVR_ENABLE = P4_6; /* GPIO2[6] on P4_6 */ + scu.XCVR_RXENABLE = P4_5; /* GPIO2[5] on P4_5 */ + scu.XCVR_TXENABLE = P4_4; /* GPIO2[4] on P4_4 */ + scu.XCVR_CS = P1_20; /* GPIO0[15] on P1_20 */ + + scu.XCVR_ENABLE_PINCFG = (SCU_GPIO_FAST); + scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST); + scu.XCVR_TXENABLE_PINCFG = (SCU_GPIO_FAST); + scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST); +#endif + break; + default: + break; + } /* MAX5864 SPI chip select (AD_CS) GPIO PinMux */ -#if defined(PRALINE) - scu.AD_CS = (PD_16); /* GPIO6[30] on PD_16 */ - scu.AD_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#else - scu.AD_CS = (P5_7); /* GPIO2[7] on P5_7 */ - scu.AD_CS_PINCFG = (SCU_GPIO_FAST); + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.AD_CS = (PD_16); /* GPIO6[30] on PD_16 */ + scu.AD_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); #endif + break; + default: + scu.AD_CS = (P5_7); /* GPIO2[7] on P5_7 */ + scu.AD_CS_PINCFG = (SCU_GPIO_FAST); + break; + } /* RFFC5071 GPIO serial interface PinMux */ -#if defined(PRALINE) - scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ - scu.MIXER_SCLK = (P9_5); /* GPIO5[18] on P9_5 */ - scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ - scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ - scu.MIXER_LD = (PD_11); /* GPIO6[25] on PD_11 */ + switch (board_id) { + case BOARD_ID_JAWBREAKER: + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ + scu.MIXER_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ + scu.MIXER_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ + scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ - scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#elif defined(JAWBREAKER) || defined(HACKRF_ONE) - scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ - scu.MIXER_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ - scu.MIXER_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ - scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ - - scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#elif defined(RAD1O) - scu.VCO_CE = (P5_4); /* GPIO2[13] on P5_4 */ - scu.VCO_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ - scu.VCO_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ - scu.VCO_LE = (P5_5); /* GPIO2[14] on P5_5 */ - scu.VCO_MUX = (PB_5); /* GPIO5[25] on PB_5 */ - scu.MIXER_EN = (P6_8); /* GPIO5[16] on P6_8 */ - scu.SYNT_RFOUT_EN = (P6_9); /* GPIO3[5] on P6_9 */ + scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ + scu.MIXER_SCLK = (P9_5); /* GPIO5[18] on P9_5 */ + scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ + scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ + scu.MIXER_LD = (PD_11); /* GPIO6[25] on PD_11 */ + + scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.VCO_CE = (P5_4); /* GPIO2[13] on P5_4 */ + scu.VCO_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ + scu.VCO_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ + scu.VCO_LE = (P5_5); /* GPIO2[14] on P5_5 */ + scu.VCO_MUX = (PB_5); /* GPIO5[25] on PB_5 */ + scu.MIXER_EN = (P6_8); /* GPIO5[16] on P6_8 */ + scu.SYNT_RFOUT_EN = (P6_9); /* GPIO3[5] on P6_9 */ +#endif + break; + default: + break; + } /* RF LDO control */ + switch (board_id) { + case BOARD_ID_JAWBREAKER: #if defined(JAWBREAKER) - scu.RF_LDO_ENABLE = (P5_0); /* GPIO2[9] on P5_0 */ + scu.RF_LDO_ENABLE = (P5_0); /* GPIO2[9] on P5_0 */ #endif + break; + default: + break; + } /* RF supply (VAA) control */ -#if defined(PRALINE) - scu.NO_VAA_ENABLE = P8_1; /* GPIO4[1] on P8_1 */ -#elif defined(HACKRF_ONE) - scu.NO_VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ -#elif defined(RAD1O) - scu.VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.NO_VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.NO_VAA_ENABLE = P8_1; /* GPIO4[1] on P8_1 */ +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ +#endif + break; + default: + break; + } /* SPI flash */ scu.SSP0_CIPO = (P3_6); @@ -256,79 +352,105 @@ const platform_scu_t* platform_scu(void) scu.FLASH_WP = (P3_5); /* GPIO1[15] on P3_5 */ /* RF switch control */ -#if defined(PRALINE) - scu.TX_EN = P6_5; /* GPIO3[4] on P6_5 */ - scu.MIX_EN_N = P6_3; /* GPIO3[2] on P6_3 */ - scu.MIX_EN_N_R1_0 = P2_6; /* GPIO5[6] on P2_6 */ - scu.LPF_EN = PA_1; /* GPIO4[8] on PA_1 */ - scu.RF_AMP_EN = PA_2; /* GPIO4[9] on PA_2 */ - scu.ANT_BIAS_EN_N = P2_12; /* GPIO1[12] on P2_12 */ - scu.ANT_BIAS_OC_N = P2_11; /* GPIO1[11] on P2_11 */ -#elif defined(HACKRF_ONE) - scu.HP = P4_0; /* GPIO2[0] on P4_0 */ - scu.LP = P5_1; /* GPIO2[10] on P5_1 */ - scu.TX_MIX_BP = P5_2; /* GPIO2[11] on P5_2 */ - scu.NO_MIX_BYPASS = P1_7; /* GPIO1[0] on P1_7 */ - scu.RX_MIX_BP = P5_3; /* GPIO2[12] on P5_3 */ - scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ - scu.TX = P6_7; /* GPIO5[15] on P6_7 */ - scu.MIX_BYPASS = P6_8; /* GPIO5[16] on P6_8 */ - scu.RX = P2_5; /* GPIO5[5] on P2_5 */ - scu.NO_TX_AMP_PWR = P6_9; /* GPIO3[5] on P6_9 */ - scu.AMP_BYPASS = P2_10; /* GPIO0[14] on P2_10 */ - scu.RX_AMP = P2_11; /* GPIO1[11] on P2_11 */ - scu.NO_RX_AMP_PWR = P2_12; /* GPIO1[12] on P2_12 */ -#elif defined(RAD1O) - scu.BY_AMP = P1_7; /* GPIO1[0] on P1_7 */ - scu.BY_AMP_N = P2_5; /* GPIO5[5] on P2_5 */ - scu.TX_RX = P2_10; /* GPIO0[14] on P2_10 */ - scu.TX_RX_N = P2_11; /* GPIO1[11] on P2_11 */ - scu.BY_MIX = P2_12; /* GPIO1[12] on P2_12 */ - scu.BY_MIX_N = P5_1; /* GPIO2[10] on P5_1 */ - scu.LOW_HIGH_FILT = P5_2; /* GPIO2[11] on P5_2 */ - scu.LOW_HIGH_FILT_N = P5_3; /* GPIO2[12] on P5_3 */ - scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ - scu.RX_LNA = P6_7; /* GPIO5[15] on P6_7 */ + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.HP = P4_0; /* GPIO2[0] on P4_0 */ + scu.LP = P5_1; /* GPIO2[10] on P5_1 */ + scu.TX_MIX_BP = P5_2; /* GPIO2[11] on P5_2 */ + scu.NO_MIX_BYPASS = P1_7; /* GPIO1[0] on P1_7 */ + scu.RX_MIX_BP = P5_3; /* GPIO2[12] on P5_3 */ + scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ + scu.TX = P6_7; /* GPIO5[15] on P6_7 */ + scu.MIX_BYPASS = P6_8; /* GPIO5[16] on P6_8 */ + scu.RX = P2_5; /* GPIO5[5] on P2_5 */ + scu.NO_TX_AMP_PWR = P6_9; /* GPIO3[5] on P6_9 */ + scu.AMP_BYPASS = P2_10; /* GPIO0[14] on P2_10 */ + scu.RX_AMP = P2_11; /* GPIO1[11] on P2_11 */ + scu.NO_RX_AMP_PWR = P2_12; /* GPIO1[12] on P2_12 */ #endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.BY_AMP = P1_7; /* GPIO1[0] on P1_7 */ + scu.BY_AMP_N = P2_5; /* GPIO5[5] on P2_5 */ + scu.TX_RX = P2_10; /* GPIO0[14] on P2_10 */ + scu.TX_RX_N = P2_11; /* GPIO1[11] on P2_11 */ + scu.BY_MIX = P2_12; /* GPIO1[12] on P2_12 */ + scu.BY_MIX_N = P5_1; /* GPIO2[10] on P5_1 */ + scu.LOW_HIGH_FILT = P5_2; /* GPIO2[11] on P5_2 */ + scu.LOW_HIGH_FILT_N = P5_3; /* GPIO2[12] on P5_3 */ + scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ + scu.RX_LNA = P6_7; /* GPIO5[15] on P6_7 */ +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.TX_EN = P6_5; /* GPIO3[4] on P6_5 */ + scu.MIX_EN_N = P6_3; /* GPIO3[2] on P6_3 */ + scu.MIX_EN_N_R1_0 = P2_6; /* GPIO5[6] on P2_6 */ + scu.LPF_EN = PA_1; /* GPIO4[8] on PA_1 */ + scu.RF_AMP_EN = PA_2; /* GPIO4[9] on PA_2 */ + scu.ANT_BIAS_EN_N = P2_12; /* GPIO1[12] on P2_12 */ + scu.ANT_BIAS_OC_N = P2_11; /* GPIO1[11] on P2_11 */ +#endif + break; + default: + break; + } /* Praline */ -#if defined(PRALINE) - scu.P2_CTRL0 = (PE_3); /* GPIO7[3] on PE_3 */ - scu.P2_CTRL1 = (PE_4); /* GPIO7[4] on PE_4 */ - scu.P1_CTRL0 = (P2_10); /* GPIO0[14] on P2_10 */ - scu.P1_CTRL1 = (P6_8); /* GPIO5[16] on P6_8 */ - scu.P1_CTRL2 = (P6_9); /* GPIO3[5] on P6_9 */ - scu.CLKIN_CTRL = (P1_20); /* GPIO0[15] on P1_20 */ - scu.AA_EN = (P1_14); /* GPIO1[7] on P1_14 */ - scu.TRIGGER_IN = (PD_12); /* GPIO6[26] on PD_12 */ - scu.TRIGGER_OUT = (P2_6); /* GPIO5[6] on P2_6 */ - scu.PPS_OUT = (P2_5); /* GPIO5[5] on P2_5 */ +#if defined(PRALINE) || defined(UNIVERSAL) + switch (board_id) { + case BOARD_ID_PRALINE: + scu.P2_CTRL0 = (PE_3); /* GPIO7[3] on PE_3 */ + scu.P2_CTRL1 = (PE_4); /* GPIO7[4] on PE_4 */ + scu.P1_CTRL0 = (P2_10); /* GPIO0[14] on P2_10 */ + scu.P1_CTRL1 = (P6_8); /* GPIO5[16] on P6_8 */ + scu.P1_CTRL2 = (P6_9); /* GPIO3[5] on P6_9 */ + scu.CLKIN_CTRL = (P1_20); /* GPIO0[15] on P1_20 */ + scu.AA_EN = (P1_14); /* GPIO1[7] on P1_14 */ + scu.TRIGGER_IN = (PD_12); /* GPIO6[26] on PD_12 */ + scu.TRIGGER_OUT = (P2_6); /* GPIO5[6] on P2_6 */ + scu.PPS_OUT = (P2_5); /* GPIO5[5] on P2_5 */ - scu.P2_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.P2_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.P1_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.P1_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.P1_CTRL2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.CLKIN_CTRL_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.AA_EN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.P2_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.P2_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.P1_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.P1_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.P1_CTRL2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.CLKIN_CTRL_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.AA_EN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + break; + default: + break; + } #endif /* HackRF One r9 */ -#if defined(HACKRF_ONE) - scu.H1R9_CLKIN_EN = P6_7; /* GPIO5[15] on P6_7 */ - scu.H1R9_CLKOUT_EN = P1_2; /* GPIO0[9] on P1_2 = has boot pull-down; */ - scu.H1R9_MCU_CLK_EN = P1_1; /* GPIO0[8] on P1_1 = has boot pull-up; */ - scu.H1R9_RX = P2_7; /* GPIO0[7] on P4_4 = has boot pull-up; */ - scu.H1R9_NO_ANT_PWR = P4_4; /* GPIO2[4] on P4_4 */ - scu.H1R9_EN1V8 = P5_0; /* GPIO2[9] on P5_0 */ - scu.H1R9_NO_VAA_EN = P6_10; /* GPIO3[6] on P6_10 */ - scu.H1R9_TRIGGER_EN = P2_5; /* GPIO5[5] on P2_5 */ +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + switch (board_id) { + case BOARD_ID_HACKRF1_R9: + scu.H1R9_CLKIN_EN = P6_7; /* GPIO5[15] on P6_7 */ + scu.H1R9_CLKOUT_EN = P1_2; /* GPIO0[9] on P1_2 = has boot pull-down; */ + scu.H1R9_MCU_CLK_EN = P1_1; /* GPIO0[8] on P1_1 = has boot pull-up; */ + scu.H1R9_RX = P2_7; /* GPIO0[7] on P4_4 = has boot pull-up; */ + scu.H1R9_NO_ANT_PWR = P4_4; /* GPIO2[4] on P4_4 */ + scu.H1R9_EN1V8 = P5_0; /* GPIO2[9] on P5_0 */ + scu.H1R9_NO_VAA_EN = P6_10; /* GPIO3[6] on P6_10 */ + scu.H1R9_TRIGGER_EN = P2_5; /* GPIO5[5] on P2_5 */ + break; + default: + break; + } #endif - /* Miscellaneous */ + /* Expansion headers */ +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) scu.PINMUX_PP_D0 = (P7_0); /* GPIO3[8] */ scu.PINMUX_PP_D1 = (P7_1); /* GPIO3[9] */ scu.PINMUX_PP_D2 = (P7_2); /* GPIO3[10] */ @@ -338,16 +460,11 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_PP_D6 = (P7_6); /* GPIO3[14] */ scu.PINMUX_PP_D7 = (P7_7); /* GPIO3[15] */ - /* TODO add other Pins */ - - scu.PINMUX_GPIO3_8 = (P7_0); /* GPIO3[8] */ - scu.PINMUX_GPIO3_9 = (P7_1); /* GPIO3[9] */ - scu.PINMUX_GPIO3_10 = (P7_2); /* GPIO3[10] */ - scu.PINMUX_GPIO3_11 = (P7_3); /* GPIO3[11] */ - scu.PINMUX_GPIO3_12 = (P7_4); /* GPIO3[12] */ - scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ - scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ - scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ + scu.PINMUX_PP_LCD_TE = (P2_3); /* GPIO5[3] on P2_3 */ + scu.PINMUX_PP_LCD_RDX = (P2_4); /* GPIO5[4] on P2_4 */ + scu.PINMUX_PP_UNUSED = (P2_8); /* GPIO5[7] on P2_8 */ + scu.PINMUX_PP_LCD_WRX = (P2_9); /* GPIO1[10] on P2_9 */ + scu.PINMUX_PP_DIR = (P2_13); /* GPIO1[13] on P2_13 */ scu.PINMUX_PP_TDO = (P1_5); /* GPIO1[8] */ scu.PINMUX_SD_POW = (P1_5); /* GPIO1[8] */ @@ -365,6 +482,16 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_U0_TXD = (P2_0); /* GPIO5[0] */ scu.PINMUX_U0_RXD = (P2_1); /* GPIO5[1] */ + scu.PINMUX_GPIO3_8 = (P7_0); /* GPIO3[8] */ + scu.PINMUX_GPIO3_9 = (P7_1); /* GPIO3[9] */ + scu.PINMUX_GPIO3_10 = (P7_2); /* GPIO3[10] */ + scu.PINMUX_GPIO3_11 = (P7_3); /* GPIO3[11] */ + scu.PINMUX_GPIO3_12 = (P7_4); /* GPIO3[12] */ + scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ + scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ + scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ +#endif + scu.PINMUX_ISP = (P2_7); /* GPIO0[7] */ scu.PINMUX_GP_CLKIN = (P4_7); diff --git a/firmware/common/platform_scu.h b/firmware/common/platform_scu.h index 2b4b42f0..2c5365e3 100644 --- a/firmware/common/platform_scu.h +++ b/firmware/common/platform_scu.h @@ -34,18 +34,17 @@ extern "C" { */ typedef struct { - /* LED PinMux */ + /* GPIO Output PinMux */ scu_grp_pin_t PINMUX_LED1; scu_grp_pin_t PINMUX_LED2; scu_grp_pin_t PINMUX_LED3; -#if defined(RAD1O) || defined(PRALINE) +#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t PINMUX_LED4; #endif - /* Power Supply PinMux */ scu_grp_pin_t PINMUX_EN1V8; scu_grp_pin_t PINMUX_EN1V2; -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t PINMUX_EN3V3_AUX_N; scu_grp_pin_t PINMUX_EN3V3_OC_N; #endif @@ -53,7 +52,7 @@ typedef struct { /* GPIO Input PinMux */ scu_grp_pin_t PINMUX_BOOT0; scu_grp_pin_t PINMUX_BOOT1; -#if !defined(HACKRF_ONE) +#if !defined(HACKRF_ONE) || defined(UNIVERSAL) scu_grp_pin_t PINMUX_BOOT2; scu_grp_pin_t PINMUX_BOOT3; #endif @@ -76,16 +75,15 @@ typedef struct { scu_grp_pin_t SSP1_CS; /* CPLD JTAG interface */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t PINMUX_FPGA_CRESET; scu_grp_pin_t PINMUX_FPGA_CDONE; scu_grp_pin_t PINMUX_FPGA_SPI_CS; -#else +#endif scu_grp_pin_t PINMUX_CPLD_TDO; + scu_grp_pin_t PINMUX_CPLD_TCK; scu_grp_pin_t PINMUX_CPLD_TMS; scu_grp_pin_t PINMUX_CPLD_TDI; -#endif - scu_grp_pin_t PINMUX_CPLD_TCK; /* CPLD SGPIO interface */ scu_grp_pin_t PINMUX_SGPIO0; @@ -121,36 +119,45 @@ typedef struct { scu_grp_pin_t TRIGGER_EN; /* MAX283x GPIO (XCVR_CTL) PinMux */ - scu_grp_pin_t XCVR_RXHP; // RAD1O, PRALINE - scu_grp_pin_t XCVR_B6; // RAD1O - scu_grp_pin_t XCVR_B7; // RAD1O - scu_grp_pin_t XCVR_ENABLE; // PRALINE, HACKRF_ONE - scu_grp_pin_t XCVR_RXENABLE; // PRALINE, HACKRF_ONE - scu_grp_pin_t XCVR_CS; // PRALINE, HACKRF_ONE - scu_grp_pin_t XCVR_LD; // PRALINE - uint32_t XCVR_ENABLE_PINCFG; // PRALINE, HACKRF_ONE - uint32_t XCVR_RXENABLE_PINCFG; // PRALINE, HACKRF_ONE - uint32_t XCVR_CS_PINCFG; // PRALINE, HACKRF_ONE - uint32_t XCVR_RXHP_PINCFG; // PRALINE - uint32_t XCVR_LD_PINCFG; // PRALINE - scu_grp_pin_t XCVR_TXENABLE; // HACKRF_ONE - uint32_t XCVR_TXENABLE_PINCFG; // HACKRF_ONE + scu_grp_pin_t XCVR_ENABLE; + scu_grp_pin_t XCVR_RXENABLE; + scu_grp_pin_t XCVR_TXENABLE; + scu_grp_pin_t XCVR_CS; + uint32_t XCVR_ENABLE_PINCFG; + uint32_t XCVR_RXENABLE_PINCFG; + uint32_t XCVR_TXENABLE_PINCFG; + uint32_t XCVR_CS_PINCFG; +#if defined(PRALINE) || defined(UNIVERSAL) + scu_grp_pin_t XCVR_LD; + uint32_t XCVR_LD_PINCFG; + uint32_t XCVR_RXHP_PINCFG; +#endif +#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) + scu_grp_pin_t XCVR_RXHP; +#endif +#if defined(RAD1O) + scu_grp_pin_t XCVR_B6; + scu_grp_pin_t XCVR_B7; +#endif /* MAX5864 SPI chip select (AD_CS) GPIO PinMux */ scu_grp_pin_t AD_CS; scu_grp_pin_t AD_CS_PINCFG; /* RFFC5071 GPIO serial interface PinMux */ +#if !defined(RAD1O) scu_grp_pin_t MIXER_ENX; scu_grp_pin_t MIXER_SCLK; scu_grp_pin_t MIXER_SDATA; scu_grp_pin_t MIXER_RESETX; uint32_t MIXER_SCLK_PINCFG; uint32_t MIXER_SDATA_PINCFG; -#if defined(PRALINE) +#endif +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t MIXER_LD; uint32_t MIXER_LD_PINCFG; -#elif defined(RAD1O) +#endif +#if defined(RAD1O) scu_grp_pin_t VCO_CE; scu_grp_pin_t VCO_SCLK; scu_grp_pin_t VCO_SDATA; @@ -166,9 +173,10 @@ typedef struct { #endif /* RF supply (VAA) control */ -#if defined(PRALINE) || defined(HACKRF_ONE) +#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) scu_grp_pin_t NO_VAA_ENABLE; -#elif defined(RAD1O) +#endif +#if defined(RAD1O) scu_grp_pin_t VAA_ENABLE; #endif @@ -181,7 +189,7 @@ typedef struct { scu_grp_pin_t FLASH_WP; /* RF switch control */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t TX_EN; scu_grp_pin_t MIX_EN_N; scu_grp_pin_t MIX_EN_N_R1_0; @@ -189,7 +197,8 @@ typedef struct { scu_grp_pin_t RF_AMP_EN; scu_grp_pin_t ANT_BIAS_EN_N; scu_grp_pin_t ANT_BIAS_OC_N; -#elif defined(HACKRF_ONE) +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) scu_grp_pin_t HP; scu_grp_pin_t LP; scu_grp_pin_t TX_MIX_BP; @@ -203,7 +212,8 @@ typedef struct { scu_grp_pin_t AMP_BYPASS; scu_grp_pin_t RX_AMP; scu_grp_pin_t NO_RX_AMP_PWR; -#elif defined(RAD1O) +#endif +#if defined(RAD1O) scu_grp_pin_t BY_AMP; scu_grp_pin_t BY_AMP_N; scu_grp_pin_t TX_RX; @@ -217,7 +227,7 @@ typedef struct { #endif /* Praline */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t P2_CTRL0; scu_grp_pin_t P2_CTRL1; scu_grp_pin_t P1_CTRL0; @@ -242,7 +252,7 @@ typedef struct { #endif /* HackRF One r9 */ -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) scu_grp_pin_t H1R9_CLKIN_EN; scu_grp_pin_t H1R9_CLKOUT_EN; scu_grp_pin_t H1R9_MCU_CLK_EN; @@ -262,9 +272,7 @@ typedef struct { scu_grp_pin_t PINMUX_PP_D5; scu_grp_pin_t PINMUX_PP_D6; scu_grp_pin_t PINMUX_PP_D7; - /* TODO add other Pins */ - scu_grp_pin_t PINMUX_GPIO3_8; scu_grp_pin_t PINMUX_GPIO3_9; scu_grp_pin_t PINMUX_GPIO3_10; From bf7d47abf18c857642a0372b9ef339db0272a889 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 21:37:14 +0100 Subject: [PATCH 006/161] Universalize: cpld_jtag.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/cpld_jtag.c | 85 ++++++++++++++++++++++++++----------- firmware/common/cpld_jtag.h | 4 +- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 4ef9ce5d..21991db0 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -19,16 +19,17 @@ * Boston, MA 02110-1301, USA. */ -#if !defined(PRALINE) +#if !defined(PRALINE) || defined(UNIVERSAL) #include #endif #include "cpld_jtag.h" -#if !defined(PRALINE) +#include "platform_detect.h" +#if !defined(PRALINE) || defined(UNIVERSAL) #include "xapp058/micro.h" #endif -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) static refill_buffer_cb refill_buffer; static uint32_t xsvf_buffer_len, xsvf_pos; static unsigned char* xsvf_buffer; @@ -38,52 +39,86 @@ void cpld_jtag_take(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; + board_id_t board_id = detected_platform(); + /* Set initial GPIO state to the voltages of the internal or external pull-ups/downs, * to avoid any glitches. */ -#if (defined HACKRF_ONE || defined PRALINE) - gpio_set(gpio->gpio_pp_tms); + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + gpio_set(gpio->gpio_pp_tms); #endif + break; + default: + break; + } gpio_clear(gpio->gpio_tck); -#ifndef PRALINE - gpio_set(gpio->gpio_tms); - gpio_set(gpio->gpio_tdi); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio_set(gpio->gpio_tms); + gpio_set(gpio->gpio_tdi); #endif + } -#if (defined HACKRF_ONE || defined PRALINE) - /* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */ - gpio_input(gpio->gpio_pp_tms); - gpio_input(gpio->gpio_pp_tdo); + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */ + gpio_input(gpio->gpio_pp_tms); + gpio_input(gpio->gpio_pp_tdo); #endif + break; + default: + break; + } gpio_output(gpio->gpio_tck); -#ifndef PRALINE - gpio_output(gpio->gpio_tms); - gpio_output(gpio->gpio_tdi); - gpio_input(gpio->gpio_tdo); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio_output(gpio->gpio_tms); + gpio_output(gpio->gpio_tdi); + gpio_input(gpio->gpio_tdo); #endif + } } void cpld_jtag_release(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; + board_id_t board_id = detected_platform(); + /* Make all pins inputs when JTAG interface not active. * Let the pull-ups/downs do the work. */ -#if (defined HACKRF_ONE || defined PRALINE) - /* Do not drive PortaPack-specific pins, initially, just to be cautious. */ - gpio_input(gpio->gpio_pp_tms); - gpio_input(gpio->gpio_pp_tdo); + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Do not drive PortaPack-specific pins, initially, just to be cautious. */ + gpio_input(gpio->gpio_pp_tms); + gpio_input(gpio->gpio_pp_tdo); #endif + break; + default: + break; + } gpio_input(gpio->gpio_tck); -#ifndef PRALINE - gpio_input(gpio->gpio_tms); - gpio_input(gpio->gpio_tdi); - gpio_input(gpio->gpio_tdo); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio_input(gpio->gpio_tms); + gpio_input(gpio->gpio_tdi); + gpio_input(gpio->gpio_tdo); #endif + } } -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) /* return 0 if success else return error code see xsvfExecute() */ int cpld_jtag_program( jtag_t* const jtag, diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index 60036759..939e5d94 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -27,12 +27,12 @@ typedef struct { gpio_t gpio_tck; -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_tms; gpio_t gpio_tdi; gpio_t gpio_tdo; #endif -#if (defined HACKRF_ONE || defined PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_pp_tms; gpio_t gpio_pp_tdo; #endif From 25918e154a4da337cbd44ce3326ae9c059d33559 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 21:39:02 +0100 Subject: [PATCH 007/161] Universalize: max283x.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/max283x.c | 270 ++++++++++++++++++++++---------------- firmware/common/max283x.h | 21 ++- firmware/common/rf_path.c | 8 +- 3 files changed, 181 insertions(+), 118 deletions(-) diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index c0e912e5..54b596bc 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -29,31 +29,36 @@ #include "fixed_point.h" #include "platform_gpio.h" #include "spi_bus.h" -#if defined(PRALINE) - #include "max2831.h" + +#if defined(PRALINE) || defined(UNIVERSAL) #include "max2831_target.h" -#else - #include "max2837.h" +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) #include "max2837_target.h" - #include "max2839.h" +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include "max2839_target.h" #endif extern spi_bus_t spi_bus_ssp1; -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) max2831_driver_t max2831 = { .bus = &spi_bus_ssp1, .target_init = max2831_target_init, .set_mode = max2831_target_set_mode, }; -#else +#endif + +#if !defined(PRALINE) || defined(UNIVERSAL) max2837_driver_t max2837 = { .bus = &spi_bus_ssp1, .target_init = max2837_target_init, .set_mode = max2837_target_set_mode, }; +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) max2839_driver_t max2839 = { .bus = &spi_bus_ssp1, .target_init = max2839_target_init, @@ -69,36 +74,43 @@ void max283x_setup(max283x_driver_t* const drv, max283x_variant_t type) const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) max2831.gpio_enable = gpio->max283x_enable; max2831.gpio_rxtx = gpio->max283x_rx_enable; max2831.gpio_rxhp = gpio->max2831_rxhp; max2831.gpio_ld = gpio->max2831_ld; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) max2837.gpio_enable = gpio->max283x_enable; max2837.gpio_rx_enable = gpio->max283x_rx_enable; max2837.gpio_tx_enable = gpio->max283x_tx_enable; +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) max2839.gpio_enable = gpio->max283x_enable; max2839.gpio_rxtx = gpio->max283x_rx_enable; #endif - switch (type) { -#ifdef PRALINE + switch (drv->type) { +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: memcpy(&drv->drv.max2831, &max2831, sizeof(max2831)); max2831_setup(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); max2837_setup(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); max2839_setup(&drv->drv.max2839); break; #endif + default: + break; } } @@ -106,44 +118,42 @@ void max283x_setup(max283x_driver_t* const drv, max283x_variant_t type) uint16_t max283x_num_regs(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return MAX2831_NUM_REGS; - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return MAX2837_NUM_REGS; - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return MAX2839_NUM_REGS; - break; #endif + default: + return 0; } - - return 0; } /* Returns the maximum data register value supported by the driver. */ uint16_t max283x_data_regs_max_value(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return MAX2831_DATA_REGS_MAX_VALUE; - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return MAX2837_DATA_REGS_MAX_VALUE; - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return MAX2839_DATA_REGS_MAX_VALUE; - break; #endif + default: + return 0; } - - return 0; } /* Read a register via SPI. Save a copy to memory and return @@ -151,22 +161,21 @@ uint16_t max283x_data_regs_max_value(max283x_driver_t* const drv) uint16_t max283x_reg_read(max283x_driver_t* const drv, uint8_t r) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_reg_read(&drv->drv.max2831, r); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_reg_read(&drv->drv.max2837, r); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_reg_read(&drv->drv.max2839, r); - break; #endif + default: + return 0; } - - return 0; } /* Write value to register via SPI and save a copy to memory. Mark @@ -174,19 +183,23 @@ uint16_t max283x_reg_read(max283x_driver_t* const drv, uint8_t r) void max283x_reg_write(max283x_driver_t* const drv, uint8_t r, uint16_t v) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_reg_write(&drv->drv.max2831, r, v); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_reg_write(&drv->drv.max2837, r, v); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_reg_write(&drv->drv.max2839, r, v); break; #endif + default: + break; } } @@ -196,97 +209,113 @@ void max283x_reg_write(max283x_driver_t* const drv, uint8_t r, uint16_t v) void max283x_regs_commit(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_regs_commit(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_regs_commit(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_regs_commit(&drv->drv.max2839); break; #endif + default: + break; } } void max283x_set_mode(max283x_driver_t* const drv, const max283x_mode_t new_mode) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_set_mode(&drv->drv.max2831, (max2831_mode_t) new_mode); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_set_mode(&drv->drv.max2837, (max2837_mode_t) new_mode); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_set_mode(&drv->drv.max2839, (max2839_mode_t) new_mode); break; #endif + default: + break; } } max283x_mode_t max283x_mode(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return (max283x_mode_t) max2831_mode(&drv->drv.max2831); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return (max283x_mode_t) max2837_mode(&drv->drv.max2837); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return (max283x_mode_t) max2839_mode(&drv->drv.max2839); - break; #endif + default: + return 0; } - return 0; } /* Turn on/off all chip functions. Does not control oscillator and CLKOUT */ void max283x_start(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_start(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_start(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_start(&drv->drv.max2839); break; #endif + default: + break; } } void max283x_stop(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_stop(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_stop(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_stop(&drv->drv.max2839); break; #endif + default: + break; } } @@ -297,19 +326,23 @@ fp_40_24_t max283x_set_frequency( bool program) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_frequency(&drv->drv.max2831, freq, program); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_frequency(&drv->drv.max2837, freq, program); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_frequency(&drv->drv.max2839, freq, program); break; #endif + default: + break; } return 0; } @@ -324,189 +357,206 @@ uint32_t max283x_set_lpf_bandwidth( #endif switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_lpf_bandwidth( &drv->drv.max2831, (max2831_mode_t) mode, bandwidth_hz); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_lpf_bandwidth(&drv->drv.max2837, bandwidth_hz); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_lpf_bandwidth(&drv->drv.max2839, bandwidth_hz); - break; #endif + default: + return 0; } - - return 0; } bool max283x_set_lna_gain(max283x_driver_t* const drv, const uint32_t gain_db) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_lna_gain(&drv->drv.max2831, gain_db); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_lna_gain(&drv->drv.max2837, gain_db); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_lna_gain(&drv->drv.max2839, gain_db); - break; #endif + default: + return false; } - - return false; } bool max283x_set_vga_gain(max283x_driver_t* const drv, const uint32_t gain_db) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_vga_gain(&drv->drv.max2831, gain_db); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_vga_gain(&drv->drv.max2837, gain_db); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_vga_gain(&drv->drv.max2839, gain_db); - break; #endif + default: + return false; } - - return false; } bool max283x_set_txvga_gain(max283x_driver_t* const drv, const uint32_t gain_db) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_txvga_gain(&drv->drv.max2831, gain_db); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_txvga_gain(&drv->drv.max2837, gain_db); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_txvga_gain(&drv->drv.max2839, gain_db); - break; #endif + default: + return false; } - - return false; } void max283x_tx(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_tx(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_tx(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_tx(&drv->drv.max2839); break; #endif + default: + break; } } void max283x_rx(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_rx(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_rx(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_rx(&drv->drv.max2839); break; #endif + default: + break; } } +/* Set MAX2831 receiver high-pass filter corner frequency in Hz */ void max283x_set_rx_hpf_frequency( max283x_driver_t* const drv, const max283x_rx_hpf_freq_t freq) { -#ifndef PRALINE +#if !defined(PRALINE) (void) freq; #endif switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_set_rx_hpf_frequency( &drv->drv.max2831, (max2831_rx_hpf_freq_t) freq); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: // unsupported break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: // unsupported break; #endif + default: + break; } } void max283x_tx_calibration(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_tx_calibration(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: // unsupported - use max283x_set_mode instead break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: // unsupported - use max283x_set_mode instead break; #endif + default: + break; } } void max283x_rx_calibration(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_rx_calibration(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: // unsupported - use max283x_set_mode instead break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: // unsupported - use max283x_set_mode instead break; #endif + default: + break; } } diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index db5a93f8..87da59b4 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -27,10 +27,13 @@ #include #include "fixed_point.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "max2831.h" -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) #include "max2837.h" +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include "max2839.h" #endif @@ -52,10 +55,13 @@ typedef enum { } max283x_rx_hpf_freq_t; typedef enum { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) MAX2831_VARIANT, -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) MAX2837_VARIANT, +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) MAX2839_VARIANT, #endif } max283x_variant_t; @@ -64,10 +70,13 @@ typedef struct { max283x_variant_t type; union { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) max2831_driver_t max2831; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) max2837_driver_t max2837; +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) max2839_driver_t max2839; #endif } drv; diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index cf77f70c..34de1c82 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -32,9 +32,11 @@ #include "max283x.h" #include "max5864.h" #include "mixer.h" -#include "platform_detect.h" #include "sgpio.h" #include "transceiver_mode.h" +#if defined(HACKRF_ONE) || defined(PRALINE) + #include "platform_detect.h" +#endif #if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) #include "platform_scu.h" #endif @@ -444,12 +446,14 @@ void rf_path_init(rf_path_t* const rf_path) ssp1_set_mode_max283x(); #ifdef PRALINE max283x_setup(&max283x, MAX2831_VARIANT); -#else +#elif defined(HACKRF_ONE) if (detected_platform() == BOARD_ID_HACKRF1_R9) { max283x_setup(&max283x, MAX2839_VARIANT); } else { max283x_setup(&max283x, MAX2837_VARIANT); } +#else + max283x_setup(&max283x, MAX2837_VARIANT); #endif max283x_start(&max283x); From dafa1349ccd800340b92c554bd06c26e49cfcb25 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 22:00:17 +0100 Subject: [PATCH 008/161] Universalize: rffc5071.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/rffc5071.c | 59 +++++++++++++++++++++++++++----------- firmware/common/rffc5071.h | 4 +-- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index ffe958c1..89befe98 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -35,16 +35,17 @@ #include #include -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include #endif #include "delay.h" #include "fixed_point.h" +#include "platform_detect.h" #include "rffc5071.h" #include "rffc5071_regs.def" // private register def macros #include "selftest.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "platform_scu.h" #endif @@ -109,15 +110,19 @@ void rffc5071_init(rffc5071_driver_t* const drv) */ void rffc5071_setup(rffc5071_driver_t* const drv) { + board_id_t board_id = detected_platform(); + gpio_set(drv->gpio_reset); gpio_output(drv->gpio_reset); -#ifdef PRALINE - /* Configure mixer PLL lock detect pin */ - const platform_scu_t* scu = platform_scu(); - scu_pinmux(scu->MIXER_LD, scu->MIXER_LD_PINCFG); - gpio_input(drv->gpio_ld); + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + /* Configure mixer PLL lock detect pin */ + const platform_scu_t* scu = platform_scu(); + scu_pinmux(scu->MIXER_LD, scu->MIXER_LD_PINCFG); + gpio_input(drv->gpio_ld); #endif + } rffc5071_init(drv); @@ -135,10 +140,16 @@ void rffc5071_setup(rffc5071_driver_t* const drv) set_RFFC5071_FULLD(drv, 0); set_RFFC5071_MODE(drv, 1); -#if defined(PRALINE) || defined(HACKRF_ONE) - /* Enable GPO Lock output signal */ - set_RFFC5071_LOCK(drv, 1); -#endif + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: + /* Enable GPO Lock output signal */ + set_RFFC5071_LOCK(drv, 1); + break; + default: + break; + } /* Enable reference oscillator standby */ set_RFFC5071_REFST(drv, 1); @@ -181,13 +192,17 @@ void rffc5071_lock_test(rffc5071_driver_t* const drv) bool rffc5071_check_lock(rffc5071_driver_t* const drv) { -#ifdef PRALINE - return gpio_read(drv->gpio_ld); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + return gpio_read(drv->gpio_ld); #else - set_RFFC5071_READSEL(drv, 0b0001); - rffc5071_regs_commit(drv); - return !!(rffc5071_reg_read(drv, RFFC5071_READBACK_REG) & 0x8000); + return false; #endif + } else { + set_RFFC5071_READSEL(drv, 0b0001); + rffc5071_regs_commit(drv); + return !!(rffc5071_reg_read(drv, RFFC5071_READBACK_REG) & 0x8000); + } } static uint16_t rffc5071_spi_read(rffc5071_driver_t* const drv, uint8_t r) @@ -360,9 +375,17 @@ void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t gpo) rffc5071_regs_commit(drv); } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) { + // This is only supported on Praline hardware. + // + // For all other boards we'll just return true to avoid a situation where a + // a caller is waiting for a lock signal that will never be detected. + if (detected_platform() != BOARD_ID_PRALINE) { + return true; + } + // The RFFC5072 can be configured to output PLL lock status on // GPO4. The lock detect signal is produced by a window detector // on the VCO tuning voltage. It goes high to show PLL lock when @@ -428,7 +451,9 @@ bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) uint8_t rsm_state = (rb >> 11) & 0b11111; // get gpo4 lock detect signal + #if defined(PRALINE) || defined(UNIVERSAL) bool gpo4_ld = gpio_read(drv->gpio_ld); + #endif // parse state switch (rsm_state) { diff --git a/firmware/common/rffc5071.h b/firmware/common/rffc5071.h index 41e0509d..6a0c4ce2 100644 --- a/firmware/common/rffc5071.h +++ b/firmware/common/rffc5071.h @@ -35,7 +35,7 @@ typedef struct { spi_bus_t* const bus; gpio_t gpio_reset; -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_ld; #endif uint16_t regs[RFFC5071_NUM_REGS]; @@ -70,7 +70,7 @@ extern void rffc5071_enable(rffc5071_driver_t* const drv); extern void rffc5071_disable(rffc5071_driver_t* const drv); extern void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) extern bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state); #endif extern bool rffc5071_check_lock(rffc5071_driver_t* const drv); From ccc144ad485bb8334384a92097c6804479608f52 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 22:04:35 +0100 Subject: [PATCH 009/161] Universalize: si5351.c Co-authored-by: Antoine van Gelder --- firmware/common/si5351c.c | 119 ++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index ceb9f4a9..b50aad81 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -23,7 +23,7 @@ #include #include -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include #endif @@ -32,7 +32,7 @@ #include "platform_detect.h" #include "selftest.h" #include "si5351c.h" -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include "gpio.h" #include "platform_gpio.h" #include "platform_scu.h" @@ -235,6 +235,7 @@ void si5351c_configure_multisynth( void si5351c_configure_clock_control(si5351c_driver_t* const drv) { uint8_t clkout_ctrl; + board_id_t board_id = detected_platform(); if (clkout_enabled) { clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | @@ -269,7 +270,8 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ }; - if (detected_platform() == BOARD_ID_HACKRF1_R9) { + if (board_id == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); @@ -280,37 +282,41 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) data[4] = SI5351C_CLK_POWERDOWN; data[5] = SI5351C_CLK_POWERDOWN; data[6] = SI5351C_CLK_POWERDOWN; +#endif } -#ifdef PRALINE - /* CLK0: AFE_CLK */ - data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); - /* CLK1: SCT_CLK and FPGA_CLK */ - data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); - /* CLK3: CLKOUT */ - clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); - /* CLK4: XCVR_CLK */ - data[5] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA) | SI5351C_CLK_INV; - data[6] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - data[3] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + /* CLK0: AFE_CLK */ + data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); + /* CLK1: SCT_CLK and FPGA_CLK */ + data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); - } else { - /* CLK2: MCU_CLK */ - data[3] = SI5351C_CLK_POWERDOWN; - } + /* CLK3: CLKOUT */ + clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); + /* CLK4: XCVR_CLK */ + data[5] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA) | SI5351C_CLK_INV; + data[6] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); + if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { + /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ + data[3] = SI5351C_CLK_FRAC_MODE | + SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); + } else { + /* CLK2: MCU_CLK */ + data[3] = SI5351C_CLK_POWERDOWN; + } #endif + } si5351c_write(drv, data, sizeof(data)); } @@ -325,30 +331,34 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) /* 7: Clock to CPU is deactivated as it is not used and creates noise */ /* 3: External clock output is deactivated by default */ -#ifndef PRALINE - uint8_t value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | - SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); -#else - uint8_t value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | - SI5351C_CLK_DISABLE(7); - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - value |= SI5351C_CLK_ENABLE(2); - } else { - value |= SI5351C_CLK_DISABLE(2); - } -#endif + board_id_t board_id = detected_platform(); uint8_t clkout = 3; + uint8_t value; - /* HackRF One r9 has only three clock generator outputs. */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - clkout = 2; + if (board_id == BOARD_ID_PRALINE) { value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | - SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | + SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | + SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); + if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { + /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ + value |= SI5351C_CLK_ENABLE(2); + } else { + value |= SI5351C_CLK_DISABLE(2); + } + } else { + value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | + SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | + SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); + + /* HackRF One r9 has only three clock generator outputs. */ + if (board_id == BOARD_ID_HACKRF1_R9) { + clkout = 2; + value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | + SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | + SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | + SI5351C_CLK_DISABLE(7); + } } value |= (clkout_enabled) ? SI5351C_CLK_ENABLE(clkout) : @@ -357,16 +367,16 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) si5351c_write(drv, data, sizeof(data)); outputs_disabled = value; -#if defined(HACKRF_ONE) if (detected_platform() == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) const platform_gpio_t* gpio = platform_gpio(); if (clkout_enabled) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); } - } #endif + } } void si5351c_set_int_mode( @@ -466,8 +476,8 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } -#if defined(HACKRF_ONE) if (detected_platform() == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -485,9 +495,8 @@ void si5351c_init(si5351c_driver_t* const drv) scu_pinmux(scu->H1R9_MCU_CLK_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); gpio_clear(gpio->h1r9_mcu_clk_en); gpio_output(gpio->h1r9_mcu_clk_en); - } #endif - (void) drv; + } } /* From 2a89ed72074f3c6ddb5b526ca4bce389dfb17ae3 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 22:05:13 +0100 Subject: [PATCH 010/161] Universalize: rf_path.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/rf_path.c | 355 ++++++++++++++++++++++---------------- firmware/common/rf_path.h | 77 +++++---- 2 files changed, 244 insertions(+), 188 deletions(-) diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 34de1c82..5c7345c4 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -23,7 +23,7 @@ #include "rf_path.h" -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) +#if !defined(JAWBREAKER) #include #endif @@ -33,11 +33,10 @@ #include "max5864.h" #include "mixer.h" #include "sgpio.h" +#include "platform_detect.h" #include "transceiver_mode.h" -#if defined(HACKRF_ONE) || defined(PRALINE) - #include "platform_detect.h" -#endif -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) +#if !defined(JAWBREAKER) + #include "gpio.h" #include "platform_scu.h" #endif @@ -97,17 +96,18 @@ #define SWITCHCTRL_ANT_PWR (1 << 6) /* turn on antenna port power */ -#ifdef HACKRF_ONE - +#if defined(HACKRF_ONE) || defined(UNIVERSAL) static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) { + board_id_t board_id = detected_platform(); + if (ctrl & SWITCHCTRL_TX) { - if (detected_platform() != BOARD_ID_HACKRF1_R9) { + if (board_id != BOARD_ID_HACKRF1_R9) { gpio_set(rf_path->gpio_tx); } gpio_clear(rf_path->gpio_rx); } else { - if (detected_platform() != BOARD_ID_HACKRF1_R9) { + if (board_id != BOARD_ID_HACKRF1_R9) { gpio_clear(rf_path->gpio_tx); } gpio_set(rf_path->gpio_rx); @@ -170,7 +170,7 @@ static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) gpio_set(rf_path->gpio_no_rx_amp_pwr); } - if (detected_platform() == BOARD_ID_HACKRF1_R9) { + if (board_id == BOARD_ID_HACKRF1_R9) { if (ctrl & SWITCHCTRL_ANT_PWR) { gpio_clear(rf_path->gpio_h1r9_no_ant_pwr); } else { @@ -190,7 +190,7 @@ static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) } #endif -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl) { if (ctrl & SWITCHCTRL_TX) { @@ -231,7 +231,7 @@ static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl) } #endif -#ifdef RAD1O +#if defined(RAD1O) static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) { if (ctrl & SWITCHCTRL_TX) { @@ -298,169 +298,220 @@ static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) { -#ifdef JAWBREAKER - (void) rf_path; /* silence unused param warning */ - mixer_set_gpo(&mixer, gpo); -#elif HACKRF_ONE - switchctrl_set_hackrf_one(rf_path, gpo); -#elif PRALINE - switchctrl_set_praline(rf_path, gpo); -#elif RAD1O - switchctrl_set_rad1o(rf_path, gpo); -#else - (void) gpo; +#if defined(JAWBREAKER) + (void) rf_path; #endif + + switch (detected_platform()) { + case BOARD_ID_JAWBREAKER: +#if defined(JAWBREAKER) + mixer_set_gpo(&mixer, gpo); +#endif + break; + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + switchctrl_set_hackrf_one(rf_path, gpo); +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + switchctrl_set_praline(rf_path, gpo); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + switchctrl_set_rad1o(rf_path, gpo); +#endif + break; + default: + break; + } } void rf_path_pin_setup(rf_path_t* const rf_path) { +#if defined(JAWBREAKER) + (void) rf_path; +#endif + + board_id_t board_id = detected_platform(); #if !defined(JAWBREAKER) const platform_scu_t* scu = platform_scu(); #endif -#ifdef HACKRF_ONE - /* Configure RF switch control signals */ - // clang-format off - scu_pinmux(scu->HP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->LP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->TX_MIX_BP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->NO_MIX_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->RX_MIX_BP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->TX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->MIX_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu_pinmux(scu->NO_TX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->AMP_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->RX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->NO_RX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - // clang-format on - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - scu_pinmux(scu->H1R9_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->H1R9_NO_ANT_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - gpio_clear(rf_path->gpio_h1r9_no_ant_pwr); - gpio_output(rf_path->gpio_h1r9_no_ant_pwr); - scu_pinmux(scu->H1R9_NO_VAA_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } else { - scu_pinmux(scu->TX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu_pinmux(scu->RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - gpio_output(rf_path->gpio_tx); - scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + /* Configure RF switch control signals */ + // clang-format off + scu_pinmux(scu->HP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->LP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->TX_MIX_BP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->NO_MIX_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RX_MIX_BP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->TX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->MIX_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu_pinmux(scu->NO_TX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->AMP_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->NO_RX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + // clang-format on + if (board_id == BOARD_ID_HACKRF1_R9) { + scu_pinmux(scu->H1R9_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux( + scu->H1R9_NO_ANT_PWR, + SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + gpio_clear(rf_path->gpio_h1r9_no_ant_pwr); + gpio_output(rf_path->gpio_h1r9_no_ant_pwr); + scu_pinmux( + scu->H1R9_NO_VAA_EN, + SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + } else { + scu_pinmux(scu->TX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu_pinmux(scu->RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + gpio_output(rf_path->gpio_tx); + scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + } - /* - * Safe (initial) switch settings turn off both amplifiers and antenna port - * power and enable both amp bypass and mixer bypass. - */ - switchctrl_set(rf_path, SWITCHCTRL_SAFE); + /* + * Safe (initial) switch settings turn off both amplifiers and antenna port + * power and enable both amp bypass and mixer bypass. + */ + switchctrl_set(rf_path, SWITCHCTRL_SAFE); - /* Configure RF switch control signals as outputs */ - gpio_output(rf_path->gpio_amp_bypass); - gpio_output(rf_path->gpio_no_mix_bypass); - gpio_output(rf_path->gpio_rx_amp); - gpio_output(rf_path->gpio_no_rx_amp_pwr); - gpio_output(rf_path->gpio_hp); - gpio_output(rf_path->gpio_lp); - gpio_output(rf_path->gpio_tx_mix_bp); - gpio_output(rf_path->gpio_rx_mix_bp); - gpio_output(rf_path->gpio_tx_amp); - gpio_output(rf_path->gpio_no_tx_amp_pwr); - gpio_output(rf_path->gpio_mix_bypass); - gpio_output(rf_path->gpio_rx); -#elif RAD1O - /* Configure RF switch control signals */ - // clang-format off - scu_pinmux(scu->BY_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->BY_AMP_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu_pinmux(scu->TX_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->TX_RX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->BY_MIX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->BY_MIX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->LOW_HIGH_FILT, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->LOW_HIGH_FILT_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->TX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->RX_LNA, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu_pinmux(scu->MIXER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - // clang-format on - - /* Configure RF power supply (VAA) switch */ - scu_pinmux(scu->VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - - /* - * Safe (initial) switch settings turn off both amplifiers and antenna port - * power and enable both amp bypass and mixer bypass. - */ - switchctrl_set(rf_path, SWITCHCTRL_SAFE); - - /* Configure RF switch control signals as outputs */ - gpio_output(rf_path->gpio_tx_rx_n); - gpio_output(rf_path->gpio_tx_rx); - gpio_output(rf_path->gpio_by_mix); - gpio_output(rf_path->gpio_by_mix_n); - gpio_output(rf_path->gpio_by_amp); - gpio_output(rf_path->gpio_by_amp_n); - gpio_output(rf_path->gpio_mixer_en); - gpio_output(rf_path->gpio_low_high_filt); - gpio_output(rf_path->gpio_low_high_filt_n); - gpio_output(rf_path->gpio_tx_amp); - gpio_output(rf_path->gpio_rx_lna); -#elif PRALINE - /* Configure RF switch control signals */ - scu_pinmux(scu->TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - board_rev_t rev = detected_revision(); - if ((rev == BOARD_REV_PRALINE_R1_0) || (rev == BOARD_REV_GSG_PRALINE_R1_0)) { - scu_pinmux(scu->MIX_EN_N_R1_0, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - } else { - scu_pinmux(scu->MIX_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } - scu_pinmux(scu->LPF_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->RF_AMP_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - - /* Configure antenna port power control signal */ - scu_pinmux(scu->ANT_BIAS_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - - /* Configure RF power supply (VAA) switch */ - scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - - /* - * Safe (initial) switch settings turn off both amplifiers and antenna port - * power and enable both amp bypass and mixer bypass. - */ - switchctrl_set(rf_path, SWITCHCTRL_SAFE); - - /* Configure RF switch control signals as outputs */ - gpio_output(rf_path->gpio_ant_bias_en_n); - gpio_output(rf_path->gpio_tx_en); - gpio_output(rf_path->gpio_mix_en_n); - gpio_output(rf_path->gpio_lpf_en); - gpio_output(rf_path->gpio_rf_amp_en); -#else - (void) rf_path; /* silence unused param warning */ + /* Configure RF switch control signals as outputs */ + gpio_output(rf_path->gpio_amp_bypass); + gpio_output(rf_path->gpio_no_mix_bypass); + gpio_output(rf_path->gpio_rx_amp); + gpio_output(rf_path->gpio_no_rx_amp_pwr); + gpio_output(rf_path->gpio_hp); + gpio_output(rf_path->gpio_lp); + gpio_output(rf_path->gpio_tx_mix_bp); + gpio_output(rf_path->gpio_rx_mix_bp); + gpio_output(rf_path->gpio_tx_amp); + gpio_output(rf_path->gpio_no_tx_amp_pwr); + gpio_output(rf_path->gpio_mix_bypass); + gpio_output(rf_path->gpio_rx); #endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + /* Configure RF switch control signals */ + // clang-format off + scu_pinmux(scu->BY_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->BY_AMP_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu_pinmux(scu->TX_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->TX_RX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->BY_MIX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->BY_MIX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->LOW_HIGH_FILT, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->LOW_HIGH_FILT_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->TX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RX_LNA, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu_pinmux(scu->MIXER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + // clang-format on + + /* Configure RF power supply (VAA) switch */ + scu_pinmux(scu->VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + + /* + * Safe (initial) switch settings turn off both amplifiers and antenna port + * power and enable both amp bypass and mixer bypass. + */ + switchctrl_set(rf_path, SWITCHCTRL_SAFE); + + /* Configure RF switch control signals as outputs */ + gpio_output(rf_path->gpio_tx_rx_n); + gpio_output(rf_path->gpio_tx_rx); + gpio_output(rf_path->gpio_by_mix); + gpio_output(rf_path->gpio_by_mix_n); + gpio_output(rf_path->gpio_by_amp); + gpio_output(rf_path->gpio_by_amp_n); + gpio_output(rf_path->gpio_mixer_en); + gpio_output(rf_path->gpio_low_high_filt); + gpio_output(rf_path->gpio_low_high_filt_n); + gpio_output(rf_path->gpio_tx_amp); + gpio_output(rf_path->gpio_rx_lna); +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + /* Configure RF switch control signals */ + scu_pinmux(scu->TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + board_rev_t rev = detected_revision(); + if ((rev == BOARD_REV_PRALINE_R1_0) || + (rev == BOARD_REV_GSG_PRALINE_R1_0)) { + scu_pinmux(scu->MIX_EN_N_R1_0, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + } else { + scu_pinmux(scu->MIX_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + } + scu_pinmux(scu->LPF_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RF_AMP_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + + /* Configure antenna port power control signal */ + scu_pinmux(scu->ANT_BIAS_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + + /* Configure RF power supply (VAA) switch */ + scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + + /* + * Safe (initial) switch settings turn off both amplifiers and antenna port + * power and enable both amp bypass and mixer bypass. + */ + switchctrl_set(rf_path, SWITCHCTRL_SAFE); + + /* Configure RF switch control signals as outputs */ + gpio_output(rf_path->gpio_ant_bias_en_n); + gpio_output(rf_path->gpio_tx_en); + gpio_output(rf_path->gpio_mix_en_n); + gpio_output(rf_path->gpio_lpf_en); + gpio_output(rf_path->gpio_rf_amp_en); +#endif + break; + default: + break; + } } void rf_path_init(rf_path_t* const rf_path) { + board_id_t board_id = detected_platform(); + ssp1_set_mode_max5864(); max5864_setup(&max5864); max5864_shutdown(&max5864); ssp1_set_mode_max283x(); -#ifdef PRALINE - max283x_setup(&max283x, MAX2831_VARIANT); -#elif defined(HACKRF_ONE) - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - max283x_setup(&max283x, MAX2839_VARIANT); - } else { - max283x_setup(&max283x, MAX2837_VARIANT); - } -#else - max283x_setup(&max283x, MAX2837_VARIANT); + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + max283x_setup(&max283x, MAX2831_VARIANT); #endif + break; + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + max283x_setup(&max283x, MAX2839_VARIANT); +#endif + break; + default: +#if !defined(PRALINE) || defined(UNIVERSAL) + max283x_setup(&max283x, MAX2837_VARIANT); +#endif + break; + } max283x_start(&max283x); - // On HackRF One, the mixer is now set up earlier in boot. -#ifndef HACKRF_ONE - mixer_setup(&mixer); -#endif + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + // On HackRF One, the mixer is now set up earlier in boot. + break; + default: + mixer_setup(&mixer); + break; + } rf_path->switchctrl = SWITCHCTRL_SAFE; } @@ -506,7 +557,7 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX); break; -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case RF_PATH_DIRECTION_TX_CALIBRATION: case RF_PATH_DIRECTION_RX_CALIBRATION: rf_path->switchctrl &= ~SWITCHCTRL_TX; diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index d9f45a30..4eb91d3f 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -26,7 +26,7 @@ #include #include "transceiver_mode.h" -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) #include "gpio.h" #endif @@ -34,7 +34,7 @@ typedef enum { RF_PATH_DIRECTION_OFF, RF_PATH_DIRECTION_RX, RF_PATH_DIRECTION_TX, -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) RF_PATH_DIRECTION_TX_CALIBRATION, RF_PATH_DIRECTION_RX_CALIBRATION, #endif @@ -48,43 +48,48 @@ typedef enum { typedef struct { uint8_t switchctrl; -#ifdef HACKRF_ONE - gpio_t gpio_hp; - gpio_t gpio_lp; - gpio_t gpio_tx_mix_bp; - gpio_t gpio_no_mix_bypass; - gpio_t gpio_rx_mix_bp; - gpio_t gpio_tx_amp; - gpio_t gpio_tx; - gpio_t gpio_mix_bypass; - gpio_t gpio_rx; - gpio_t gpio_no_tx_amp_pwr; - gpio_t gpio_amp_bypass; - gpio_t gpio_rx_amp; - gpio_t gpio_no_rx_amp_pwr; - // In HackRF One r9 this control signal has been moved to the microcontroller. - gpio_t gpio_h1r9_no_ant_pwr; + + struct { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio_t gpio_hp; + gpio_t gpio_lp; + gpio_t gpio_tx_mix_bp; + gpio_t gpio_no_mix_bypass; + gpio_t gpio_rx_mix_bp; + gpio_t gpio_tx_amp; + gpio_t gpio_tx; + gpio_t gpio_mix_bypass; + gpio_t gpio_rx; + gpio_t gpio_no_tx_amp_pwr; + gpio_t gpio_amp_bypass; + gpio_t gpio_rx_amp; + gpio_t gpio_no_rx_amp_pwr; + // In HackRF One r9 this control signal has been moved to the microcontroller. + gpio_t gpio_h1r9_no_ant_pwr; #endif -#ifdef RAD1O - gpio_t gpio_tx_rx_n; - gpio_t gpio_tx_rx; - gpio_t gpio_by_mix; - gpio_t gpio_by_mix_n; - gpio_t gpio_by_amp; - gpio_t gpio_by_amp_n; - gpio_t gpio_mixer_en; - gpio_t gpio_low_high_filt; - gpio_t gpio_low_high_filt_n; - gpio_t gpio_tx_amp; - gpio_t gpio_rx_lna; + +#if defined(RAD1O) + gpio_t gpio_tx_rx_n; + gpio_t gpio_tx_rx; + gpio_t gpio_by_mix; + gpio_t gpio_by_mix_n; + gpio_t gpio_by_amp; + gpio_t gpio_by_amp_n; + gpio_t gpio_mixer_en; + gpio_t gpio_low_high_filt; + gpio_t gpio_low_high_filt_n; + gpio_t gpio_tx_amp; + gpio_t gpio_rx_lna; #endif -#ifdef PRALINE - gpio_t gpio_tx_en; - gpio_t gpio_mix_en_n; - gpio_t gpio_lpf_en; - gpio_t gpio_rf_amp_en; - gpio_t gpio_ant_bias_en_n; + +#if defined(PRALINE) || defined(UNIVERSAL) + gpio_t gpio_tx_en; + gpio_t gpio_mix_en_n; + gpio_t gpio_lpf_en; + gpio_t gpio_rf_amp_en; + gpio_t gpio_ant_bias_en_n; #endif + }; } rf_path_t; void rf_path_pin_setup(rf_path_t* const rf_path); From 52c9b4ac0a7728d16d71abad9311d535700ed18b Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:32:50 +0100 Subject: [PATCH 011/161] Universalize: mixer.{c,h}, usb_api_register.c Co-authored-by: Antoine van Gelder --- firmware/common/mixer.c | 157 +++++++++++++++++-------- firmware/common/mixer.h | 27 ++++- firmware/common/rf_path.c | 9 +- firmware/hackrf_usb/hackrf_usb.c | 8 +- firmware/hackrf_usb/usb_api_register.c | 52 ++++++-- 5 files changed, 185 insertions(+), 68 deletions(-) diff --git a/firmware/common/mixer.c b/firmware/common/mixer.c index 8623ee6b..9e1afb32 100644 --- a/firmware/common/mixer.c +++ b/firmware/common/mixer.c @@ -25,101 +25,164 @@ #include "fixed_point.h" #include "platform_gpio.h" -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) +#if defined(RAD1O) + #include "max2871.h" +#else + #include "platform_detect.h" #include "rffc5071.h" #include "rffc5071_spi.h" #include "spi_bus.h" -#elif defined(RAD1O) - #include "max2871.h" #endif -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) +#if !defined(RAD1O) static rffc5071_spi_config_t rffc5071_spi_config; -spi_bus_t spi_bus_rffc5071 = { +static spi_bus_t spi_bus_rffc5071 = { .config = &rffc5071_spi_config, .start = rffc5071_spi_start, .stop = rffc5071_spi_stop, .transfer = rffc5071_spi_transfer, .transfer_gather = rffc5071_spi_transfer_gather, }; +#endif mixer_driver_t mixer = { - .bus = &spi_bus_rffc5071, -}; -#elif defined(RAD1O) -mixer_driver_t mixer = {}; +#if !defined(RAD1O) + .rffc5071.bus = &spi_bus_rffc5071, #endif +}; void mixer_bus_setup(mixer_driver_t* const mixer) { +#if defined(RAD1O) (void) mixer; - -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) +#else const platform_gpio_t* gpio = platform_gpio(); - rffc5071_spi_config = (rffc5071_spi_config_t){ - .gpio_select = gpio->rffc5072_select, - .gpio_clock = gpio->rffc5072_clock, - .gpio_data = gpio->rffc5072_data, - }; - spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config); #endif + + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_spi_config = (rffc5071_spi_config_t){ + .gpio_select = gpio->rffc5072_select, + .gpio_clock = gpio->rffc5072_clock, + .gpio_data = gpio->rffc5072_data, + }; + spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config); +#endif + break; + case MAX2871_VARIANT: + break; + } } -void mixer_setup(mixer_driver_t* const mixer) +void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) { + mixer->type = type; + const platform_gpio_t* gpio = platform_gpio(); -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - mixer->gpio_reset = gpio->rffc5072_reset; - #if defined(PRALINE) - mixer->gpio_ld = gpio->rffc5072_ld; + /* Mixer GPIO serial interface PinMux */ + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + mixer->rffc5071.gpio_reset = gpio->rffc5072_reset; + if (detected_platform() == BOARD_ID_PRALINE) { + #if defined(PRALINE) || defined(UNIVERSAL) + mixer->rffc5071.gpio_ld = gpio->rffc5072_ld; #endif - rffc5071_setup(mixer); -#elif defined(RAD1O) - mixer->gpio_vco_ce = gpio->vco_ce; - mixer->gpio_vco_sclk = gpio->vco_sclk; - mixer->gpio_vco_sdata = gpio->vco_sdata; - mixer->gpio_vco_le = gpio->vco_le; - mixer->gpio_synt_rfout_en = gpio->synt_rfout_en; - mixer->gpio_vco_mux = gpio->vco_mux; - max2871_setup(mixer); + } #endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + mixer->max2871.gpio_vco_ce = gpio->vco_ce; + mixer->max2871.gpio_vco_sclk = gpio->vco_sclk; + mixer->max2871.gpio_vco_sdata = gpio->vco_sdata; + mixer->max2871.gpio_vco_le = gpio->vco_le; + mixer->max2871.gpio_synt_rfout_en = gpio->synt_rfout_en; + mixer->max2871.gpio_vco_mux = gpio->vco_mux; +#endif + break; + } + + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_setup(&mixer->rffc5071); +#endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + max2871_setup(&mixer->max2871); +#endif + break; + } } fp_40_24_t mixer_set_frequency(mixer_driver_t* const mixer, fp_40_24_t lo, bool program) { -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - return rffc5071_set_frequency(mixer, lo, program); -#elif defined(RAD1O) - return max2871_set_frequency(mixer, lo, program); + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + return rffc5071_set_frequency(&mixer->rffc5071, lo, program); #endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + return max2871_set_frequency(&mixer->max2871, lo, program); +#endif + break; + } + + return 0; } void mixer_enable(mixer_driver_t* const mixer) { -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - rffc5071_enable(mixer); -#elif defined(RAD1O) - max2871_enable(mixer); + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_enable(&mixer->rffc5071); #endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + max2871_enable(&mixer->max2871); +#endif + break; + } } void mixer_disable(mixer_driver_t* const mixer) { -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - rffc5071_disable(mixer); -#elif defined(RAD1O) - max2871_disable(mixer); + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_disable(&mixer->rffc5071); #endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + max2871_disable(&mixer->max2871); +#endif + break; + } } void mixer_set_gpo(mixer_driver_t* const mixer, uint8_t gpo) { -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - rffc5071_set_gpo(mixer, gpo); -#elif defined(RAD1O) - (void) mixer; +#if defined(RAD1O) (void) gpo; #endif + + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_set_gpo(&mixer->rffc5071, gpo); +#endif + break; + case MAX2871_VARIANT: + break; + } } diff --git a/firmware/common/mixer.h b/firmware/common/mixer.h index 6ba47d75..0afacb6b 100644 --- a/firmware/common/mixer.h +++ b/firmware/common/mixer.h @@ -26,17 +26,32 @@ #include "fixed_point.h" -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - #include "rffc5071.h" -typedef rffc5071_driver_t mixer_driver_t; -#elif defined(RAD1O) +#if defined(RAD1O) #include "max2871.h" -typedef max2871_driver_t mixer_driver_t; +#else + #include "rffc5071.h" #endif +typedef enum { + RFFC5071_VARIANT, + MAX2871_VARIANT, +} mixer_variant_t; + +typedef struct { + mixer_variant_t type; + + union { +#if defined(RAD1O) + max2871_driver_t max2871; +#else + rffc5071_driver_t rffc5071; +#endif + }; +} mixer_driver_t; + #include extern void mixer_bus_setup(mixer_driver_t* const mixer); -extern void mixer_setup(mixer_driver_t* const mixer); +extern void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type); /* Set frequency (Hz). */ extern fp_40_24_t mixer_set_frequency( diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 5c7345c4..8d2a6e2c 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -508,8 +508,15 @@ void rf_path_init(rf_path_t* const rf_path) case BOARD_ID_HACKRF1_R9: // On HackRF One, the mixer is now set up earlier in boot. break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + mixer_setup(&mixer, MAX2871_VARIANT); +#endif + break; default: - mixer_setup(&mixer); +#if !defined(RAD1O) + mixer_setup(&mixer, RFFC5071_VARIANT); +#endif break; } rf_path->switchctrl = SWITCHCTRL_SAFE; diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index ee8f897d..471469e5 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -45,7 +45,7 @@ #include #include #include -#if defined(HACKRF_ONE) +#if !defined(RAD1O) #include #endif #if defined(PRALINE) || defined(HACKRF_ONE) @@ -308,7 +308,9 @@ int main(void) // This will be cleared if any self-test check fails. selftest.report.pass = true; + // Detect hardware platform before we do anything else. detect_hardware_platform(); + pin_shutdown(); #ifndef RAD1O clock_gen_shutdown(); @@ -340,7 +342,7 @@ int main(void) #ifdef HACKRF_ONE // Set up mixer before enabling RF power, because its // GPO is used to control the antenna bias tee. - mixer_setup(&mixer); + mixer_setup(&mixer, RFFC5071_VARIANT); #endif #if (defined HACKRF_ONE || defined RAD1O) enable_rf_power(); @@ -401,7 +403,7 @@ int main(void) rf_path_init(&rf_path); #ifndef RAD1O - rffc5071_lock_test(&mixer); + rffc5071_lock_test(&mixer.rffc5071); #endif #ifdef PRALINE diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index 01d9fa8f..b74589ec 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -30,17 +30,19 @@ #include #include +#include #include #include #include #include #include -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(JAWBREAKER) - #include -#endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include #endif +#if !defined(RAD1O) + #include + #include +#endif usb_request_status_t usb_vendor_request_write_max283x( usb_endpoint_t* const endpoint, @@ -136,20 +138,29 @@ usb_request_status_t usb_vendor_request_read_si5351c( } } -#ifndef RAD1O usb_request_status_t usb_vendor_request_write_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { +#if defined(RAD1O) + (void) endpoint; + (void) stage; + if (detected_platform() == BOARD_ID_RAD1O) { + return USB_REQUEST_STATUS_STALL; + } +#endif + if (stage == USB_TRANSFER_STAGE_SETUP) { +#if !defined(RAD1O) if (endpoint->setup.index < RFFC5071_NUM_REGS) { rffc5071_reg_write( - &mixer, + &mixer.rffc5071, endpoint->setup.index, endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); return USB_REQUEST_STATUS_OK; } +#endif return USB_REQUEST_STATUS_STALL; } else { return USB_REQUEST_STATUS_OK; @@ -160,10 +171,19 @@ usb_request_status_t usb_vendor_request_read_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - uint16_t value; +#if defined(RAD1O) + (void) endpoint; + (void) stage; + if (detected_platform() == BOARD_ID_RAD1O) { + return USB_REQUEST_STATUS_STALL; + } +#endif + if (stage == USB_TRANSFER_STAGE_SETUP) { +#if !defined(RAD1O) + uint16_t value; if (endpoint->setup.index < RFFC5071_NUM_REGS) { - value = rffc5071_reg_read(&mixer, endpoint->setup.index); + value = rffc5071_reg_read(&mixer.rffc5071, endpoint->setup.index); endpoint->buffer[0] = value & 0xff; endpoint->buffer[1] = value >> 8; usb_transfer_schedule_block( @@ -175,12 +195,12 @@ usb_request_status_t usb_vendor_request_read_rffc5071( usb_transfer_schedule_ack(endpoint->out); return USB_REQUEST_STATUS_OK; } +#endif return USB_REQUEST_STATUS_STALL; } else { return USB_REQUEST_STATUS_OK; } } -#endif usb_request_status_t usb_vendor_request_set_clkout_enable( usb_endpoint_t* const endpoint, @@ -266,13 +286,18 @@ usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( return USB_REQUEST_STATUS_OK; } -#ifdef PRALINE usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { +#if defined(PRALINE) || defined(UNIVERSAL) fpga_reg_write(&fpga, endpoint->setup.index, endpoint->setup.value); +#endif usb_transfer_schedule_ack(endpoint->in); return USB_REQUEST_STATUS_OK; } @@ -283,7 +308,12 @@ usb_request_status_t usb_vendor_request_read_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { +#if defined(PRALINE) || defined(UNIVERSAL) const uint8_t value = fpga_reg_read(&fpga, endpoint->setup.index); endpoint->buffer[0] = value; usb_transfer_schedule_block( @@ -292,11 +322,11 @@ usb_request_status_t usb_vendor_request_read_fpga_reg( 1, NULL, NULL); +#endif usb_transfer_schedule_ack(endpoint->out); } return USB_REQUEST_STATUS_OK; } -#endif /* * Each register is transferred as a uint8_t register number followed by a From 0b3ed0d43c2019dd3157ed5d44fe2cab58709062 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:51:41 +0100 Subject: [PATCH 012/161] Universalize: tuning.{c,h}, tune_config.h Co-authored-by: Antoine van Gelder --- firmware/common/tune_config.h | 2 +- firmware/common/tuning.c | 37 ++++++++++++++++++-------------- firmware/common/tuning.h | 2 ++ firmware/hackrf_usb/hackrf_usb.c | 2 ++ 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/firmware/common/tune_config.h b/firmware/common/tune_config.h index 7fc76af9..34c692e6 100644 --- a/firmware/common/tune_config.h +++ b/firmware/common/tune_config.h @@ -21,7 +21,7 @@ #pragma once -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) #include "fpga.h" typedef struct { diff --git a/firmware/common/tuning.c b/firmware/common/tuning.c index f613ab30..9c863beb 100644 --- a/firmware/common/tuning.c +++ b/firmware/common/tuning.c @@ -29,18 +29,23 @@ #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) -#ifdef PRALINE - #define MIN_BYPASS_FREQ FP_MHZ(2320) - #define MAX_BYPASS_FREQ FP_MHZ(2580) -#else - #define MIN_BYPASS_FREQ FP_MHZ(2170) - #define MAX_BYPASS_FREQ FP_MHZ(2740) -#endif - #define MID1_HP_FREQ FP_MHZ(3600) #define MID2_HP_FREQ FP_MHZ(5100) #define MAX_HP_FREQ FP_MHZ(7250) +static fp_40_24_t min_bypass_freq = FP_MHZ(2170); +static fp_40_24_t max_bypass_freq = FP_MHZ(2740); + +void tuning_setup(void) +{ +#if defined(PRALINE) || defined(UNIVERSAL) + if (detected_platform() == BOARD_ID_PRALINE) { + min_bypass_freq = FP_MHZ(2320); + max_bypass_freq = FP_MHZ(2580); + } +#endif +} + fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) { fp_40_24_t freq_if; @@ -57,8 +62,8 @@ fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) case RF_PATH_FILTER_HIGH_PASS: if (freq_rf < MID1_HP_FREQ) { /* IF is graduated from 2170 MHz to 2740 MHz */ - freq_if = MIN_BYPASS_FREQ + - (((freq_rf - (MAX_BYPASS_FREQ)) * 57) / 86); + freq_if = min_bypass_freq + + (((freq_rf - (max_bypass_freq)) * 57) / 86); } else if (freq_rf < MID2_HP_FREQ) { /* IF is graduated from 2350 MHz to 2650 MHz */ freq_if = FP_MHZ(2350) + ((freq_rf - (MID1_HP_FREQ)) / 5); @@ -75,9 +80,9 @@ fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) rf_path_filter_t select_img_reject(fp_40_24_t freq_rf) { - if (freq_rf > MAX_BYPASS_FREQ) { + if (freq_rf > max_bypass_freq) { return RF_PATH_FILTER_HIGH_PASS; - } else if (freq_rf >= MIN_BYPASS_FREQ) { + } else if (freq_rf >= min_bypass_freq) { return RF_PATH_FILTER_BYPASS; } else { return RF_PATH_FILTER_LOW_PASS; @@ -91,14 +96,14 @@ fp_40_24_t restrict_rf(fp_40_24_t freq_rf, rf_path_filter_t img_reject) switch (img_reject) { case RF_PATH_FILTER_LOW_PASS: - max_rf = MIN_BYPASS_FREQ; + max_rf = min_bypass_freq; break; case RF_PATH_FILTER_HIGH_PASS: - min_rf = MAX_BYPASS_FREQ; + min_rf = max_bypass_freq; break; default: - min_rf = MIN_BYPASS_FREQ; - max_rf = MAX_BYPASS_FREQ; + min_rf = min_bypass_freq; + max_rf = max_bypass_freq; } freq_rf = MIN(freq_rf, max_rf); freq_rf = MAX(freq_rf, min_rf); diff --git a/firmware/common/tuning.h b/firmware/common/tuning.h index a338cd28..db123ee3 100644 --- a/firmware/common/tuning.h +++ b/firmware/common/tuning.h @@ -26,6 +26,8 @@ #include "fixed_point.h" #include "rf_path.h" +void tuning_setup(void); + /** * Select fixed or graduated IF for a given RF and image reject filter. */ diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 471469e5..0f21e199 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -339,6 +340,7 @@ int main(void) clock_gen_init(); #endif #endif + tuning_setup(); #ifdef HACKRF_ONE // Set up mixer before enabling RF power, because its // GPO is used to control the antenna bias tee. From 8587964d323afe41129b89d4175c1ed650035848 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:53:20 +0100 Subject: [PATCH 013/161] Universalize: hackrf_ui.c Co-authored-by: Antoine van Gelder --- firmware/common/hackrf_ui.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index 6e230943..ac16a444 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -23,8 +23,9 @@ #include #include "hackrf_ui.h" +#include "platform_detect.h" #include "transceiver_mode.h" -#if defined(PRALINE) || defined(HACKRF_ONE) +#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) #include "ui_portapack.h" #endif #if defined(RAD1O) @@ -82,16 +83,26 @@ const hackrf_ui_t* hackrf_ui(void) { /* Detect on first use. If no UI hardware is detected, use a stub function table. */ if (ui == NULL && ui_enabled) { -#if (defined HACKRF_ONE || defined PRALINE) - if (portapack_hackrf_ui_init) { - ui = portapack_hackrf_ui_init(); - } + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + if (portapack_hackrf_ui_init) { + ui = portapack_hackrf_ui_init(); + } #endif -#ifdef RAD1O - if (rad1o_ui_setup) { - ui = rad1o_ui_setup(); - } + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + if (rad1o_ui_setup) { + ui = rad1o_ui_setup(); + } #endif + break; + default: + break; + } } if (ui == NULL) { From 547aa38294f037153712e4f8b82e645a91e3485c Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:54:12 +0100 Subject: [PATCH 014/161] Universalize: operacake_sctimer.c Co-authored-by: Antoine van Gelder --- firmware/common/operacake_sctimer.c | 40 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 9ae37567..21ce3d7e 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -30,6 +30,8 @@ #include #endif +#include + #include "delay.h" #include "sct.h" @@ -95,25 +97,31 @@ void operacake_sctimer_init(void) P7_0, SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); -#ifndef PRALINE - // Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12 - SGPIO_OUT_MUX_CFG12 = SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode - SGPIO_OUT_MUX_CFG_P_OE_CFG(0); // gpio_oe - SGPIO_GPIO_OENREG |= BIT12; + uint8_t sct_clock_input; + if (detected_platform() != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + // Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12 + SGPIO_OUT_MUX_CFG12 = + SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode + SGPIO_OUT_MUX_CFG_P_OE_CFG(0); // gpio_oe + SGPIO_GPIO_OENREG |= BIT12; - // Use the GIMA to connect the SGPIO clock to the SCTimer - GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1 + // Use the GIMA to connect the SGPIO clock to the SCTimer + GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1 - uint8_t sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1; -#else - // Configure pin P6_4 as SCT_IN_6 - scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); - - // Use the GIMA to connect MS0/CLK1 (SCT_CLK) on pin P6_4 to the SCTimer - GIMA_CTIN_6_IN = 0x0 << 4; - - uint8_t sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6; + sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1; #endif + } else { +#if defined(PRALINE) || defined(UNIVERSAL) + // Configure pin P6_4 as SCT_IN_6 + scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); + + // Use the GIMA to connect MS0/CLK1 (SCT_CLK) on pin P6_4 to the SCTimer + GIMA_CTIN_6_IN = 0x0 << 4; + + sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6; +#endif + } // We configure this register first, because the user manual says to SCT_CONFIG |= SCT_CONFIG_UNIFY_32_BIT | SCT_CONFIG_CLKMODE_PRESCALED_BUS_CLOCK | From 85ada5ba488468ff73dc1a46fab9ac9c6204a103 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:55:40 +0100 Subject: [PATCH 015/161] Universalize: w25q80bv.c Co-authored-by: Antoine van Gelder --- firmware/common/w25q80bv.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/firmware/common/w25q80bv.c b/firmware/common/w25q80bv.c index 9e7e1acd..f569bedf 100644 --- a/firmware/common/w25q80bv.c +++ b/firmware/common/w25q80bv.c @@ -27,10 +27,11 @@ * programming the flash. */ -#include "w25q80bv.h" - -#include #include +#include + +#include "platform_detect.h" +#include "w25q80bv.h" #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -59,13 +60,13 @@ void w25q80bv_setup(w25q80bv_driver_t* const drv) uint8_t device_id; drv->page_len = 256U; -#ifdef PRALINE - drv->num_pages = 16384U; - drv->num_bytes = 4194304U; -#else - drv->num_pages = 4096U; - drv->num_bytes = 1048576U; -#endif + if (detected_platform() == BOARD_ID_PRALINE) { + drv->num_pages = 16384U; + drv->num_bytes = 4194304U; + } else { + drv->num_pages = 4096U; + drv->num_bytes = 1048576U; + } drv->target_init(drv); From 7c78654afc9fe385f9474f05bde530819f5bdd32 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:56:31 +0100 Subject: [PATCH 016/161] Universalize: sgpio.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/sgpio.c | 14 ++++++++------ firmware/common/sgpio.h | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index f364d7b0..ac755d51 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -58,7 +58,7 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config) scu_pinmux(scu->PINMUX_SGPIO15, scu->PINMUX_SGPIO15_PINCFG); /* GPIO5[14] */ if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) scu_pinmux( scu->H1R9_TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[5] */ @@ -72,10 +72,12 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config) sgpio_cpld_set_mixer_invert(config, 0); gpio_output(config->gpio_q_invert); -#ifndef PRALINE - trigger_enable(false); - gpio_output(config->gpio_trigger_enable); + if (detected_platform() != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + trigger_enable(false); + gpio_output(config->gpio_trigger_enable); #endif + } } void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice) @@ -157,11 +159,11 @@ void sgpio_configure(sgpio_config_t* const config, const sgpio_direction_t direc SGPIO_OUT_MUX_CFG_P_OE_CFG(0) // gpio_oe (state set by GPIO_OEREG) | SGPIO_OUT_MUX_CFG_P_OUT_CFG(0) // dout_doutm1 (1-bit mode) ; - SGPIO_OUT_MUX_CFG(10) = // GPIO10: Output: disable + SGPIO_OUT_MUX_CFG(10) = // GPIO10: Output: disable SGPIO_OUT_MUX_CFG_P_OE_CFG(0) // gpio_oe (state set by GPIO_OEREG) | SGPIO_OUT_MUX_CFG_P_OUT_CFG(4) // gpio_out (level set by GPIO_OUTREG) ; - SGPIO_OUT_MUX_CFG(11) = // GPIO11: Output: direction + SGPIO_OUT_MUX_CFG(11) = // GPIO11: Output: direction SGPIO_OUT_MUX_CFG_P_OE_CFG(0) // gpio_oe (state set by GPIO_OEREG) | SGPIO_OUT_MUX_CFG_P_OUT_CFG(4) // gpio_out (level set by GPIO_OUTREG) ; diff --git a/firmware/common/sgpio.h b/firmware/common/sgpio.h index a3b6b2eb..f6fdf213 100644 --- a/firmware/common/sgpio.h +++ b/firmware/common/sgpio.h @@ -34,7 +34,7 @@ typedef enum { typedef struct { gpio_t gpio_q_invert; -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_trigger_enable; #endif bool slice_mode_multislice; From a14c4c85f820b0fa8e4bf7e0acf430749054c12c Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:57:58 +0100 Subject: [PATCH 017/161] Universalize: radio.c Co-authored-by: Antoine van Gelder --- firmware/common/radio.c | 202 ++++++++++++++++++++++++---------------- 1 file changed, 120 insertions(+), 82 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 0692274c..2a6a46c7 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -35,7 +35,7 @@ #include "sgpio.h" #include "transceiver_mode.h" #include "tuning.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "fpga.h" #include "tune_config.h" #endif @@ -200,18 +200,22 @@ static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank) case TRANSCEIVER_MODE_SS: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) { -#ifdef PRALINE - fpga_set_tx_interpolation_ratio(&fpga, n); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_tx_interpolation_ratio(&fpga, n); #endif + } radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX] = n; } break; default: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) { -#ifdef PRALINE - fpga_set_rx_decimation_ratio(&fpga, n); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_rx_decimation_ratio(&fpga, n); #endif + } radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX] = n; } } @@ -301,7 +305,7 @@ static fp_40_24_t digital_from_analog_rf( return drf; } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) /* * Convert center frequency of digital baseband (as seen by MCU) to center * frequency of analog baseband (as seen by ADC/DAC). @@ -430,7 +434,7 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) /* Look up settings appropriate for requested RF. */ if (detected_platform() == BOARD_ID_PRALINE) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) const tune_config_t* tune_config = select_tune_config(opmode, freq_rf); if (requested_rotation == RADIO_UNSET) { @@ -510,9 +514,11 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) new_freq = true; } if ((rotation != applied_rotation) && (rotation != RADIO_UNSET)) { -#ifdef PRALINE - fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); #endif + } radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION] = rotation; new_freq = true; } @@ -587,68 +593,93 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE]; } -#ifdef PRALINE - /* Praline legacy mode always sets baseband bandwidth automatically. */ - (void) bank; - uint32_t lpf_bandwidth = auto_bandwidth(radio, opmode); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + /* Praline legacy mode always sets baseband bandwidth automatically. */ + (void) bank; + uint32_t lpf_bandwidth = auto_bandwidth(radio, opmode); - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; - new_bw = true; - } - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_RX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; - new_bw = true; - } - bool narrow_lpf_enable = false; - bool applied_narrow_lpf_enable = - radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF]; - if (lpf_bandwidth <= 1750000) { - narrow_lpf_enable = true; - } - if (applied_narrow_lpf_enable != narrow_lpf_enable) { - narrowband_filter_set(narrow_lpf_enable); - radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF] = - narrow_lpf_enable; - new_bw = true; - } - /* Always set HPF bandwidth to 30 kHz for now. */ - const max283x_rx_hpf_freq_t hpf_bandwidth = MAX283x_RX_HPF_30_KHZ; - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] != hpf_bandwidth) { - max283x_set_rx_hpf_frequency(&max283x, hpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] = hpf_bandwidth; - new_bw = true; - } -#else - uint64_t lpf_bandwidth; - lpf_bandwidth = bank[RADIO_XCVR_TX_LPF]; - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = bank[RADIO_XCVR_RX_LPF]; - } - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_TX]; - } - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_RX]; - } - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF]; - } - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = auto_bandwidth(radio, opmode); - } - - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_RX] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_TX] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; - new_bw = true; - } + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != + lpf_bandwidth) { + max283x_set_lpf_bandwidth( + &max283x, + MAX283x_MODE_TX, + lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = + lpf_bandwidth; + new_bw = true; + } + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] != + lpf_bandwidth) { + max283x_set_lpf_bandwidth( + &max283x, + MAX283x_MODE_RX, + lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = + lpf_bandwidth; + new_bw = true; + } + bool narrow_lpf_enable = false; + bool applied_narrow_lpf_enable = + radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF]; + if (lpf_bandwidth <= 1750000) { + narrow_lpf_enable = true; + } + if (applied_narrow_lpf_enable != narrow_lpf_enable) { + narrowband_filter_set(narrow_lpf_enable); + radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF] = + narrow_lpf_enable; + new_bw = true; + } + /* Always set HPF bandwidth to 30 kHz for now. */ + const max283x_rx_hpf_freq_t hpf_bandwidth = MAX283x_RX_HPF_30_KHZ; + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] != + hpf_bandwidth) { + max283x_set_rx_hpf_frequency(&max283x, hpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] = + hpf_bandwidth; + new_bw = true; + } #endif + } else { +#if !defined(PRALINE) || defined(UNIVERSAL) + uint64_t lpf_bandwidth; + lpf_bandwidth = bank[RADIO_XCVR_TX_LPF]; + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = bank[RADIO_XCVR_RX_LPF]; + } + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_TX]; + } + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_RX]; + } + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF]; + } + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = auto_bandwidth(radio, opmode); + } + + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != + lpf_bandwidth) { + max283x_set_lpf_bandwidth( + &max283x, + MAX283x_MODE_TX, + lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_RX] = + lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_TX] = + lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = + lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = + lpf_bandwidth; + new_bw = true; + } +#endif + } return new_bw; } @@ -784,26 +815,33 @@ static bool radio_update_trigger(radio_t* const radio, uint64_t* bank) static bool radio_update_dc_block(radio_t* const radio, uint64_t* bank) { -#ifndef PRALINE +#if !defined(PRALINE) && !defined(UNIVERSAL) (void) radio; (void) bank; return false; -#else - const uint64_t requested = bank[RADIO_DC_BLOCK]; - bool enable = requested; +#endif - if (requested == RADIO_UNSET) { - enable = true; - } + if (detected_platform() == BOARD_ID_PRALINE) { + const uint64_t requested = bank[RADIO_DC_BLOCK]; + bool enable = requested; - if (radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] == (uint64_t) enable) { + if (requested == RADIO_UNSET) { + enable = true; + } + + if (radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] == + (uint64_t) enable) { + return false; + } + +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_rx_dc_block_enable(&fpga, enable); +#endif + radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; + return true; + } else { return false; } - - fpga_set_rx_dc_block_enable(&fpga, enable); - radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; - return true; -#endif } bool radio_update(radio_t* const radio) From d862545ce3c78c7de1a5d6a0775dbac04e7f37ce Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 01:09:30 +0100 Subject: [PATCH 018/161] Universalize: hackrf_core.{c,h}, selftest.h Co-authored-by: Antoine van Gelder --- firmware/common/hackrf_core.c | 781 +++++++++++++++++++++------------- firmware/common/hackrf_core.h | 18 +- firmware/common/selftest.h | 9 +- 3 files changed, 504 insertions(+), 304 deletions(-) diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 25e3b870..c8b66443 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -25,7 +25,7 @@ #include #include #include -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) +#if !defined(RAD1O) #include #endif @@ -41,10 +41,10 @@ #include "platform_scu.h" #include "spi_bus.h" #include "w25q80bv_target.h" -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) #include "portapack.h" #endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "ice40_spi.h" #endif @@ -124,7 +124,7 @@ ssp_config_t ssp_config_w25q80bv = { .clock_prescale_rate = 2, }; -spi_bus_t spi_bus_ssp0 = { +static spi_bus_t spi_bus_ssp0 = { .obj = (void*) SSP0_BASE, .config = &ssp_config_w25q80bv, .start = spi_ssp_start, @@ -142,8 +142,8 @@ sgpio_config_t sgpio_config = { .slice_mode_multislice = true, }; -#ifdef PRALINE -ssp_config_t ssp_config_ice40_fpga = { +#if defined(PRALINE) || defined(UNIVERSAL) +static ssp_config_t ssp_config_ice40_fpga = { .data_bits = SSP_DATA_8BITS, .spi_mode = SSP_CPOL_1_CPHA_1, .serial_clock_rate = 21, @@ -163,11 +163,9 @@ radio_t radio = { .sample_rate_cb = sample_rate_set, }; -rf_path_t rf_path = { - .switchctrl = 0, -}; +rf_path_t rf_path; -jtag_gpio_t jtag_gpio_cpld = {}; +jtag_gpio_t jtag_gpio_cpld; jtag_t jtag_cpld = { .gpio = &jtag_gpio_cpld, @@ -343,61 +341,77 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) sgpio_cpld_stream_disable(&sgpio_config); } -#ifndef PRALINE - /* Integer mode can be enabled if p1 is even and p2 is zero. */ - if (p1 & 0x1 || p2) { - si5351c_set_int_mode(&clock_gen, 0, 0); - } else { - si5351c_set_int_mode(&clock_gen, 0, 1); - } + if (detected_platform() != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + /* Integer mode can be enabled if p1 is even and p2 is zero. */ + if (p1 & 0x1 || p2) { + si5351c_set_int_mode(&clock_gen, 0, 0); + } else { + si5351c_set_int_mode(&clock_gen, 0, 1); + } - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - /* - * On HackRF One r9 all sample clocks are externally derived - * from MS1/CLK1 operating at twice the sample rate. - */ - si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); + if (detected_platform() == BOARD_ID_HACKRF1_R9) { + /* + * On HackRF One r9 all sample clocks are externally derived + * from MS1/CLK1 operating at twice the sample rate. + */ + si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); + } else { + /* + * On other platforms the clock generator produces three + * different sample clocks, all derived from multisynth 0. + */ + /* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */ + si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); + + /* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */ + si5351c_configure_multisynth( + &clock_gen, + 1, + 0, + 0, + 0, + 0); //p1 doesn't matter + + /* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */ + si5351c_configure_multisynth( + &clock_gen, + 2, + 0, + 0, + 0, + 0); //p1 doesn't matter + } +#endif } else { - /* - * On other platforms the clock generator produces three - * different sample clocks, all derived from multisynth 0. - */ - /* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */ +#if defined(PRALINE) || defined(UNIVERSAL) + /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); - /* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */ - si5351c_configure_multisynth(&clock_gen, 1, 0, 0, 0, 0); //p1 doesn't matter + /* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */ + si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 1); - /* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */ - si5351c_configure_multisynth(&clock_gen, 2, 0, 0, 0, 0); //p1 doesn't matter - } -#else - /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ - si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); + /* Delay FPGA_CLK relative to AFE_CLK. */ + uint8_t phase_offset = 0; + if (p1 < 2100) { + phase_offset = (p1 >> 4) - 6; + } + si5351c_set_phase(&clock_gen, 1, phase_offset); - /* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */ - si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 1); + if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { + /* + * On older boards FPGA_CLK is on CLK2 while SCT_CLK is on + * CLK1. We configure both so that behavior is consistent with + * newer boards that use CLK1 for both FPGA_CLK and SCT_CLK. + */ + si5351c_configure_multisynth(&clock_gen, 2, p1, p2, p3, 1); + si5351c_set_phase(&clock_gen, 2, phase_offset); + } - /* Delay FPGA_CLK relative to AFE_CLK. */ - uint8_t phase_offset = 0; - if (p1 < 2100) { - phase_offset = (p1 >> 4) - 6; - } - si5351c_set_phase(&clock_gen, 1, phase_offset); - - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* - * On older boards FPGA_CLK is on CLK2 while SCT_CLK is on - * CLK1. We configure both so that behavior is consistent with - * newer boards that use CLK1 for both FPGA_CLK and SCT_CLK. - */ - si5351c_configure_multisynth(&clock_gen, 2, p1, p2, p3, 1); - si5351c_set_phase(&clock_gen, 2, phase_offset); - } - - /* Reset PLL to synchronize output clock phase. */ - si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); + /* Reset PLL to synchronize output clock phase. */ + si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); #endif + } if (streaming) { sgpio_cpld_stream_enable(&sgpio_config); @@ -549,7 +563,7 @@ void cpu_clock_init(void) CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); -#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE) +#if !defined(RAD1O) /* Disable unused clocks */ /* Start with PLLs */ CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); @@ -707,12 +721,20 @@ void clock_gen_shutdown(void) clock_source_t activate_best_clock_source(void) { -#if (defined HACKRF_ONE || defined PRALINE) - /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ - if (portapack_reference_oscillator && portapack()) { - portapack_reference_oscillator(false); - } + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ + if (portapack_reference_oscillator && portapack()) { + portapack_reference_oscillator(false); + } #endif + break; + default: + break; + } clock_source_t source = CLOCK_SOURCE_HACKRF; @@ -720,18 +742,26 @@ clock_source_t activate_best_clock_source(void) if (si5351c_clkin_signal_valid(&clock_gen)) { source = CLOCK_SOURCE_EXTERNAL; } else { -#if (defined HACKRF_ONE || defined PRALINE) - /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ - if (portapack_reference_oscillator && portapack()) { - portapack_reference_oscillator(true); - delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */ - if (si5351c_clkin_signal_valid(&clock_gen)) { - source = CLOCK_SOURCE_PORTAPACK; - } else { - portapack_reference_oscillator(false); + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ + if (portapack_reference_oscillator && portapack()) { + portapack_reference_oscillator(true); + delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */ + if (si5351c_clkin_signal_valid(&clock_gen)) { + source = CLOCK_SOURCE_PORTAPACK; + } else { + portapack_reference_oscillator(false); + } } - } #endif + break; + default: + break; + } /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ } @@ -753,7 +783,7 @@ void ssp1_set_mode_max5864(void) spi_bus_start(max5864.bus, &ssp_config_max5864); } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void ssp1_set_mode_ice40(void) { spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); @@ -762,12 +792,14 @@ void ssp1_set_mode_ice40(void) void pin_shutdown(void) { - const platform_gpio_t* gpio = platform_gpio(); - const platform_scu_t* scu = platform_scu(); - /* Configure all GPIO as Input (safe state) */ gpio_init(); + /* Detect Platform */ + board_id_t board_id = detected_platform(); + const platform_gpio_t* gpio = platform_gpio(); + const platform_scu_t* scu = platform_scu(); + /* TDI and TMS pull-ups are required in all JTAG-compliant devices. * * The HackRF CPLD is always present, so let the CPLD pull up its TDI and TMS. @@ -782,119 +814,159 @@ void pin_shutdown(void) * * LPC43xx pull-up and pull-down resistors are approximately 53K. */ -#if (defined HACKRF_ONE || defined PRALINE) - scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); #endif + break; + default: + break; + } scu_pinmux(scu->PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); -#ifndef PRALINE - scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); #endif + } /* Configure SCU Pin Mux as GPIO */ scu_pinmux(scu->PINMUX_LED1, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED2, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED3, SCU_GPIO_NOPULL); -#ifdef RAD1O - scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4); + switch (board_id) { + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4); #endif -#ifdef PRALINE - scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); #endif + break; + default: + break; + } /* Configure USB indicators */ -#ifdef JAWBREAKER - scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3); - scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3); + if (board_id == BOARD_ID_JAWBREAKER) { +#if defined(JAWBREAKER) + scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3); + scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3); #endif - -#ifdef PRALINE - disable_1v2_power(); - disable_3v3aux_power(); - gpio_output(gpio->gpio_1v2_enable); - gpio_output(gpio->gpio_3v3aux_enable_n); - scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#else - disable_1v8_power(); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - #ifdef HACKRF_ONE - gpio_output(gpio->h1r9_1v8_enable); - scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - #endif - } else { - gpio_output(gpio->gpio_1v8_enable); - scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); } + + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + disable_1v2_power(); + disable_3v3aux_power(); + gpio_output(gpio->gpio_1v2_enable); + gpio_output(gpio->gpio_3v3aux_enable_n); + scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); #endif - -#if (defined HACKRF_ONE || defined PRALINE) - /* Safe state: start with VAA turned off: */ - disable_rf_power(); - - /* Configure RF power supply (VAA) switch control signal as output */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - #ifdef HACKRF_ONE - gpio_output(gpio->h1r9_vaa_disable); - #endif - } else { - gpio_output(gpio->vaa_disable); + break; + default: + disable_1v8_power(); + if (detected_platform() == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio_output(gpio->h1r9_1v8_enable); + scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); +#endif + } else { + gpio_output(gpio->gpio_1v8_enable); + scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + } + break; } + + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Safe state: start with VAA turned off: */ + disable_rf_power(); + + /* Configure RF power supply (VAA) switch control signal as output */ + if (board_id == BOARD_ID_HACKRF1_R9) { + #if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio_output(gpio->h1r9_vaa_disable); + #endif + } else { + gpio_output(gpio->vaa_disable); + } + break; #endif + case BOARD_ID_RAD1O: +#if defined(RAD1O) + /* Safe state: start with VAA turned off: */ + disable_rf_power(); -#ifdef RAD1O - /* Safe state: start with VAA turned off: */ - disable_rf_power(); + /* Configure RF power supply (VAA) switch control signal as output */ + gpio_output(gpio->vaa_enable); - /* Configure RF power supply (VAA) switch control signal as output */ - gpio_output(gpio->vaa_enable); + /* Disable unused clock outputs. They generate noise. */ + scu_pinmux(CLK0, SCU_CLK_IN | SCU_CONF_FUNCTION7); + scu_pinmux(CLK2, SCU_CLK_IN | SCU_CONF_FUNCTION7); - /* Disable unused clock outputs. They generate noise. */ - scu_pinmux(CLK0, SCU_CLK_IN | SCU_CONF_FUNCTION7); - scu_pinmux(CLK2, SCU_CLK_IN | SCU_CONF_FUNCTION7); - - scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); #endif + break; + default: + break; + } -#ifdef PRALINE - scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG); - scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG); - scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG); - scu_pinmux(scu->P1_CTRL1, scu->P1_CTRL1_PINCFG); - scu_pinmux(scu->P1_CTRL2, scu->P1_CTRL2_PINCFG); - scu_pinmux(scu->CLKIN_CTRL, scu->CLKIN_CTRL_PINCFG); - scu_pinmux(scu->AA_EN, scu->AA_EN_PINCFG); - scu_pinmux(scu->TRIGGER_IN, scu->TRIGGER_IN_PINCFG); - scu_pinmux(scu->TRIGGER_OUT, scu->TRIGGER_OUT_PINCFG); - scu_pinmux(scu->PPS_OUT, scu->PPS_OUT_PINCFG); - scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4); - scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG); + scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG); + scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG); + scu_pinmux(scu->P1_CTRL1, scu->P1_CTRL1_PINCFG); + scu_pinmux(scu->P1_CTRL2, scu->P1_CTRL2_PINCFG); + scu_pinmux(scu->CLKIN_CTRL, scu->CLKIN_CTRL_PINCFG); + scu_pinmux(scu->AA_EN, scu->AA_EN_PINCFG); + scu_pinmux(scu->TRIGGER_IN, scu->TRIGGER_IN_PINCFG); + scu_pinmux(scu->TRIGGER_OUT, scu->TRIGGER_OUT_PINCFG); + scu_pinmux(scu->PPS_OUT, scu->PPS_OUT_PINCFG); + scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4); + scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - p2_ctrl_set(P2_SIGNAL_CLK3); - p1_ctrl_set(P1_SIGNAL_CLKIN); - narrowband_filter_set(0); - clkin_ctrl_set(CLKIN_SIGNAL_P22); + p2_ctrl_set(P2_SIGNAL_CLK3); + p1_ctrl_set(P1_SIGNAL_CLKIN); + narrowband_filter_set(0); + clkin_ctrl_set(CLKIN_SIGNAL_P22); - gpio_output(gpio->p2_ctrl0); - gpio_output(gpio->p2_ctrl1); - gpio_output(gpio->p1_ctrl0); - gpio_output(gpio->p1_ctrl1); - gpio_output(gpio->p1_ctrl2); - gpio_output(gpio->clkin_ctrl); - gpio_output(gpio->pps_out); - gpio_output(gpio->aa_en); - gpio_input(gpio->trigger_in); - gpio_input(gpio->trigger_out); - gpio_clear(gpio->fpga_cfg_spi_cs); - gpio_output(gpio->fpga_cfg_spi_cs); - gpio_clear(gpio->fpga_cfg_creset); - gpio_output(gpio->fpga_cfg_creset); - gpio_input(gpio->fpga_cfg_cdone); + gpio_output(gpio->p2_ctrl0); + gpio_output(gpio->p2_ctrl1); + gpio_output(gpio->p1_ctrl0); + gpio_output(gpio->p1_ctrl1); + gpio_output(gpio->p1_ctrl2); + gpio_output(gpio->clkin_ctrl); + gpio_output(gpio->pps_out); + gpio_output(gpio->aa_en); + gpio_input(gpio->trigger_in); + gpio_input(gpio->trigger_out); + gpio_clear(gpio->fpga_cfg_spi_cs); + gpio_output(gpio->fpga_cfg_spi_cs); + gpio_clear(gpio->fpga_cfg_creset); + gpio_output(gpio->fpga_cfg_creset); + gpio_input(gpio->fpga_cfg_cdone); #endif + break; + default: + break; + } /* enable input on SCL and SDA pins */ SCU_SFSI2C0 = SCU_I2C0_NOMINAL; @@ -903,30 +975,43 @@ void pin_shutdown(void) /* Run after pin_shutdown() and prior to enabling power supplies. */ void pin_setup(void) { + /* Detect Platform */ + board_id_t board_id = detected_platform(); const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); + /* Configure LEDs */ led_off(0); led_off(1); led_off(2); -#ifdef RAD1O - led_off(3); -#endif + switch (board_id) { + case BOARD_ID_RAD1O: + led_off(3); + default: + break; + } gpio_output(gpio->led[0]); gpio_output(gpio->led[1]); gpio_output(gpio->led[2]); -#if (defined RAD1O || defined PRALINE) - gpio_output(gpio->led[3]); + switch (board_id) { + case BOARD_ID_RAD1O: + case BOARD_ID_PRALINE: +#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) + gpio_output(gpio->led[3]); #endif + break; + default: + break; + } /* Configure drivers and driver pins */ ssp_config_max283x.gpio_select = gpio->max283x_select; -#if defined(PRALINE) - ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words -#else - ssp_config_max283x.data_bits = SSP_DATA_16BITS; -#endif + if (board_id == BOARD_ID_PRALINE) { + ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words + } else { + ssp_config_max283x.data_bits = SSP_DATA_16BITS; + } ssp_config_max5864.gpio_select = gpio->max5864_select; @@ -935,11 +1020,13 @@ void pin_setup(void) spi_flash.gpio_wp = gpio->w25q80bv_wp; sgpio_config.gpio_q_invert = gpio->q_invert; -#if !defined(PRALINE) - sgpio_config.gpio_trigger_enable = gpio->trigger_enable; + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + sgpio_config.gpio_trigger_enable = gpio->trigger_enable; #endif + } -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; ice40.gpio_select = gpio->fpga_cfg_spi_cs; ice40.gpio_creset = gpio->fpga_cfg_creset; @@ -952,73 +1039,102 @@ void pin_setup(void) jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; #endif -#if defined(HACKRF_ONE) || defined(PRALINE) - jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; - jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; + jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; + jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; #endif + } + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; + jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; +#endif + break; + default: + break; + } ssp1_set_mode_max283x(); mixer_bus_setup(&mixer); -#if defined(HACKRF_ONE) if (detected_platform() == BOARD_ID_HACKRF1_R9) { - rf_path.gpio_rx = gpio->h1r9_rx; +#if defined(HACKRF_ONE) || defined(UNIVERSAL) sgpio_config.gpio_trigger_enable = gpio->h1r9_trigger_enable; - } #endif + } - /* Configure RF path */ -#if defined(HACKRF_ONE) - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_hp = gpio->hp, - .gpio_lp = gpio->lp, - .gpio_tx_mix_bp = gpio->tx_mix_bp, - .gpio_no_mix_bypass = gpio->no_mix_bypass, - .gpio_rx_mix_bp = gpio->rx_mix_bp, - .gpio_tx_amp = gpio->tx_amp, - .gpio_tx = gpio->tx, - .gpio_mix_bypass = gpio->mix_bypass, - .gpio_rx = gpio->rx, - .gpio_no_tx_amp_pwr = gpio->no_tx_amp_pwr, - .gpio_amp_bypass = gpio->amp_bypass, - .gpio_rx_amp = gpio->rx_amp, - .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, - }; - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - rf_path.gpio_rx = gpio->h1r9_rx; - rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; - } -#elif defined(RAD1O) - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_tx_rx_n = gpio->tx_rx_n, - .gpio_tx_rx = gpio->tx_rx, - .gpio_by_mix = gpio->by_mix, - .gpio_by_mix_n = gpio->by_mix_n, - .gpio_by_amp = gpio->by_amp, - .gpio_by_amp_n = gpio->by_amp_n, - .gpio_mixer_en = gpio->mixer_en, - .gpio_low_high_filt = gpio->low_high_filt, - .gpio_low_high_filt_n = gpio->low_high_filt_n, - .gpio_tx_amp = gpio->tx_amp, - .gpio_rx_lna = gpio->rx_lna, - }; -#elif defined(PRALINE) - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_tx_en = gpio->tx_en, - .gpio_mix_en_n = gpio->mix_en_n, - .gpio_lpf_en = gpio->lpf_en, - .gpio_rf_amp_en = gpio->rf_amp_en, - .gpio_ant_bias_en_n = gpio->ant_bias_en_n, - }; - board_rev_t rev = detected_revision(); - if ((rev == BOARD_REV_PRALINE_R1_0) || (rev == BOARD_REV_GSG_PRALINE_R1_0)) { - rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; - } + // initialize rf_path struct and assign gpio's + switch (board_id) { + case BOARD_ID_JAWBREAKER: + break; + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_hp = gpio->hp, + .gpio_lp = gpio->lp, + .gpio_tx_mix_bp = gpio->tx_mix_bp, + .gpio_no_mix_bypass = gpio->no_mix_bypass, + .gpio_rx_mix_bp = gpio->rx_mix_bp, + .gpio_tx_amp = gpio->tx_amp, + .gpio_tx = gpio->tx, + .gpio_mix_bypass = gpio->mix_bypass, + .gpio_rx = gpio->rx, + .gpio_no_tx_amp_pwr = gpio->no_tx_amp_pwr, + .gpio_amp_bypass = gpio->amp_bypass, + .gpio_rx_amp = gpio->rx_amp, + .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, + }; + if (board_id == BOARD_ID_HACKRF1_R9) { + rf_path.gpio_rx = gpio->h1r9_rx; + rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; + } #endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_tx_rx_n = gpio->tx_rx_n, + .gpio_tx_rx = gpio->tx_rx, + .gpio_by_mix = gpio->by_mix, + .gpio_by_mix_n = gpio->by_mix_n, + .gpio_by_amp = gpio->by_amp, + .gpio_by_amp_n = gpio->by_amp_n, + .gpio_mixer_en = gpio->mixer_en, + .gpio_low_high_filt = gpio->low_high_filt, + .gpio_low_high_filt_n = gpio->low_high_filt_n, + .gpio_tx_amp = gpio->tx_amp, + .gpio_rx_lna = gpio->rx_lna, + }; +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_tx_en = gpio->tx_en, + .gpio_mix_en_n = gpio->mix_en_n, + .gpio_lpf_en = gpio->lpf_en, + .gpio_rf_amp_en = gpio->rf_amp_en, + .gpio_ant_bias_en_n = gpio->ant_bias_en_n, + }; + if ((detected_revision() == BOARD_REV_PRALINE_R1_0) || + (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { + rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; + } +#endif + break; + default: + break; + } rf_path_pin_setup(&rf_path); /* Configure external clock in */ @@ -1027,52 +1143,86 @@ void pin_setup(void) sgpio_configure_pin_functions(&sgpio_config); } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void enable_1v2_power(void) { - gpio_set(platform_gpio()->gpio_1v2_enable); + switch (detected_platform()) { + case BOARD_ID_PRALINE: + gpio_set(platform_gpio()->gpio_1v2_enable); + break; + default: + break; + } } void disable_1v2_power(void) { - gpio_clear(platform_gpio()->gpio_1v2_enable); + switch (detected_platform()) { + case BOARD_ID_PRALINE: + gpio_clear(platform_gpio()->gpio_1v2_enable); + break; + default: + break; + } } void enable_3v3aux_power(void) { - gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); + switch (detected_platform()) { + case BOARD_ID_PRALINE: + gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); + break; + default: + break; + } } void disable_3v3aux_power(void) { - gpio_set(platform_gpio()->gpio_3v3aux_enable_n); + switch (detected_platform()) { + case BOARD_ID_PRALINE: + gpio_set(platform_gpio()->gpio_3v3aux_enable_n); + break; + default: + break; + } } -#else +#endif + void enable_1v8_power(void) { - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - #ifdef HACKRF_ONE + switch (detected_platform()) { + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) gpio_set(platform_gpio()->h1r9_1v8_enable); - #endif - } else { +#endif + break; + case BOARD_ID_PRALINE: + break; + default: gpio_set(platform_gpio()->gpio_1v8_enable); + break; } } void disable_1v8_power(void) { - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - #ifdef HACKRF_ONE + switch (detected_platform()) { + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) gpio_clear(platform_gpio()->h1r9_1v8_enable); - #endif - } else { +#endif + break; + case BOARD_ID_PRALINE: + break; + default: gpio_clear(platform_gpio()->gpio_1v8_enable); + break; } } -#endif -#ifdef HACKRF_ONE -void enable_rf_power(void) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +static inline void enable_rf_power_hackrf_one(void) { const platform_gpio_t* gpio = platform_gpio(); uint32_t i; @@ -1089,7 +1239,7 @@ void enable_rf_power(void) } } -void disable_rf_power(void) +static inline void disable_rf_power_hackrf_one(void) { if (detected_platform() == BOARD_ID_HACKRF1_R9) { gpio_set(platform_gpio()->h1r9_vaa_disable); @@ -1099,8 +1249,8 @@ void disable_rf_power(void) } #endif -#ifdef PRALINE -void enable_rf_power(void) +#if defined(PRALINE) || defined(UNIVERSAL) +static inline void enable_rf_power_praline(void) { gpio_clear(platform_gpio()->vaa_disable); @@ -1108,14 +1258,14 @@ void enable_rf_power(void) delay(1000000); } -void disable_rf_power(void) +static inline void disable_rf_power_praline(void) { gpio_set(platform_gpio()->vaa_disable); } #endif -#ifdef RAD1O -void enable_rf_power(void) +#if defined(RAD1O) +static inline void enable_rf_power_rad1o(void) { gpio_set(platform_gpio()->vaa_enable); @@ -1123,33 +1273,77 @@ void enable_rf_power(void) delay(1000000); } -void disable_rf_power(void) +static inline void disable_rf_power_rad1o(void) { gpio_clear(platform_gpio()->vaa_enable); } #endif -#ifdef PRALINE -void led_on(const led_t led) +void enable_rf_power(void) { - gpio_clear(platform_gpio()->led[led]); -} - -void led_off(const led_t led) -{ - gpio_set(platform_gpio()->led[led]); -} -#else -void led_on(const led_t led) -{ - gpio_set(platform_gpio()->led[led]); -} - -void led_off(const led_t led) -{ - gpio_clear(platform_gpio()->led[led]); -} + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + enable_rf_power_hackrf_one(); #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + enable_rf_power_praline(); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + enable_rf_power_rad1o(); +#endif + break; + default: + break; + } +} + +void disable_rf_power(void) +{ + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + disable_rf_power_hackrf_one(); +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + disable_rf_power_praline(); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + disable_rf_power_rad1o(); +#endif + break; + default: + break; + } +} + +void led_on(const led_t led) +{ + if (detected_platform() == BOARD_ID_PRALINE) { + gpio_clear(platform_gpio()->led[led]); + } else { + gpio_set(platform_gpio()->led[led]); + } +} + +void led_off(const led_t led) +{ + if (detected_platform() == BOARD_ID_PRALINE) { + gpio_set(platform_gpio()->led[led]); + } else { + gpio_clear(platform_gpio()->led[led]); + } +} void led_toggle(const led_t led) { @@ -1158,26 +1352,33 @@ void led_toggle(const led_t led) void set_leds(const uint8_t state) { + board_id_t board_id = detected_platform(); + int num_leds = 3; -#if (defined RAD1O || defined PRALINE) - num_leds = 4; -#endif + if (board_id == BOARD_ID_RAD1O || board_id == BOARD_ID_PRALINE) { + num_leds = 4; + } + for (int i = 0; i < num_leds; i++) { -#ifdef PRALINE - gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); -#else - gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); -#endif + if (board_id == BOARD_ID_PRALINE) { + gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); + } else { + gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); + } } } void trigger_enable(const bool enable) { -#ifndef PRALINE - gpio_write(sgpio_config.gpio_trigger_enable, enable); -#else - fpga_set_trigger_enable(&fpga, enable); + if (detected_platform() != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio_write(sgpio_config.gpio_trigger_enable, enable); #endif + } else { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_trigger_enable(&fpga, enable); +#endif + } } void halt_and_flash(const uint32_t duration) @@ -1195,7 +1396,7 @@ void halt_and_flash(const uint32_t duration) } } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void p1_ctrl_set(const p1_ctrl_signal_t signal) { const platform_gpio_t* gpio = platform_gpio(); diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 539ce770..93bc44c9 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Great Scott Gadgets + * Copyright 2012-2026 Great Scott Gadgets * Copyright 2012 Benjamin Vernoux * Copyright 2012 Jared Boone * @@ -42,7 +42,7 @@ extern "C" { #include "si5351c.h" #include "spi_ssp.h" #include "w25q80bv.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "fpga.h" #include "ice40_spi.h" #endif @@ -51,7 +51,8 @@ extern "C" { extern si5351c_driver_t clock_gen; extern ssp_config_t ssp_config_w25q80bv; -#ifdef PRALINE +extern max283x_driver_t max283x; +#if defined(PRALINE) || defined(UNIVERSAL) extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif @@ -70,28 +71,27 @@ void clock_gen_init(void); void clock_gen_shutdown(void); void ssp1_set_mode_max283x(void); void ssp1_set_mode_max5864(void); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void ssp1_set_mode_ice40(void); #endif void pin_shutdown(void); void pin_setup(void); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void enable_1v2_power(void); void disable_1v2_power(void); void enable_3v3aux_power(void); void disable_3v3aux_power(void); -#else +#endif void enable_1v8_power(void); void disable_1v8_power(void); -#endif fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program); clock_source_t activate_best_clock_source(void); -#if (defined HACKRF_ONE || defined RAD1O || defined PRALINE) +#if defined(RAD1O) || defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) void enable_rf_power(void); void disable_rf_power(void); #endif @@ -112,7 +112,7 @@ void trigger_enable(const bool enable); void halt_and_flash(const uint32_t duration); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) typedef enum { P1_SIGNAL_TRIGGER_IN = 0, P1_SIGNAL_AUX_CLK1 = 1, diff --git a/firmware/common/selftest.h b/firmware/common/selftest.h index 28e02415..8223a582 100644 --- a/firmware/common/selftest.h +++ b/firmware/common/selftest.h @@ -37,23 +37,22 @@ typedef uint8_t test_result_t; typedef struct { uint16_t mixer_id; -#ifndef RAD1O +#if !defined(RAD1O) bool mixer_locks[NUM_LOCK_ATTEMPTS]; #endif -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) uint16_t max2831_mux_rssi_1; uint16_t max2831_mux_temp; uint16_t max2831_mux_rssi_2; bool max2831_mux_test_ok; -#else +#endif uint16_t max283x_readback_bad_value; uint16_t max283x_readback_expected_value; uint8_t max283x_readback_register_count; uint8_t max283x_readback_total_registers; -#endif uint8_t si5351_rev_id; bool si5351_readback_ok; -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) test_result_t fpga_image_load; test_result_t fpga_spi; test_result_t sgpio_rx; From e34393a1dd15de46a3558a04603441a21bb2aa16 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 01:12:34 +0100 Subject: [PATCH 019/161] Universalize: firmware/hackrf_usb Co-authored-by: Antoine van Gelder --- firmware/hackrf_usb/hackrf_usb.c | 190 ++++++++++++++-------- firmware/hackrf_usb/usb_api_board_info.c | 15 +- firmware/hackrf_usb/usb_api_cpld.c | 10 ++ firmware/hackrf_usb/usb_api_praline.c | 21 +++ firmware/hackrf_usb/usb_api_register.h | 2 +- firmware/hackrf_usb/usb_api_selftest.c | 170 ++++++++++--------- firmware/hackrf_usb/usb_api_sweep.c | 19 ++- firmware/hackrf_usb/usb_api_transceiver.c | 11 ++ firmware/hackrf_usb/usb_descriptor.c | 132 +++++++++++---- firmware/hackrf_usb/usb_descriptor.h | 28 +++- firmware/hackrf_usb/usb_device.c | 43 ++++- firmware/hackrf_usb/usb_device.h | 13 ++ 12 files changed, 451 insertions(+), 203 deletions(-) diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 0f21e199..fb441339 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -46,23 +46,22 @@ #include #include #include -#if !defined(RAD1O) - #include -#endif -#if defined(PRALINE) || defined(HACKRF_ONE) +#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) #include #endif -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(JAWBREAKER) +#if !defined(RAD1O) + #include #include #endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include #if !(defined(DFU_MODE) || defined(RAM_MODE)) #include #include #include #endif -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) #include #include #endif @@ -80,9 +79,10 @@ #include "usb_descriptor.h" #include "usb_device.h" #include "usb_endpoint.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "usb_api_praline.h" -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) #include "usb_api_cpld.h" #endif @@ -100,12 +100,12 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_si5351c, usb_vendor_request_set_sample_rate_frac, usb_vendor_request_set_baseband_filter_bandwidth, -#ifdef RAD1O - NULL, // write_rffc5071 not used - NULL, // read_rffc5071 not used -#else +#if !defined(RAD1O) usb_vendor_request_write_rffc5071, usb_vendor_request_read_rffc5071, +#else + NULL, // write_rffc5071 not used + NULL, // read_rffc5071 not used #endif usb_vendor_request_erase_spiflash, usb_vendor_request_write_spiflash, @@ -120,7 +120,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_set_vga_gain, usb_vendor_request_set_txvga_gain, NULL, // was set_if_freq -#if (defined HACKRF_ONE || defined PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) usb_vendor_request_set_antenna_enable, #else NULL, @@ -137,7 +137,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_spiflash_status, usb_vendor_request_spiflash_clear_status, usb_vendor_request_operacake_gpio_test, -#ifdef HACKRF_ONE +#if defined(HACKRF_ONE) || defined(UNIVERSAL) usb_vendor_request_cpld_checksum, #else NULL, @@ -154,7 +154,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_supported_platform, usb_vendor_request_set_leds, usb_vendor_request_user_config_set_bias_t_opts, -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) usb_vendor_request_write_fpga_reg, usb_vendor_request_read_fpga_reg, usb_vendor_request_p2_ctrl, @@ -250,7 +250,7 @@ void usb_set_descriptor_by_serial_number(void) } } -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) static bool cpld_jtag_sram_load(jtag_t* const jtag) { cpld_jtag_take(jtag); @@ -278,7 +278,7 @@ static void m0_rom_to_ram(void) memcpy(dest, (uint32_t*) (base + src), len); } -#if defined(PRALINE) && !(defined(DFU_MODE) || defined(RAM_MODE)) +#if (defined(PRALINE) || defined(UNIVERSAL)) && !(defined(DFU_MODE) || defined(RAM_MODE)) extern uint32_t _binary_fpga_bin_start; void fpga_loader_setup(void) @@ -311,73 +311,88 @@ int main(void) // Detect hardware platform before we do anything else. detect_hardware_platform(); + board_id_t board_id = detected_platform(); pin_shutdown(); -#ifndef RAD1O - clock_gen_shutdown(); -#endif + if (board_id != BOARD_ID_RAD1O) { + clock_gen_shutdown(); + } delay_us_at_mhz(10000, 96); pin_setup(); -#ifndef PRALINE - enable_1v8_power(); - #ifndef RAD1O - /* - * On rad1o, the clock generator power supply comes from the RF supply - * which is enabled later. On H1 and Jawbreaker, the clock generator is - * on the main 3V3 supply. - */ - clock_gen_init(); - #endif -#else - enable_3v3aux_power(); + if (board_id != BOARD_ID_PRALINE) { + enable_1v8_power(); + if (board_id != BOARD_ID_RAD1O) { + /* + * On rad1o, the clock generator power supply comes from the RF supply + * which is enabled later. On H1 and Jawbreaker, the clock generator is + * on the main 3V3 supply. + */ + clock_gen_init(); + } + } else { +#if defined(PRALINE) || defined(UNIVERSAL) + enable_3v3aux_power(); #if !defined(DFU_MODE) && !defined(RAM_MODE) - enable_1v2_power(); - enable_rf_power(); - /* - * On Praline, the clock generator power supply comes from 3V3FPGA - * which is enabled when 1V2FPGA is turned on. - */ - clock_gen_init(); + enable_1v2_power(); + enable_rf_power(); + /* + * On Praline, the clock generator power supply comes from 3V3FPGA + * which is enabled when 1V2FPGA is turned on. + */ + clock_gen_init(); #endif #endif + } tuning_setup(); -#ifdef HACKRF_ONE - // Set up mixer before enabling RF power, because its - // GPO is used to control the antenna bias tee. - mixer_setup(&mixer, RFFC5071_VARIANT); + if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + // Set up mixer before enabling RF power, because its + // GPO is used to control the antenna bias tee. + mixer_setup(&mixer, RFFC5071_VARIANT); #endif -#if (defined HACKRF_ONE || defined RAD1O) - enable_rf_power(); -#endif -#ifdef RAD1O - clock_gen_init(); + } + if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9 || + board_id == BOARD_ID_RAD1O) { +#if defined(RAD1O) || defined(HACKRF_ONE) || defined(UNIVERSAL) + enable_rf_power(); #endif + } + if (board_id == BOARD_ID_RAD1O) { + clock_gen_init(); + } cpu_clock_init(); /* Wake the M0 */ ipc_halt_m0(); ipc_start_m0((uint32_t) &__ram_m0_start__); -#ifndef PRALINE - if (!cpld_jtag_sram_load(&jtag_cpld)) { - halt_and_flash(6000000); - } -#else - #if defined(DFU_MODE) || defined(RAM_MODE) - selftest.fpga_image_load = SKIPPED; - selftest.report.pass = false; - #else - fpga_image_load(&fpga_loader, 0); - #endif - delay_us_at_mhz(100, 204); - fpga_spi_selftest(); - fpga_sgpio_selftest(); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + if (!cpld_jtag_sram_load(&jtag_cpld)) { + halt_and_flash(6000000); + } #endif + } else { +#if defined(PRALINE) || defined(UNIVERSAL) + #if defined(DFU_MODE) || defined(RAM_MODE) + selftest.fpga_image_load = SKIPPED; + selftest.report.pass = false; + #else + fpga_image_load(&fpga_loader, 0); + #endif + delay_us_at_mhz(100, 204); + fpga_spi_selftest(); + fpga_sgpio_selftest(); +#endif + } radio_init(&radio); -#if (defined HACKRF_ONE || defined PRALINE) - portapack_init(); + if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9 || + board_id == BOARD_ID_PRALINE) { +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + portapack_init(); #endif + } #ifndef DFU_MODE usb_set_descriptor_by_serial_number(); @@ -386,6 +401,35 @@ int main(void) usb_set_configuration_changed_cb(usb_configuration_changed); usb_peripheral_reset(); + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + memcpy(&usb_device, + &usb_device_hackrf_one, + sizeof(usb_device_hackrf_one)); +#endif + break; + case BOARD_ID_JAWBREAKER: +#if defined(JAWBREAKER) + memcpy(&usb_device, + &usb_device_jawbreaker, + sizeof(usb_device_jawbreaker)); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + memcpy(&usb_device, &usb_device_rad1o, sizeof(usb_device_rad1o)); +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + memcpy(&usb_device, &usb_device_praline, sizeof(usb_device_praline)); +#endif + break; + default: + break; + }; usb_device_init(0, &usb_device); usb_queue_init(&usb_endpoint_control_out_queue); @@ -404,13 +448,15 @@ int main(void) rf_path_init(&rf_path); -#ifndef RAD1O +#if !defined(RAD1O) rffc5071_lock_test(&mixer.rffc5071); #endif -#ifdef PRALINE - fpga_if_xcvr_selftest(); + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_if_xcvr_selftest(); #endif + } if (da7219_detect()) { operacake_skip_i2c_address(DA7219_ADDRESS); @@ -456,11 +502,13 @@ int main(void) case TRANSCEIVER_MODE_RX_SWEEP: sweep_mode(request.seq); break; -#ifndef PRALINE case TRANSCEIVER_MODE_CPLD_UPDATE: - cpld_update(); - break; + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + cpld_update(); #endif + } + break; default: break; } diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index a1ad3bce..8e64ad7f 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -34,7 +34,7 @@ #include #include #include -#ifdef HACKRF_ONE +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include #include #endif @@ -131,18 +131,19 @@ usb_request_status_t usb_vendor_request_reset( if (stage == USB_TRANSFER_STAGE_SETUP) { pin_shutdown(); clock_gen_shutdown(); -#ifdef HACKRF_ONE - /* - * Set boot pins as inputs so that the bootloader reads them - * correctly after the reset. - */ if (detected_platform() == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + /* + * Set boot pins as inputs so that the bootloader reads them + * correctly after the reset. + */ const platform_gpio_t* gpio = platform_gpio(); gpio_input(gpio->h1r9_mcu_clk_en); gpio_input(gpio->h1r9_clkout_en); gpio_input(gpio->h1r9_rx); - } #endif + } + wwdt_reset(100000); usb_transfer_schedule_ack(endpoint->in); diff --git a/firmware/hackrf_usb/usb_api_cpld.c b/firmware/hackrf_usb/usb_api_cpld.c index 7d8200c3..05636a0b 100644 --- a/firmware/hackrf_usb/usb_api_cpld.c +++ b/firmware/hackrf_usb/usb_api_cpld.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,15 @@ usb_request_status_t usb_vendor_request_cpld_checksum( static uint32_t cpld_crc; uint8_t length; + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + // supported + break; + default: + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { cpld_jtag_take(&jtag_cpld); const bool checksum_success = cpld_xc2c64a_jtag_checksum( diff --git a/firmware/hackrf_usb/usb_api_praline.c b/firmware/hackrf_usb/usb_api_praline.c index 6a44da60..2a41b839 100644 --- a/firmware/hackrf_usb/usb_api_praline.c +++ b/firmware/hackrf_usb/usb_api_praline.c @@ -24,6 +24,7 @@ #include "usb_api_praline.h" #include +#include #include #include #include @@ -35,6 +36,10 @@ usb_request_status_t usb_vendor_request_p1_ctrl( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { p1_ctrl_set(endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -46,6 +51,10 @@ usb_request_status_t usb_vendor_request_p2_ctrl( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { p2_ctrl_set(endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -57,6 +66,10 @@ usb_request_status_t usb_vendor_request_clkin_ctrl( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { clkin_ctrl_set(endpoint->setup.value & 1); usb_transfer_schedule_ack(endpoint->in); @@ -68,6 +81,10 @@ usb_request_status_t usb_vendor_request_set_narrowband_filter( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { narrowband_filter_set(endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -86,6 +103,10 @@ usb_request_status_t usb_vendor_request_set_fpga_bitstream( #else extern struct fpga_loader_t fpga_loader; + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { if (!fpga_image_load(&fpga_loader, endpoint->setup.value)) { return USB_REQUEST_STATUS_STALL; diff --git a/firmware/hackrf_usb/usb_api_register.h b/firmware/hackrf_usb/usb_api_register.h index 24825144..58a84aff 100644 --- a/firmware/hackrf_usb/usb_api_register.h +++ b/firmware/hackrf_usb/usb_api_register.h @@ -56,7 +56,7 @@ usb_request_status_t usb_vendor_request_set_leds( usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index 4fe2e628..fa5267b5 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -21,20 +21,18 @@ #include #include -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(JAWBREAKER) +#if !defined(RAD1O) #include #endif #include #include +#include #include #include #include #include -#if !defined(PRALINE) - #include -#endif #include "usb_api_selftest.h" @@ -63,7 +61,7 @@ void append(char** dest, size_t* capacity, const char* str) } } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) static const char* test_result_to_str(test_result_t result) { switch (result) { @@ -82,91 +80,105 @@ static const char* test_result_to_str(test_result_t result) void generate_selftest_report(void) { + board_id_t board_id = detected_platform(); + char* s = &selftest.report.msg[0]; size_t c = sizeof(selftest.report.msg); -#ifdef RAD1O - append(&s, &c, "Mixer: MAX2871, ID: "); - append(&s, &c, itoa(selftest.mixer_id, 10)); - append(&s, &c, "\n"); -#else - append(&s, &c, "Mixer: RFFC5072, ID: "); - append(&s, &c, itoa(selftest.mixer_id >> 3, 10)); - append(&s, &c, ", Rev: "); - append(&s, &c, itoa(selftest.mixer_id & 0x7, 10)); - append(&s, &c, ", Locks: "); - bool lock; - for (int i = 0; i < NUM_LOCK_ATTEMPTS; i++) { - lock = selftest.mixer_locks[i]; - append(&s, &c, itoa(lock, 2)); - } - if (lock) { - append(&s, &c, " (PASS)"); - } else { - append(&s, &c, " (FAIL)"); - } - append(&s, &c, "\n"); + if (board_id == BOARD_ID_RAD1O) { +#if defined(RAD1O) + append(&s, &c, "Mixer: MAX2871, ID: "); + append(&s, &c, itoa(selftest.mixer_id, 10)); + append(&s, &c, "\n"); #endif + } else { +#if !defined(RAD1O) + append(&s, &c, "Mixer: RFFC5072, ID: "); + append(&s, &c, itoa(selftest.mixer_id >> 3, 10)); + append(&s, &c, ", Rev: "); + append(&s, &c, itoa(selftest.mixer_id & 0x7, 10)); + append(&s, &c, ", Locks: "); + bool lock; + for (int i = 0; i < NUM_LOCK_ATTEMPTS; i++) { + lock = selftest.mixer_locks[i]; + append(&s, &c, itoa(lock, 2)); + } + if (lock) { + append(&s, &c, " (PASS)"); + } else { + append(&s, &c, " (FAIL)"); + } + append(&s, &c, "\n"); +#endif + } + append(&s, &c, "Clock: Si5351"); append(&s, &c, ", Rev: "); append(&s, &c, itoa(selftest.si5351_rev_id, 10)); append(&s, &c, ", readback: "); append(&s, &c, selftest.si5351_readback_ok ? "OK" : "FAIL"); append(&s, &c, "\n"); -#ifdef PRALINE - append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); - append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); - append(&s, &c, "\n"); -#else - append(&s, &c, "Transceiver: "); - append(&s, - &c, - (detected_platform() == BOARD_ID_HACKRF1_R9 ? "MAX2839" : "MAX2837")); - append(&s, &c, ", readback success: "); - append(&s, &c, itoa(selftest.max283x_readback_register_count, 10)); - append(&s, &c, "/"); - append(&s, &c, itoa(selftest.max283x_readback_total_registers, 10)); - if (selftest.max283x_readback_register_count < - selftest.max283x_readback_total_registers) { - append(&s, &c, ", bad value: 0x"); - append(&s, &c, itoa(selftest.max283x_readback_bad_value, 10)); - append(&s, &c, ", expected: 0x"); - append(&s, &c, itoa(selftest.max283x_readback_expected_value, 10)); - } - append(&s, &c, "\n"); -#endif -#ifdef PRALINE - append(&s, &c, "FPGA configuration: "); - append(&s, &c, test_result_to_str(selftest.fpga_image_load)); - append(&s, &c, "\n"); - append(&s, &c, "FPGA SPI: "); - append(&s, &c, test_result_to_str(selftest.fpga_spi)); - append(&s, &c, "\n"); - append(&s, &c, "SGPIO RX test: "); - append(&s, &c, test_result_to_str(selftest.sgpio_rx)); - append(&s, &c, "\n"); - append(&s, &c, "Loopback test: "); - append(&s, &c, test_result_to_str(selftest.xcvr_loopback)); - append(&s, &c, "\n"); - // Dump transceiver loopback measurements. - for (int i = 0; i < 4; ++i) { - struct xcvr_measurements* m = &selftest.xcvr_measurements[i]; - append(&s, &c, " "); - append(&s, &c, itoa(i, 10)); - append(&s, &c, ":"); - append(&s, &c, itoa(m->zcs_i, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->zcs_q, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->max_mag_i, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->max_mag_q, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->avg_mag_sq_i, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->avg_mag_sq_q, 10)); + + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); + append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); append(&s, &c, "\n"); - } #endif + } else { +#if !defined(PRALINE) || defined(UNIVERSAL) + append(&s, &c, "Transceiver: "); + append(&s, &c, (board_id == BOARD_ID_HACKRF1_R9 ? "MAX2839" : "MAX2837")); + append(&s, &c, ", readback success: "); + append(&s, &c, itoa(selftest.max283x_readback_register_count, 10)); + append(&s, &c, "/"); + append(&s, &c, itoa(selftest.max283x_readback_total_registers, 10)); + if (selftest.max283x_readback_register_count < + selftest.max283x_readback_total_registers) { + append(&s, &c, ", bad value: 0x"); + append(&s, &c, itoa(selftest.max283x_readback_bad_value, 10)); + append(&s, &c, ", expected: 0x"); + append(&s, + &c, + itoa(selftest.max283x_readback_expected_value, 10)); + } + append(&s, &c, "\n"); +#endif + } + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + append(&s, &c, "FPGA configuration: "); + append(&s, &c, test_result_to_str(selftest.fpga_image_load)); + append(&s, &c, "\n"); + append(&s, &c, "FPGA SPI: "); + append(&s, &c, test_result_to_str(selftest.fpga_spi)); + append(&s, &c, "\n"); + append(&s, &c, "SGPIO RX test: "); + append(&s, &c, test_result_to_str(selftest.sgpio_rx)); + append(&s, &c, "\n"); + append(&s, &c, "Loopback test: "); + append(&s, &c, test_result_to_str(selftest.xcvr_loopback)); + append(&s, &c, "\n"); + // Dump transceiver loopback measurements. + for (int i = 0; i < 4; ++i) { + struct xcvr_measurements* m = &selftest.xcvr_measurements[i]; + append(&s, &c, " "); + append(&s, &c, itoa(i, 10)); + append(&s, &c, ":"); + append(&s, &c, itoa(m->zcs_i, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->zcs_q, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->max_mag_i, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->max_mag_q, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->avg_mag_sq_i, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->avg_mag_sq_q, 10)); + append(&s, &c, "\n"); + } +#endif + } } usb_request_status_t usb_vendor_request_read_selftest( diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index a9bfc695..4f7d8e57 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -46,11 +47,6 @@ #define MAX(x, y) ((x) > (y) ? (x) : (y)) #define FREQ_GRANULARITY 1000000 #define MAX_RANGES 10 -#ifndef PRALINE - #define THROWAWAY_BUFFERS 2 -#else - #define THROWAWAY_BUFFERS 1 -#endif static uint64_t sweep_freq; static uint16_t frequencies[MAX_RANGES * 2]; @@ -59,6 +55,7 @@ static uint16_t num_ranges = 0; static uint32_t dwell_blocks = 0; static uint32_t step_width = 0; static uint32_t offset = 0; +static uint32_t throwaway_buffers = 0; static enum sweep_style style = LINEAR; static bool freq_ui_dirty = false; static bool img_reject_ui_dirty = false; @@ -91,6 +88,12 @@ usb_request_status_t usb_vendor_request_init_sweep( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() == BOARD_ID_PRALINE) { + throwaway_buffers = 1; + } else { + throwaway_buffers = 2; + } + uint32_t num_bytes; int i; if (stage == USB_TRANSFER_STAGE_SETUP) { @@ -144,7 +147,7 @@ void sweep_bulk_transfer_complete(void* user_data, unsigned int bytes_transferre // For each buffer transferred, we need to bump the count to allow // for the buffer(s) that are to be discarded. - m0_state.m4_count += (THROWAWAY_BUFFERS + 1) * 0x4000; + m0_state.m4_count += (throwaway_buffers + 1) * 0x4000; } void sweep_mode(uint32_t seq) @@ -200,7 +203,7 @@ void sweep_mode(uint32_t seq) // Set M0 to switch back to RX after we have received our // discard buffers. - m0_state.threshold += (0x4000 * THROWAWAY_BUFFERS); + m0_state.threshold += (0x4000 * throwaway_buffers); m0_state.next_mode = M0_MODE_RX; // Write metadata to buffer. @@ -225,7 +228,7 @@ void sweep_mode(uint32_t seq) NULL); // Use other buffer next time. - phase = (phase + 1) % THROWAWAY_BUFFERS; + phase = (phase + 1) % throwaway_buffers; if (++blocks_queued == dwell_blocks) { // Calculate next sweep frequency. diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index d4b47f9f..30d7ab7e 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -287,6 +288,16 @@ usb_request_status_t usb_vendor_request_set_antenna_enable( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: + // supported + break; + default: + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { radio_reg_write( &radio, diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index 7c98f0ca..f213f484 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -51,7 +51,8 @@ #define USB_STRING_LANGID (0x0409) -uint8_t usb_descriptor_device[] = { +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +uint8_t usb_descriptor_device_hackrf[] = { 18, // bLength USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType USB_WORD(0x0200), // bcdUSB @@ -60,13 +61,50 @@ uint8_t usb_descriptor_device[] = { 0x00, // bDeviceProtocol USB_MAX_PACKET0, // bMaxPacketSize0 USB_WORD(USB_VENDOR_ID), // idVendor - USB_WORD(USB_PRODUCT_ID), // idProduct + USB_WORD(0x6089), // idProduct USB_WORD(USB_API_VERSION), // bcdDevice 0x01, // iManufacturer 0x02, // iProduct 0x04, // iSerialNumber 0x01 // bNumConfigurations }; +#endif +#if defined(JAWBREAKER) +uint8_t usb_descriptor_device_jawbreaker[] = { + 18, // bLength + USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType + USB_WORD(0x0200), // bcdUSB + 0x00, // bDeviceClass + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + USB_MAX_PACKET0, // bMaxPacketSize0 + USB_WORD(USB_VENDOR_ID), // idVendor + USB_WORD(0x604B), // idProduct + USB_WORD(USB_API_VERSION), // bcdDevice + 0x01, // iManufacturer + 0x02, // iProduct + 0x04, // iSerialNumber + 0x01 // bNumConfigurations +}; +#endif +#if defined(RAD1O) +uint8_t usb_descriptor_device_rad1o[] = { + 18, // bLength + USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType + USB_WORD(0x0200), // bcdUSB + 0x00, // bDeviceClass + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + USB_MAX_PACKET0, // bMaxPacketSize0 + USB_WORD(USB_VENDOR_ID), // idVendor + USB_WORD(0xCC15), // idProduct + USB_WORD(USB_API_VERSION), // bcdDevice + 0x01, // iManufacturer + 0x02, // iProduct + 0x04, // iSerialNumber + 0x01 // bNumConfigurations +}; +#endif uint8_t usb_descriptor_device_qualifier[] = { 10, // bLength @@ -185,8 +223,8 @@ uint8_t usb_descriptor_string_manufacturer[] = { 's', 0x00, }; -uint8_t usb_descriptor_string_product[] = { -#ifdef HACKRF_ONE +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +uint8_t usb_descriptor_string_product_hackrf_one[] = { 22, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType 'H', 0x00, @@ -199,7 +237,26 @@ uint8_t usb_descriptor_string_product[] = { 'O', 0x00, 'n', 0x00, 'e', 0x00, -#elif JAWBREAKER +}; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +uint8_t usb_descriptor_string_product_praline[] = { + 22, // bLength + USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType + 'H', 0x00, + 'a', 0x00, + 'c', 0x00, + 'k', 0x00, + 'R', 0x00, + 'F', 0x00, + ' ', 0x00, + 'P', 0x00, + 'r', 0x00, + 'o', 0x00, +}; +#endif +#if defined(JAWBREAKER) +uint8_t usb_descriptor_string_product_jawbreaker[] = { 36, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType 'H', 0x00, @@ -219,7 +276,10 @@ uint8_t usb_descriptor_string_product[] = { 'k', 0x00, 'e', 0x00, 'r', 0x00, -#elif RAD1O +}; +#endif +#if defined(RAD1O) +uint8_t usb_descriptor_string_product_rad1o[] = { 12, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType 'r', 0x00, @@ -227,30 +287,8 @@ uint8_t usb_descriptor_string_product[] = { 'd', 0x00, '1', 0x00, 'o', 0x00, -#elif PRALINE - 22, // bLength - USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType - 'H', 0x00, - 'a', 0x00, - 'c', 0x00, - 'k', 0x00, - 'R', 0x00, - 'F', 0x00, - ' ', 0x00, - 'P', 0x00, - 'r', 0x00, - 'o', 0x00, -#else - 14, // bLength - USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType - 'H', 0x00, - 'a', 0x00, - 'c', 0x00, - 'k', 0x00, - 'R', 0x00, - 'F', 0x00, -#endif }; +#endif uint8_t usb_descriptor_string_config_description[] = { 24, // bLength @@ -291,14 +329,46 @@ uint8_t usb_descriptor_string_serial_number[] = { uint8_t usb_descriptor_string_serial_number[USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN]; #endif -uint8_t* usb_descriptor_strings[] = { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +uint8_t* usb_descriptor_strings_hackrf_one[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, - usb_descriptor_string_product, + usb_descriptor_string_product_hackrf_one, usb_descriptor_string_config_description, usb_descriptor_string_serial_number, 0, // TERMINATOR }; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +uint8_t* usb_descriptor_strings_praline[] = { + usb_descriptor_string_languages, + usb_descriptor_string_manufacturer, + usb_descriptor_string_product_praline, + usb_descriptor_string_config_description, + usb_descriptor_string_serial_number, + 0, // TERMINATOR +}; +#endif +#if defined(JAWBREAKER) +uint8_t* usb_descriptor_strings_jawbreaker[] = { + usb_descriptor_string_languages, + usb_descriptor_string_manufacturer, + usb_descriptor_string_product_jawbreaker, + usb_descriptor_string_config_description, + usb_descriptor_string_serial_number, + 0, // TERMINATOR +}; +#endif +#if defined(RAD1O) +uint8_t* usb_descriptor_strings_rad1o[] = { + usb_descriptor_string_languages, + usb_descriptor_string_manufacturer, + usb_descriptor_string_product_rad1o, + usb_descriptor_string_config_description, + usb_descriptor_string_serial_number, + 0, // TERMINATOR +}; +#endif uint8_t wcid_string_descriptor[] = { 18, // bLength diff --git a/firmware/hackrf_usb/usb_descriptor.h b/firmware/hackrf_usb/usb_descriptor.h index 097ccce0..93f3eb78 100644 --- a/firmware/hackrf_usb/usb_descriptor.h +++ b/firmware/hackrf_usb/usb_descriptor.h @@ -24,20 +24,42 @@ #include -extern uint8_t usb_descriptor_device[]; +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +extern uint8_t usb_descriptor_device_hackrf[]; +#endif +#if defined(JAWBREAKER) +extern uint8_t usb_descriptor_device_jawbreaker[]; +#endif +#if defined(RAD1O) +extern uint8_t usb_descriptor_device_rad1o[]; +#endif extern uint8_t usb_descriptor_device_qualifier[]; extern uint8_t usb_descriptor_configuration_full_speed[]; extern uint8_t usb_descriptor_configuration_high_speed[]; extern uint8_t usb_descriptor_string_languages[]; extern uint8_t usb_descriptor_string_manufacturer[]; -extern uint8_t usb_descriptor_string_product[]; +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +extern uint8_t usb_descriptor_string_product_hackrf_one[]; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +extern uint8_t usb_descriptor_string_product_praline[]; +#endif +#if defined(JAWBREAKER) +extern uint8_t usb_descriptor_string_product_jawbreaker[]; +#endif +#if defined(RAD1O) +extern uint8_t usb_descriptor_string_product_rad1o[]; +#endif #define USB_DESCRIPTOR_STRING_SERIAL_LEN 32 #define USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN \ (USB_DESCRIPTOR_STRING_SERIAL_LEN * 2 + 2) /* UTF-16LE */ extern uint8_t usb_descriptor_string_serial_number[]; -extern uint8_t* usb_descriptor_strings[]; +extern uint8_t* usb_descriptor_strings_hackrf_one[]; +extern uint8_t* usb_descriptor_strings_jawbreaker[]; +extern uint8_t* usb_descriptor_strings_rad1o[]; +extern uint8_t* usb_descriptor_strings_praline[]; #define USB_WCID_VENDOR_REQ 0x19 extern uint8_t wcid_string_descriptor[]; diff --git a/firmware/hackrf_usb/usb_device.c b/firmware/hackrf_usb/usb_device.c index b689ce9f..4ad46424 100644 --- a/firmware/hackrf_usb/usb_device.c +++ b/firmware/hackrf_usb/usb_device.c @@ -45,12 +45,49 @@ usb_configuration_t* usb_configurations[] = { 0, }; -usb_device_t usb_device = { - .descriptor = usb_descriptor_device, - .descriptor_strings = usb_descriptor_strings, +usb_device_t usb_device; + +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +const usb_device_t usb_device_hackrf_one = { + .descriptor = usb_descriptor_device_hackrf, + .descriptor_strings = usb_descriptor_strings_hackrf_one, .qualifier_descriptor = usb_descriptor_device_qualifier, .configurations = &usb_configurations, .configuration = 0, .wcid_string_descriptor = wcid_string_descriptor, .wcid_feature_descriptor = wcid_feature_descriptor, }; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +const usb_device_t usb_device_praline = { + .descriptor = usb_descriptor_device_hackrf, + .descriptor_strings = usb_descriptor_strings_praline, + .qualifier_descriptor = usb_descriptor_device_qualifier, + .configurations = &usb_configurations, + .configuration = 0, + .wcid_string_descriptor = wcid_string_descriptor, + .wcid_feature_descriptor = wcid_feature_descriptor, +}; +#endif +#if defined(JAWBREAKER) +const usb_device_t usb_device_jawbreaker = { + .descriptor = usb_descriptor_device_jawbreaker, + .descriptor_strings = usb_descriptor_strings_jawbreaker, + .qualifier_descriptor = usb_descriptor_device_qualifier, + .configurations = &usb_configurations, + .configuration = 0, + .wcid_string_descriptor = wcid_string_descriptor, + .wcid_feature_descriptor = wcid_feature_descriptor, +}; +#endif +#if defined(RAD1O) +const usb_device_t usb_device_rad1o = { + .descriptor = usb_descriptor_device_rad1o, + .descriptor_strings = usb_descriptor_strings_rad1o, + .qualifier_descriptor = usb_descriptor_device_qualifier, + .configurations = &usb_configurations, + .configuration = 0, + .wcid_string_descriptor = wcid_string_descriptor, + .wcid_feature_descriptor = wcid_feature_descriptor, +}; +#endif diff --git a/firmware/hackrf_usb/usb_device.h b/firmware/hackrf_usb/usb_device.h index 79df298d..528d084d 100644 --- a/firmware/hackrf_usb/usb_device.h +++ b/firmware/hackrf_usb/usb_device.h @@ -26,3 +26,16 @@ #include extern usb_device_t usb_device; + +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +extern const usb_device_t usb_device_hackrf_one; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +extern const usb_device_t usb_device_praline; +#endif +#if defined(JAWBREAKER) +extern const usb_device_t usb_device_jawbreaker; +#endif +#if defined(RAD1O) +extern const usb_device_t usb_device_rad1o; +#endif From 509249fe74302663c29095fc2b3354a9e01868f0 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 01:13:09 +0100 Subject: [PATCH 020/161] Add UNIVERSAL board target. --- firmware/common/firmware_info.c | 3 +++ firmware/hackrf-common.cmake | 21 +++++++++++++++++---- firmware/hackrf_usb/CMakeLists.txt | 24 ++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/firmware/common/firmware_info.c b/firmware/common/firmware_info.c index 1ac02074..527dd19b 100644 --- a/firmware/common/firmware_info.c +++ b/firmware/common/firmware_info.c @@ -30,6 +30,9 @@ #define SUPPORTED_PLATFORM PLATFORM_RAD1O #elif PRALINE #define SUPPORTED_PLATFORM PLATFORM_PRALINE +#elif UNIVERSAL + #define SUPPORTED_PLATFORM \ + (PLATFORM_PRALINE | PLATFORM_HACKRF1_OG | PLATFORM_HACKRF1_R9) #else #define SUPPORTED_PLATFORM 0 #endif diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 26a24d6e..9a349b1e 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -74,7 +74,7 @@ if(NOT DEFINED BOARD) set(BOARD HACKRF_ONE) endif() -if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE") +if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE" OR BOARD STREQUAL "UNIVERSAL") set(MCU_PARTNO LPC4320) else() set(MCU_PARTNO LPC4330) @@ -195,7 +195,8 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/m0_state.c ${PATH_HACKRF_FIRMWARE_COMMON}/adc.c ${PATH_HACKRF_FIRMWARE_COMMON}/da7219.c - ) + ${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c + ) if(BOARD STREQUAL "RAD1O") SET(SRC_M4 @@ -216,14 +217,26 @@ macro(DeclareTargets) ${SRC_M4} ${PATH_HACKRF_FIRMWARE_COMMON}/fpga.c ${PATH_HACKRF_FIRMWARE_COMMON}/ice40_spi.c - ${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2831.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2831_target.c ) else() SET(SRC_M4 ${SRC_M4} - ${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2837.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2837_target.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2839.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2839_target.c + ) + endif() + + if(BOARD STREQUAL "UNIVERSAL") + SET(SRC_M4 + ${SRC_M4} + ${PATH_HACKRF_FIRMWARE_COMMON}/fpga.c + ${PATH_HACKRF_FIRMWARE_COMMON}/ice40_spi.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2831.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2831_target.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2837.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2837_target.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2839.c diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index 3ab1beed..3fd3ea87 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -35,7 +35,7 @@ add_custom_command( OUTPUT ${PATH_PRALINE_FPGA_OBJ} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy ${PATH_PRALINE_FPGA_BIN} "fpga.bin" - COMMAND ${CMAKE_OBJCOPY} + COMMAND ${CMAKE_OBJCOPY} -I binary -O elf32-littlearm -B arm @@ -75,6 +75,26 @@ set(SRC_M4 set(SRC_M0 sgpio_m0.s) +if(BOARD STREQUAL "UNIVERSAL") + SET(SRC_M4 + ${SRC_M4} + usb_api_cpld.c + "${PATH_HACKRF_FIRMWARE_COMMON}/cpld_xc2c.c" + "${PATH_HACKRF_CPLD_DATA_C}" + "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/lenval.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/micro.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/ports.c" + + "${PATH_HACKRF_FIRMWARE_COMMON}/lz4_blk.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/fpga_image.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/fpga_selftest.c" + usb_api_praline.c + ) + SET(OBJ_M4 + ${PATH_PRALINE_FPGA_OBJ} + ) +endif() + if(BOARD STREQUAL "PRALINE") SET(SRC_M4 ${SRC_M4} @@ -98,7 +118,7 @@ else() ) endif() -if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE") +if(BOARD STREQUAL "PRALINE" OR BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "UNIVERSAL") SET(SRC_M4 ${SRC_M4} "${PATH_HACKRF_FIRMWARE_COMMON}/portapack.c" From 9d11652d6240a77bf39110baf985433d728b2a7f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 2 Apr 2026 04:48:00 +0100 Subject: [PATCH 021/161] Add UNIVERSAL firmware build to GitHub Actions. --- .github/workflows/build.yml | 2 +- .github/workflows/includes-check.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07b419e5..a93a27c1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,7 +159,7 @@ jobs: strategy: matrix: os: ['macos', 'ubuntu', 'windows'] - board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE'] + board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE', 'UNIVERSAL'] cmake: ['3.10.0', 'latest'] exclude: - os: 'windows' diff --git a/.github/workflows/includes-check.yml b/.github/workflows/includes-check.yml index dfb631bd..11640069 100644 --- a/.github/workflows/includes-check.yml +++ b/.github/workflows/includes-check.yml @@ -13,7 +13,7 @@ jobs: options: --user 0 strategy: matrix: - board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE'] + board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE', 'UNIVERSAL'] cmake: ['3.10.0', 'latest'] # Don't cancel all builds when one fails From 7fd2d4b86ff326c8030215dd452f780b7d697de9 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 2 Apr 2026 07:45:08 +0100 Subject: [PATCH 022/161] Jenkinsfile: add tests with BOARD=UNIVERSAL. --- Jenkinsfile | 122 ++++++++++++++---- ...ild_hpro_firmware.sh => build_firmware.sh} | 2 +- ci-scripts/build_h1_firmware.sh | 9 -- 3 files changed, 99 insertions(+), 34 deletions(-) rename ci-scripts/{build_hpro_firmware.sh => build_firmware.sh} (80%) delete mode 100755 ci-scripts/build_h1_firmware.sh diff --git a/Jenkinsfile b/Jenkinsfile index 2b03c4de..72f54209 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,21 @@ import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException +def docker_args = '''--group-add=20 --group-add=46 --device-cgroup-rule="c 189:* rmw" \ + --device-cgroup-rule="c 166:* rmw" -v /dev/bus/usb:/dev/bus/usb \ + -v /tmp/req_pipe:/tmp/req_pipe -v /tmp/res_pipe:/tmp/res_pipe''' + +def h1_test = '''python3 ci-scripts/hackrf_test.py --ci --log log \ + --hostdir host/build/hackrf-tools/src/ \ + --fwupdate firmware/hackrf_usb/build/ \ + --tester 0000000000000000325866e629a25623 \ + --eut RunningFromRAM --unattended --rev r4''' + +def hpro_test = '''python3 ci-scripts/hackrf_pro_test.py --ci --log log \ + --hostdir host/build/hackrf-tools/src \ + --fwupdate firmware/hackrf_usb/build \ + --tester 0000000000000000a06063c82338145f \ + --eut RunningFromRAM -p --rev r1.2''' + pipeline { agent any stages { @@ -8,19 +24,17 @@ pipeline { sh 'docker build -t hackrf https://github.com/greatscottgadgets/hackrf.git' } } - stage('Test HackRF One') { + stage('Test HackRF One with BOARD=HACKRF_ONE') { agent { docker { image 'hackrf' reuseNode true - args '''--group-add=20 --group-add=46 --device-cgroup-rule="c 189:* rmw" \ - --device-cgroup-rule="c 166:* rmw" -v /dev/bus/usb:/dev/bus/usb \ - -v /tmp/req_pipe:/tmp/req_pipe -v /tmp/res_pipe:/tmp/res_pipe''' + args docker_args } } steps { sh './ci-scripts/install_host.sh' - sh './ci-scripts/build_h1_firmware.sh' + sh './ci-scripts/build_firmware.sh HACKRF_ONE' script { allOff(); } @@ -38,13 +52,7 @@ pipeline { sh 'sleep 1s' script { // Allow 5 minutes for the test to run - runCommand(5, 'MINUTES', "HackRF One Test", - '''python3 ci-scripts/hackrf_test.py --ci --log log \ - --hostdir host/build/hackrf-tools/src/ \ - --fwupdate firmware/hackrf_usb/build/ \ - --tester 0000000000000000325866e629a25623 \ - --eut RunningFromRAM --unattended --rev r4''' - ) + runCommand(5, 'MINUTES', "HackRF One Test", h1_test) allOff() } } @@ -57,19 +65,58 @@ pipeline { } } } - stage('Test HackRF Pro') { + stage('Test HackRF One with BOARD=UNIVERSAL') { agent { docker { image 'hackrf' reuseNode true - args '''--group-add=20 --group-add=46 --device-cgroup-rule="c 189:* rmw" \ - --device-cgroup-rule="c 166:* rmw" -v /dev/bus/usb:/dev/bus/usb \ - -v /tmp/req_pipe:/tmp/req_pipe -v /tmp/res_pipe:/tmp/res_pipe''' + args docker_args } } steps { sh './ci-scripts/install_host.sh' - sh './ci-scripts/build_hpro_firmware.sh' + sh './ci-scripts/build_firmware.sh UNIVERSAL' + script { + allOff(); + } + retry(3) { + script { + reset('h1_eut') + } + sh 'sleep 1s' + sh './ci-scripts/test_host.sh' + } + retry(3) { + script { + reset('h1_tester h1_eut') + } + sh 'sleep 1s' + script { + // Allow 5 minutes for the test to run + runCommand(5, 'MINUTES', "HackRF One Test", h1_test) + allOff() + } + } + retry(3) { + script { + reset('h1_eut') + } + sh 'sleep 1s' + sh 'python3 ci-scripts/test_sgpio_debug.py' + } + } + } + stage('Test HackRF Pro with BOARD=PRALINE') { + agent { + docker { + image 'hackrf' + reuseNode true + args "$docker_args" + } + } + steps { + sh './ci-scripts/install_host.sh' + sh './ci-scripts/build_firmware.sh PRALINE' script { allOff(); } @@ -87,13 +134,40 @@ pipeline { sh 'sleep 1s' script { // Allow 5 minutes for the test to run - runCommand(5, 'MINUTES', "HackRF Pro Test", - '''python3 ci-scripts/hackrf_pro_test.py --ci --log log \ - --hostdir host/build/hackrf-tools/src \ - --fwupdate firmware/hackrf_usb/build \ - --tester 0000000000000000a06063c82338145f \ - --eut RunningFromRAM -p --rev r1.2''' - ) + runCommand(5, 'MINUTES', "HackRF Pro Test", hpro_test) + } + } + } + } + stage('Test HackRF Pro with BOARD=UNIVERSAL') { + agent { + docker { + image 'hackrf' + reuseNode true + args "$docker_args" + } + } + steps { + sh './ci-scripts/install_host.sh' + sh './ci-scripts/build_firmware.sh UNIVERSAL' + script { + allOff(); + } + retry(3) { + script { + reset('hpro_eut') + } + sh 'sleep 1s' + sh './ci-scripts/test_host.sh' + } + retry(3) { + script { + reset('hpro_tester hpro_eut') + } + sh 'sleep 1s' + script { + // Allow 5 minutes for the test to run + runCommand(5, 'MINUTES', "HackRF Pro Test", hpro_test) } } } diff --git a/ci-scripts/build_hpro_firmware.sh b/ci-scripts/build_firmware.sh similarity index 80% rename from ci-scripts/build_hpro_firmware.sh rename to ci-scripts/build_firmware.sh index 830d4bf7..b6fc4ec4 100755 --- a/ci-scripts/build_hpro_firmware.sh +++ b/ci-scripts/build_firmware.sh @@ -4,6 +4,6 @@ git submodule init git submodule update cd firmware/hackrf_usb rm -rf build -cmake -DBOARD=PRALINE -B build +cmake -DBOARD=$1 -B build cmake --build build cd ../.. diff --git a/ci-scripts/build_h1_firmware.sh b/ci-scripts/build_h1_firmware.sh deleted file mode 100755 index 762d64de..00000000 --- a/ci-scripts/build_h1_firmware.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -e -git submodule init -git submodule update -cd firmware/hackrf_usb -rm -rf build -cmake -DBOARD=HACKRF_ONE -B build -cmake --build build -cd ../.. From 77569163a30bd4cc8072dcac923a2c79ed29c7d2 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 6 Apr 2026 21:21:01 +0100 Subject: [PATCH 023/161] max283x: Move platform switch inside max283x_setup(). --- firmware/common/max283x.c | 51 +++++++++++++++++---------------------- firmware/common/max283x.h | 2 +- firmware/common/rf_path.c | 18 +------------- 3 files changed, 24 insertions(+), 47 deletions(-) diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 54b596bc..9c64d668 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -27,6 +27,7 @@ #include #include "fixed_point.h" +#include "platform_detect.h" #include "platform_gpio.h" #include "spi_bus.h" @@ -67,49 +68,41 @@ max2839_driver_t max2839 = { #endif /* Initialize chip. */ -void max283x_setup(max283x_driver_t* const drv, max283x_variant_t type) +void max283x_setup(max283x_driver_t* const drv) { - drv->type = type; - const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ + switch (detected_platform()) { + case BOARD_ID_PRALINE: #if defined(PRALINE) || defined(UNIVERSAL) - max2831.gpio_enable = gpio->max283x_enable; - max2831.gpio_rxtx = gpio->max283x_rx_enable; - max2831.gpio_rxhp = gpio->max2831_rxhp; - max2831.gpio_ld = gpio->max2831_ld; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - max2837.gpio_enable = gpio->max283x_enable; - max2837.gpio_rx_enable = gpio->max283x_rx_enable; - max2837.gpio_tx_enable = gpio->max283x_tx_enable; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - max2839.gpio_enable = gpio->max283x_enable; - max2839.gpio_rxtx = gpio->max283x_rx_enable; -#endif - - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: + drv->type = MAX2831_VARIANT; + max2831.gpio_enable = gpio->max283x_enable; + max2831.gpio_rxtx = gpio->max283x_rx_enable; + max2831.gpio_rxhp = gpio->max2831_rxhp; + max2831.gpio_ld = gpio->max2831_ld; memcpy(&drv->drv.max2831, &max2831, sizeof(max2831)); max2831_setup(&drv->drv.max2831); - break; #endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); - max2837_setup(&drv->drv.max2837); break; -#endif + case BOARD_ID_HACKRF1_R9: #if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: + drv->type = MAX2839_VARIANT; + max2839.gpio_enable = gpio->max283x_enable; + max2839.gpio_rxtx = gpio->max283x_rx_enable; memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); max2839_setup(&drv->drv.max2839); - break; #endif + break; default: +#if !defined(PRALINE) || defined(UNIVERSAL) + drv->type = MAX2837_VARIANT; + max2837.gpio_enable = gpio->max283x_enable; + max2837.gpio_rx_enable = gpio->max283x_rx_enable; + max2837.gpio_tx_enable = gpio->max283x_tx_enable; + memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); + max2837_setup(&drv->drv.max2837); +#endif break; } } diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index 87da59b4..bcafff06 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -83,7 +83,7 @@ typedef struct { } max283x_driver_t; /* Initialize chip. */ -void max283x_setup(max283x_driver_t* const drv, max283x_variant_t type); +void max283x_setup(max283x_driver_t* const drv); /* Returns the number of registers supported by the driver. */ uint16_t max283x_num_regs(max283x_driver_t* const drv); diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 8d2a6e2c..68e05878 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -484,23 +484,7 @@ void rf_path_init(rf_path_t* const rf_path) max5864_shutdown(&max5864); ssp1_set_mode_max283x(); - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) - max283x_setup(&max283x, MAX2831_VARIANT); -#endif - break; - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - max283x_setup(&max283x, MAX2839_VARIANT); -#endif - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) - max283x_setup(&max283x, MAX2837_VARIANT); -#endif - break; - } + max283x_setup(&max283x); max283x_start(&max283x); switch (board_id) { From 7fa88d13e0edbefad131aff7758a02dfc7046828 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 6 Apr 2026 22:58:34 +0100 Subject: [PATCH 024/161] max283x: Add macros to simplify variant-specific dispatch. --- firmware/common/max283x.c | 442 +++++++++----------------------------- 1 file changed, 100 insertions(+), 342 deletions(-) diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 9c64d668..6a6d5f26 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -107,93 +107,95 @@ void max283x_setup(max283x_driver_t* const drv) } } +/* Macros to simplify dispatch of variant-specific operations. */ + +/* clang-format off */ + +#if defined(UNIVERSAL) + // UNIVERSAL: all variants + #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ + if (_drv->type == MAX2831_VARIANT) { \ + _max2831; \ + } else if (_drv->type == MAX2837_VARIANT) { \ + _max2837; \ + } else { \ + _max2839; \ + } +#elif defined(HACKRF_ONE) + // HACKRF_ONE: MAX2837 and MAX2839 only + #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ + if (_drv->type == MAX2837_VARIANT) { \ + _max2837; \ + } else { \ + _max2839; \ + } +#elif defined(PRALINE) + // PRALINE: MAX2831 only + #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ + (void) drv; \ + _max2831 +#else + // JAWBREAKER, RAD1O: MAX2837 only + #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ + (void) drv; \ + _max2837 +#endif + +#define CALL(_drv, _func, ...) \ + DISPATCH(drv, \ + max2831_ ## _func(&_drv->drv.max2831, ##__VA_ARGS__), \ + max2837_ ## _func(&_drv->drv.max2837, ##__VA_ARGS__), \ + max2839_ ## _func(&_drv->drv.max2839, ##__VA_ARGS__) \ + ); \ + +#define RESULT(_drv, _type, _func, ...) ({ \ + _type _result; \ + DISPATCH(drv, \ + _result = max2831_ ## _func(&_drv->drv.max2831, ##__VA_ARGS__), \ + _result = max2837_ ## _func(&_drv->drv.max2837, ##__VA_ARGS__), \ + _result = max2839_ ## _func(&_drv->drv.max2839, ##__VA_ARGS__) \ + ); \ + _result; \ +}) + +#define CONSTANT(_drv, _type, _name) ({\ + _type value; \ + DISPATCH(drv, \ + value = MAX2831_ ## _name, \ + value = MAX2837_ ## _name, \ + value = MAX2839_ ## _name \ + ); \ + value; \ +}) + +#define PRALINE_ONLY(_drv, _max2831) DISPATCH(_drv, _max2831, , ) + +/* clang-format on */ + /* Returns the number of registers supported by the driver. */ uint16_t max283x_num_regs(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return MAX2831_NUM_REGS; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return MAX2837_NUM_REGS; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return MAX2839_NUM_REGS; -#endif - default: - return 0; - } + return CONSTANT(drv, uint16_t, NUM_REGS); } /* Returns the maximum data register value supported by the driver. */ uint16_t max283x_data_regs_max_value(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return MAX2831_DATA_REGS_MAX_VALUE; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return MAX2837_DATA_REGS_MAX_VALUE; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return MAX2839_DATA_REGS_MAX_VALUE; -#endif - default: - return 0; - } + return CONSTANT(drv, uint16_t, DATA_REGS_MAX_VALUE); } /* Read a register via SPI. Save a copy to memory and return * value. Mark clean. */ uint16_t max283x_reg_read(max283x_driver_t* const drv, uint8_t r) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_reg_read(&drv->drv.max2831, r); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_reg_read(&drv->drv.max2837, r); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_reg_read(&drv->drv.max2839, r); -#endif - default: - return 0; - } + return RESULT(drv, uint16_t, reg_read, r); } /* Write value to register via SPI and save a copy to memory. Mark * clean. */ void max283x_reg_write(max283x_driver_t* const drv, uint8_t r, uint16_t v) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_reg_write(&drv->drv.max2831, r, v); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_reg_write(&drv->drv.max2837, r, v); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_reg_write(&drv->drv.max2839, r, v); - break; -#endif - default: - break; - } + CALL(drv, reg_write, r, v); } /* Write all dirty registers via SPI from memory. Mark all clean. Some @@ -201,115 +203,36 @@ void max283x_reg_write(max283x_driver_t* const drv, uint8_t r, uint16_t v) * provided routines for those operations. */ void max283x_regs_commit(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_regs_commit(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_regs_commit(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_regs_commit(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, regs_commit); } void max283x_set_mode(max283x_driver_t* const drv, const max283x_mode_t new_mode) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_set_mode(&drv->drv.max2831, (max2831_mode_t) new_mode); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_set_mode(&drv->drv.max2837, (max2837_mode_t) new_mode); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_set_mode(&drv->drv.max2839, (max2839_mode_t) new_mode); - break; -#endif - default: - break; - } + DISPATCH( + drv, + max2831_set_mode(&drv->drv.max2831, (max2831_mode_t) new_mode), + max2837_set_mode(&drv->drv.max2837, (max2837_mode_t) new_mode), + max2839_set_mode(&drv->drv.max2839, (max2839_mode_t) new_mode)); } max283x_mode_t max283x_mode(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return (max283x_mode_t) max2831_mode(&drv->drv.max2831); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return (max283x_mode_t) max2837_mode(&drv->drv.max2837); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return (max283x_mode_t) max2839_mode(&drv->drv.max2839); -#endif - default: - return 0; - } + DISPATCH( + drv, + return (max283x_mode_t) max2831_mode(&drv->drv.max2831), + return (max283x_mode_t) max2837_mode(&drv->drv.max2837), + return (max283x_mode_t) max2839_mode(&drv->drv.max2839)); } /* Turn on/off all chip functions. Does not control oscillator and CLKOUT */ void max283x_start(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_start(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_start(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_start(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, start); } void max283x_stop(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_stop(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_stop(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_stop(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, stop); } /* Set frequency in 1/(2**24) Hz. */ @@ -318,26 +241,7 @@ fp_40_24_t max283x_set_frequency( fp_40_24_t freq, bool program) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_set_frequency(&drv->drv.max2831, freq, program); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_frequency(&drv->drv.max2837, freq, program); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_frequency(&drv->drv.max2839, freq, program); - break; -#endif - default: - break; - } - return 0; + return RESULT(drv, fp_40_24_t, set_frequency, freq, program); } uint32_t max283x_set_lpf_bandwidth( @@ -345,135 +249,42 @@ uint32_t max283x_set_lpf_bandwidth( const max283x_mode_t mode, const uint32_t bandwidth_hz) { -#ifndef PRALINE +#if !defined(PRALINE) (void) mode; #endif - - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: + DISPATCH( + drv, return max2831_set_lpf_bandwidth( &drv->drv.max2831, (max2831_mode_t) mode, - bandwidth_hz); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_lpf_bandwidth(&drv->drv.max2837, bandwidth_hz); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_lpf_bandwidth(&drv->drv.max2839, bandwidth_hz); -#endif - default: - return 0; - } + bandwidth_hz), + return max2837_set_lpf_bandwidth(&drv->drv.max2837, bandwidth_hz), + return max2839_set_lpf_bandwidth(&drv->drv.max2839, bandwidth_hz)); } bool max283x_set_lna_gain(max283x_driver_t* const drv, const uint32_t gain_db) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_set_lna_gain(&drv->drv.max2831, gain_db); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_lna_gain(&drv->drv.max2837, gain_db); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_lna_gain(&drv->drv.max2839, gain_db); -#endif - default: - return false; - } + return RESULT(drv, bool, set_lna_gain, gain_db); } bool max283x_set_vga_gain(max283x_driver_t* const drv, const uint32_t gain_db) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_set_vga_gain(&drv->drv.max2831, gain_db); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_vga_gain(&drv->drv.max2837, gain_db); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_vga_gain(&drv->drv.max2839, gain_db); -#endif - default: - return false; - } + return RESULT(drv, bool, set_vga_gain, gain_db); } bool max283x_set_txvga_gain(max283x_driver_t* const drv, const uint32_t gain_db) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_set_txvga_gain(&drv->drv.max2831, gain_db); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_txvga_gain(&drv->drv.max2837, gain_db); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_txvga_gain(&drv->drv.max2839, gain_db); -#endif - default: - return false; - } + return RESULT(drv, bool, set_txvga_gain, gain_db); } void max283x_tx(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_tx(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_tx(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_tx(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, tx); } void max283x_rx(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_rx(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_rx(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_rx(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, rx); } /* Set MAX2831 receiver high-pass filter corner frequency in Hz */ @@ -481,75 +292,22 @@ void max283x_set_rx_hpf_frequency( max283x_driver_t* const drv, const max283x_rx_hpf_freq_t freq) { -#if !defined(PRALINE) +#if !defined(PRALINE) && !defined(UNIVERSAL) (void) freq; #endif - - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: + PRALINE_ONLY( + drv, max2831_set_rx_hpf_frequency( &drv->drv.max2831, - (max2831_rx_hpf_freq_t) freq); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - // unsupported - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - // unsupported - break; -#endif - default: - break; - } + (max2831_rx_hpf_freq_t) freq)); } void max283x_tx_calibration(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_tx_calibration(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - // unsupported - use max283x_set_mode instead - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - // unsupported - use max283x_set_mode instead - break; -#endif - default: - break; - } + PRALINE_ONLY(drv, max2831_tx_calibration(&drv->drv.max2831)); } void max283x_rx_calibration(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_rx_calibration(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - // unsupported - use max283x_set_mode instead - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - // unsupported - use max283x_set_mode instead - break; -#endif - default: - break; - } + PRALINE_ONLY(drv, max2831_rx_calibration(&drv->drv.max2831)); } From 7938f93295a01837a8d417beb20c31dac8b8b084 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 15 Apr 2026 02:05:24 +0100 Subject: [PATCH 025/161] firmware: Use macros to simplify platform-specific code. --- firmware/common/cpld_jtag.c | 64 +-- firmware/common/hackrf_core.c | 549 +++++++++-------------- firmware/common/hackrf_ui.c | 18 +- firmware/common/max283x.c | 44 +- firmware/common/mixer.c | 121 ++--- firmware/common/operacake_sctimer.c | 11 +- firmware/common/platform_detect.h | 111 +++++ firmware/common/platform_gpio.c | 234 +++------- firmware/common/platform_scu.c | 300 ++++--------- firmware/common/radio.c | 71 ++- firmware/common/rf_path.c | 90 ++-- firmware/common/rffc5071.c | 37 +- firmware/common/sgpio.c | 15 +- firmware/common/si5351c.c | 48 +- firmware/common/tuning.c | 13 +- firmware/hackrf_usb/hackrf_usb.c | 111 ++--- firmware/hackrf_usb/usb_api_board_info.c | 6 +- firmware/hackrf_usb/usb_api_register.c | 138 +++--- firmware/hackrf_usb/usb_api_selftest.c | 37 +- 19 files changed, 782 insertions(+), 1236 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 21991db0..58359051 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -39,83 +39,55 @@ void cpld_jtag_take(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; - board_id_t board_id = detected_platform(); - /* Set initial GPIO state to the voltages of the internal or external pull-ups/downs, * to avoid any glitches. */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( gpio_set(gpio->gpio_pp_tms); -#endif - break; - default: - break; - } + ) + gpio_clear(gpio->gpio_tck); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + + IF_NOT_PRALINE ( gpio_set(gpio->gpio_tms); gpio_set(gpio->gpio_tdi); -#endif - } + ) - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( /* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */ gpio_input(gpio->gpio_pp_tms); gpio_input(gpio->gpio_pp_tdo); -#endif - break; - default: - break; - } + ) + gpio_output(gpio->gpio_tck); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + + IF_NOT_PRALINE ( gpio_output(gpio->gpio_tms); gpio_output(gpio->gpio_tdi); gpio_input(gpio->gpio_tdo); -#endif - } + ) } void cpld_jtag_release(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; - board_id_t board_id = detected_platform(); - /* Make all pins inputs when JTAG interface not active. * Let the pull-ups/downs do the work. */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( /* Do not drive PortaPack-specific pins, initially, just to be cautious. */ gpio_input(gpio->gpio_pp_tms); gpio_input(gpio->gpio_pp_tdo); -#endif - break; - default: - break; - } + ) + gpio_input(gpio->gpio_tck); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + + IF_NOT_PRALINE ( gpio_input(gpio->gpio_tms); gpio_input(gpio->gpio_tdi); gpio_input(gpio->gpio_tdo); -#endif - } + ) } #if !defined(PRALINE) || defined(UNIVERSAL) diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index c8b66443..689a2a30 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -341,8 +341,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) sgpio_cpld_stream_disable(&sgpio_config); } - if (detected_platform() != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( /* Integer mode can be enabled if p1 is even and p2 is zero. */ if (p1 & 0x1 || p2) { si5351c_set_int_mode(&clock_gen, 0, 0); @@ -350,13 +349,14 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) si5351c_set_int_mode(&clock_gen, 0, 1); } - if (detected_platform() == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( /* * On HackRF One r9 all sample clocks are externally derived * from MS1/CLK1 operating at twice the sample rate. */ si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); - } else { + ) + IF_NOT_H1_R9 ( /* * On other platforms the clock generator produces three * different sample clocks, all derived from multisynth 0. @@ -381,10 +381,9 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) 0, 0, 0); //p1 doesn't matter - } -#endif - } else { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + ) + IF_PRALINE ( /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); @@ -410,8 +409,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) /* Reset PLL to synchronize output clock phase. */ si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); -#endif - } + ) if (streaming) { sgpio_cpld_stream_enable(&sgpio_config); @@ -563,73 +561,75 @@ void cpu_clock_init(void) CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); -#if !defined(RAD1O) - /* Disable unused clocks */ - /* Start with PLLs */ - CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); + IF_NOT_RAD1O ( + /* Disable unused clocks */ + /* Start with PLLs */ + CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); - /* Dividers */ - CGU_IDIVA_CTRL = CGU_IDIVA_CTRL_PD(1); - CGU_IDIVB_CTRL = CGU_IDIVB_CTRL_PD(1); - CGU_IDIVC_CTRL = CGU_IDIVC_CTRL_PD(1); - CGU_IDIVD_CTRL = CGU_IDIVD_CTRL_PD(1); - CGU_IDIVE_CTRL = CGU_IDIVE_CTRL_PD(1); + /* Dividers */ + CGU_IDIVA_CTRL = CGU_IDIVA_CTRL_PD(1); + CGU_IDIVB_CTRL = CGU_IDIVB_CTRL_PD(1); + CGU_IDIVC_CTRL = CGU_IDIVC_CTRL_PD(1); + CGU_IDIVD_CTRL = CGU_IDIVD_CTRL_PD(1); + CGU_IDIVE_CTRL = CGU_IDIVE_CTRL_PD(1); - /* Base clocks */ - CGU_BASE_SPIFI_CLK = CGU_BASE_SPIFI_CLK_PD(1); /* SPIFI is only used at boot */ - CGU_BASE_USB1_CLK = CGU_BASE_USB1_CLK_PD(1); /* USB1 is not exposed on HackRF */ - CGU_BASE_PHY_RX_CLK = CGU_BASE_PHY_RX_CLK_PD(1); - CGU_BASE_PHY_TX_CLK = CGU_BASE_PHY_TX_CLK_PD(1); - CGU_BASE_LCD_CLK = CGU_BASE_LCD_CLK_PD(1); - CGU_BASE_VADC_CLK = CGU_BASE_VADC_CLK_PD(1); - CGU_BASE_SDIO_CLK = CGU_BASE_SDIO_CLK_PD(1); - CGU_BASE_UART0_CLK = CGU_BASE_UART0_CLK_PD(1); - CGU_BASE_UART1_CLK = CGU_BASE_UART1_CLK_PD(1); - CGU_BASE_UART2_CLK = CGU_BASE_UART2_CLK_PD(1); - CGU_BASE_UART3_CLK = CGU_BASE_UART3_CLK_PD(1); - CGU_BASE_OUT_CLK = CGU_BASE_OUT_CLK_PD(1); - CGU_BASE_AUDIO_CLK = CGU_BASE_AUDIO_CLK_PD(1); - CGU_BASE_CGU_OUT0_CLK = CGU_BASE_CGU_OUT0_CLK_PD(1); - CGU_BASE_CGU_OUT1_CLK = CGU_BASE_CGU_OUT1_CLK_PD(1); + /* Base clocks */ + CGU_BASE_SPIFI_CLK = + CGU_BASE_SPIFI_CLK_PD(1); /* SPIFI is only used at boot */ + CGU_BASE_USB1_CLK = + CGU_BASE_USB1_CLK_PD(1); /* USB1 is not exposed on HackRF */ + CGU_BASE_PHY_RX_CLK = CGU_BASE_PHY_RX_CLK_PD(1); + CGU_BASE_PHY_TX_CLK = CGU_BASE_PHY_TX_CLK_PD(1); + CGU_BASE_LCD_CLK = CGU_BASE_LCD_CLK_PD(1); + CGU_BASE_VADC_CLK = CGU_BASE_VADC_CLK_PD(1); + CGU_BASE_SDIO_CLK = CGU_BASE_SDIO_CLK_PD(1); + CGU_BASE_UART0_CLK = CGU_BASE_UART0_CLK_PD(1); + CGU_BASE_UART1_CLK = CGU_BASE_UART1_CLK_PD(1); + CGU_BASE_UART2_CLK = CGU_BASE_UART2_CLK_PD(1); + CGU_BASE_UART3_CLK = CGU_BASE_UART3_CLK_PD(1); + CGU_BASE_OUT_CLK = CGU_BASE_OUT_CLK_PD(1); + CGU_BASE_AUDIO_CLK = CGU_BASE_AUDIO_CLK_PD(1); + CGU_BASE_CGU_OUT0_CLK = CGU_BASE_CGU_OUT0_CLK_PD(1); + CGU_BASE_CGU_OUT1_CLK = CGU_BASE_CGU_OUT1_CLK_PD(1); - /* Disable unused peripheral clocks */ - CCU1_CLK_APB1_CAN1_CFG = 0; - CCU1_CLK_APB1_I2S_CFG = 0; - CCU1_CLK_APB1_MOTOCONPWM_CFG = 0; - //CCU1_CLK_APB3_ADC0_CFG = 0; - CCU1_CLK_APB3_ADC1_CFG = 0; - CCU1_CLK_APB3_CAN0_CFG = 0; - CCU1_CLK_APB3_DAC_CFG = 0; - //CCU1_CLK_M4_DMA_CFG = 0; - CCU1_CLK_M4_EMC_CFG = 0; - CCU1_CLK_M4_EMCDIV_CFG = 0; - CCU1_CLK_M4_ETHERNET_CFG = 0; - CCU1_CLK_M4_LCD_CFG = 0; - CCU1_CLK_M4_QEI_CFG = 0; - CCU1_CLK_M4_RITIMER_CFG = 0; - // CCU1_CLK_M4_SCT_CFG = 0; - CCU1_CLK_M4_SDIO_CFG = 0; - CCU1_CLK_M4_SPIFI_CFG = 0; - CCU1_CLK_M4_TIMER0_CFG = 0; - //CCU1_CLK_M4_TIMER1_CFG = 0; - //CCU1_CLK_M4_TIMER2_CFG = 0; - CCU1_CLK_M4_TIMER3_CFG = 0; - CCU1_CLK_M4_UART1_CFG = 0; - CCU1_CLK_M4_USART0_CFG = 0; - CCU1_CLK_M4_USART2_CFG = 0; - CCU1_CLK_M4_USART3_CFG = 0; - CCU1_CLK_M4_USB1_CFG = 0; - CCU1_CLK_M4_VADC_CFG = 0; - // CCU1_CLK_SPIFI_CFG = 0; - // CCU1_CLK_USB1_CFG = 0; - // CCU1_CLK_VADC_CFG = 0; - // CCU2_CLK_APB0_UART1_CFG = 0; - // CCU2_CLK_APB0_USART0_CFG = 0; - // CCU2_CLK_APB2_USART2_CFG = 0; - // CCU2_CLK_APB2_USART3_CFG = 0; - // CCU2_CLK_APLL_CFG = 0; - // CCU2_CLK_SDIO_CFG = 0; -#endif + /* Disable unused peripheral clocks */ + CCU1_CLK_APB1_CAN1_CFG = 0; + CCU1_CLK_APB1_I2S_CFG = 0; + CCU1_CLK_APB1_MOTOCONPWM_CFG = 0; + //CCU1_CLK_APB3_ADC0_CFG = 0; + CCU1_CLK_APB3_ADC1_CFG = 0; + CCU1_CLK_APB3_CAN0_CFG = 0; + CCU1_CLK_APB3_DAC_CFG = 0; + //CCU1_CLK_M4_DMA_CFG = 0; + CCU1_CLK_M4_EMC_CFG = 0; + CCU1_CLK_M4_EMCDIV_CFG = 0; + CCU1_CLK_M4_ETHERNET_CFG = 0; + CCU1_CLK_M4_LCD_CFG = 0; + CCU1_CLK_M4_QEI_CFG = 0; + CCU1_CLK_M4_RITIMER_CFG = 0; + // CCU1_CLK_M4_SCT_CFG = 0; + CCU1_CLK_M4_SDIO_CFG = 0; + CCU1_CLK_M4_SPIFI_CFG = 0; + CCU1_CLK_M4_TIMER0_CFG = 0; + //CCU1_CLK_M4_TIMER1_CFG = 0; + //CCU1_CLK_M4_TIMER2_CFG = 0; + CCU1_CLK_M4_TIMER3_CFG = 0; + CCU1_CLK_M4_UART1_CFG = 0; + CCU1_CLK_M4_USART0_CFG = 0; + CCU1_CLK_M4_USART2_CFG = 0; + CCU1_CLK_M4_USART3_CFG = 0; + CCU1_CLK_M4_USB1_CFG = 0; + CCU1_CLK_M4_VADC_CFG = 0; + // CCU1_CLK_SPIFI_CFG = 0; + // CCU1_CLK_USB1_CFG = 0; + // CCU1_CLK_VADC_CFG = 0; + // CCU2_CLK_APB0_UART1_CFG = 0; + // CCU2_CLK_APB0_USART0_CFG = 0; + // CCU2_CLK_APB2_USART2_CFG = 0; + // CCU2_CLK_APB2_USART3_CFG = 0; + // CCU2_CLK_APLL_CFG = 0; + // CCU2_CLK_SDIO_CFG = 0; + ) } void clock_gen_init(void) @@ -670,7 +670,7 @@ void clock_gen_init(void) * CLK7 -> AUX_CLK2 */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( /* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */ si5351c_configure_multisynth( &clock_gen, @@ -679,7 +679,8 @@ void clock_gen_init(void) 0, 1, 0); /* 800/20 = 40MHz */ - } else { + ) + IF_NOT_H1_R9 ( /* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */ si5351c_configure_multisynth( &clock_gen, @@ -696,7 +697,7 @@ void clock_gen_init(void) 0, 1, 0); /* 800/20 = 40MHz */ - } + ) /* MS6/CLK6 is unused. */ /* MS7/CLK7 is unused. */ @@ -721,20 +722,12 @@ void clock_gen_shutdown(void) clock_source_t activate_best_clock_source(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(false); } -#endif - break; - default: - break; - } + ) clock_source_t source = CLOCK_SOURCE_HACKRF; @@ -742,11 +735,7 @@ clock_source_t activate_best_clock_source(void) if (si5351c_clkin_signal_valid(&clock_gen)) { source = CLOCK_SOURCE_EXTERNAL; } else { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(true); @@ -757,11 +746,7 @@ clock_source_t activate_best_clock_source(void) portapack_reference_oscillator(false); } } -#endif - break; - default: - break; - } + ) /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ } @@ -796,7 +781,6 @@ void pin_shutdown(void) gpio_init(); /* Detect Platform */ - board_id_t board_id = detected_platform(); const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -814,99 +798,69 @@ void pin_shutdown(void) * * LPC43xx pull-up and pull-down resistors are approximately 53K. */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); -#endif - break; - default: - break; - } + ) scu_pinmux(scu->PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); -#endif - } + ) /* Configure SCU Pin Mux as GPIO */ scu_pinmux(scu->PINMUX_LED1, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED2, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED3, SCU_GPIO_NOPULL); - switch (board_id) { - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); -#endif - break; - default: - break; - } + ) /* Configure USB indicators */ - if (board_id == BOARD_ID_JAWBREAKER) { -#if defined(JAWBREAKER) + IF_JAWBREAKER ( scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3); scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3); -#endif - } + ) - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( disable_1v2_power(); disable_3v3aux_power(); gpio_output(gpio->gpio_1v2_enable); gpio_output(gpio->gpio_3v3aux_enable_n); scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#endif - break; - default: + ) + + IF_NOT_PRALINE ( disable_1v8_power(); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( gpio_output(gpio->h1r9_1v8_enable); scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#endif - } else { + ) + IF_NOT_H1_R9 ( gpio_output(gpio->gpio_1v8_enable); scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } - break; - } + ) + ) - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_H1_OR_PRALINE ( /* Safe state: start with VAA turned off: */ disable_rf_power(); /* Configure RF power supply (VAA) switch control signal as output */ - if (board_id == BOARD_ID_HACKRF1_R9) { - #if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( gpio_output(gpio->h1r9_vaa_disable); - #endif - } else { + ) + IF_NOT_H1_R9 ( gpio_output(gpio->vaa_disable); - } - break; -#endif - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + ) + + IF_RAD1O ( /* Safe state: start with VAA turned off: */ disable_rf_power(); @@ -919,15 +873,9 @@ void pin_shutdown(void) scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); -#endif - break; - default: - break; - } + ) - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG); scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG); scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG); @@ -962,11 +910,7 @@ void pin_shutdown(void) gpio_clear(gpio->fpga_cfg_creset); gpio_output(gpio->fpga_cfg_creset); gpio_input(gpio->fpga_cfg_cdone); -#endif - break; - default: - break; - } + ) /* enable input on SCL and SDA pins */ SCU_SFSI2C0 = SCU_I2C0_NOMINAL; @@ -976,7 +920,6 @@ void pin_shutdown(void) void pin_setup(void) { /* Detect Platform */ - board_id_t board_id = detected_platform(); const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -984,34 +927,26 @@ void pin_setup(void) led_off(0); led_off(1); led_off(2); - switch (board_id) { - case BOARD_ID_RAD1O: + IF_FOUR_LEDS ( led_off(3); - default: - break; - } + ) gpio_output(gpio->led[0]); gpio_output(gpio->led[1]); gpio_output(gpio->led[2]); - switch (board_id) { - case BOARD_ID_RAD1O: - case BOARD_ID_PRALINE: -#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) + + IF_FOUR_LEDS ( gpio_output(gpio->led[3]); -#endif - break; - default: - break; - } + ) /* Configure drivers and driver pins */ ssp_config_max283x.gpio_select = gpio->max283x_select; - if (board_id == BOARD_ID_PRALINE) { - ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words - } else { + IF_NOT_PRALINE ( ssp_config_max283x.data_bits = SSP_DATA_16BITS; - } + ) + IF_PRALINE ( + ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words + ) ssp_config_max5864.gpio_select = gpio->max5864_select; @@ -1020,62 +955,41 @@ void pin_setup(void) spi_flash.gpio_wp = gpio->w25q80bv_wp; sgpio_config.gpio_q_invert = gpio->q_invert; - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) - sgpio_config.gpio_trigger_enable = gpio->trigger_enable; -#endif - } -#if defined(PRALINE) || defined(UNIVERSAL) - ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; - ice40.gpio_select = gpio->fpga_cfg_spi_cs; - ice40.gpio_creset = gpio->fpga_cfg_creset; - ice40.gpio_cdone = gpio->fpga_cfg_cdone; -#endif + IF_NOT_PRALINE ( + sgpio_config.gpio_trigger_enable = gpio->trigger_enable; + ) + + IF_PRALINE ( + ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; + ice40.gpio_select = gpio->fpga_cfg_spi_cs; + ice40.gpio_creset = gpio->fpga_cfg_creset; + ice40.gpio_cdone = gpio->fpga_cfg_cdone; + ) jtag_gpio_cpld.gpio_tck = gpio->cpld_tck; -#if !defined(PRALINE) || defined(UNIVERSAL) - jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; - jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; - jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; -#endif - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + + IF_NOT_PRALINE ( jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; -#endif - } - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + ) + + IF_EXPANSION_COMPATIBLE ( jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; -#endif - break; - default: - break; - } + ) ssp1_set_mode_max283x(); mixer_bus_setup(&mixer); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( sgpio_config.gpio_trigger_enable = gpio->h1r9_trigger_enable; -#endif - } + ) // initialize rf_path struct and assign gpio's - switch (board_id) { - case BOARD_ID_JAWBREAKER: - break; - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( rf_path = (rf_path_t){ .switchctrl = 0, .gpio_hp = gpio->hp, @@ -1092,14 +1006,13 @@ void pin_setup(void) .gpio_rx_amp = gpio->rx_amp, .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, }; - if (board_id == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( rf_path.gpio_rx = gpio->h1r9_rx; rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; - } -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + ) + + IF_RAD1O ( rf_path = (rf_path_t){ .switchctrl = 0, .gpio_tx_rx_n = gpio->tx_rx_n, @@ -1114,10 +1027,9 @@ void pin_setup(void) .gpio_tx_amp = gpio->tx_amp, .gpio_rx_lna = gpio->rx_lna, }; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + + IF_PRALINE ( rf_path = (rf_path_t){ .switchctrl = 0, .gpio_tx_en = gpio->tx_en, @@ -1130,11 +1042,8 @@ void pin_setup(void) (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; } -#endif - break; - default: - break; - } + ) + rf_path_pin_setup(&rf_path); /* Configure external clock in */ @@ -1146,79 +1055,55 @@ void pin_setup(void) #if defined(PRALINE) || defined(UNIVERSAL) void enable_1v2_power(void) { - switch (detected_platform()) { - case BOARD_ID_PRALINE: + IF_PRALINE ( gpio_set(platform_gpio()->gpio_1v2_enable); - break; - default: - break; - } + ) } void disable_1v2_power(void) { - switch (detected_platform()) { - case BOARD_ID_PRALINE: + IF_PRALINE ( gpio_clear(platform_gpio()->gpio_1v2_enable); - break; - default: - break; - } + ) } void enable_3v3aux_power(void) { - switch (detected_platform()) { - case BOARD_ID_PRALINE: + IF_PRALINE ( gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); - break; - default: - break; - } + ) } void disable_3v3aux_power(void) { - switch (detected_platform()) { - case BOARD_ID_PRALINE: + IF_PRALINE ( gpio_set(platform_gpio()->gpio_3v3aux_enable_n); - break; - default: - break; - } + ) } #endif void enable_1v8_power(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - gpio_set(platform_gpio()->h1r9_1v8_enable); -#endif - break; - case BOARD_ID_PRALINE: - break; - default: - gpio_set(platform_gpio()->gpio_1v8_enable); - break; - } + IF_NOT_PRALINE ( + IF_H1_R9 ( + gpio_set(platform_gpio()->h1r9_1v8_enable); + ) + IF_NOT_H1_R9 ( + gpio_set(platform_gpio()->gpio_1v8_enable); + ) + ) } void disable_1v8_power(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - gpio_clear(platform_gpio()->h1r9_1v8_enable); -#endif - break; - case BOARD_ID_PRALINE: - break; - default: - gpio_clear(platform_gpio()->gpio_1v8_enable); - break; - } + IF_NOT_PRALINE ( + IF_H1_R9 ( + gpio_clear(platform_gpio()->h1r9_1v8_enable); + ) + IF_NOT_H1_R9 ( + gpio_clear(platform_gpio()->gpio_1v8_enable); + ) + ) } #if defined(HACKRF_ONE) || defined(UNIVERSAL) @@ -1281,68 +1166,48 @@ static inline void disable_rf_power_rad1o(void) void enable_rf_power(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( enable_rf_power_hackrf_one(); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( enable_rf_power_praline(); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( enable_rf_power_rad1o(); -#endif - break; - default: - break; - } + ) } void disable_rf_power(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( disable_rf_power_hackrf_one(); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( disable_rf_power_praline(); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( disable_rf_power_rad1o(); -#endif - break; - default: - break; - } + ) } void led_on(const led_t led) { - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( gpio_clear(platform_gpio()->led[led]); - } else { + ) + IF_NOT_PRALINE ( gpio_set(platform_gpio()->led[led]); - } + ) } void led_off(const led_t led) { - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( gpio_set(platform_gpio()->led[led]); - } else { + ) + IF_NOT_PRALINE ( gpio_clear(platform_gpio()->led[led]); - } + ) } void led_toggle(const led_t led) @@ -1352,33 +1217,29 @@ void led_toggle(const led_t led) void set_leds(const uint8_t state) { - board_id_t board_id = detected_platform(); - int num_leds = 3; - if (board_id == BOARD_ID_RAD1O || board_id == BOARD_ID_PRALINE) { + IF_FOUR_LEDS ( num_leds = 4; - } + ) for (int i = 0; i < num_leds; i++) { - if (board_id == BOARD_ID_PRALINE) { + IF_PRALINE ( gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); - } else { + ) + IF_NOT_PRALINE ( gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); - } + ) } } void trigger_enable(const bool enable) { - if (detected_platform() != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( gpio_write(sgpio_config.gpio_trigger_enable, enable); -#endif - } else { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( fpga_set_trigger_enable(&fpga, enable); -#endif - } + ) } void halt_and_flash(const uint32_t duration) diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index ac16a444..092ec12b 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -83,26 +83,16 @@ const hackrf_ui_t* hackrf_ui(void) { /* Detect on first use. If no UI hardware is detected, use a stub function table. */ if (ui == NULL && ui_enabled) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( if (portapack_hackrf_ui_init) { ui = portapack_hackrf_ui_init(); } -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( if (rad1o_ui_setup) { ui = rad1o_ui_setup(); } -#endif - break; - default: - break; - } + ) } if (ui == NULL) { diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 6a6d5f26..feb7837d 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -73,9 +73,7 @@ void max283x_setup(max283x_driver_t* const drv) const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ - switch (detected_platform()) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( drv->type = MAX2831_VARIANT; max2831.gpio_enable = gpio->max283x_enable; max2831.gpio_rxtx = gpio->max283x_rx_enable; @@ -83,28 +81,24 @@ void max283x_setup(max283x_driver_t* const drv) max2831.gpio_ld = gpio->max2831_ld; memcpy(&drv->drv.max2831, &max2831, sizeof(max2831)); max2831_setup(&drv->drv.max2831); -#endif - break; - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - drv->type = MAX2839_VARIANT; - max2839.gpio_enable = gpio->max283x_enable; - max2839.gpio_rxtx = gpio->max283x_rx_enable; - memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); - max2839_setup(&drv->drv.max2839); -#endif - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) - drv->type = MAX2837_VARIANT; - max2837.gpio_enable = gpio->max283x_enable; - max2837.gpio_rx_enable = gpio->max283x_rx_enable; - max2837.gpio_tx_enable = gpio->max283x_tx_enable; - memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); - max2837_setup(&drv->drv.max2837); -#endif - break; - } + ) + IF_NOT_PRALINE ( + IF_H1_R9 ( + drv->type = MAX2839_VARIANT; + max2839.gpio_enable = gpio->max283x_enable; + max2839.gpio_rxtx = gpio->max283x_rx_enable; + memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); + max2839_setup(&drv->drv.max2839); + ) + IF_NOT_H1_R9 ( + drv->type = MAX2837_VARIANT; + max2837.gpio_enable = gpio->max283x_enable; + max2837.gpio_rx_enable = gpio->max283x_rx_enable; + max2837.gpio_tx_enable = gpio->max283x_tx_enable; + memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); + max2837_setup(&drv->drv.max2837); + ) + ) } /* Macros to simplify dispatch of variant-specific operations. */ diff --git a/firmware/common/mixer.c b/firmware/common/mixer.c index 9e1afb32..0706170b 100644 --- a/firmware/common/mixer.c +++ b/firmware/common/mixer.c @@ -24,11 +24,11 @@ #include #include "fixed_point.h" +#include "platform_detect.h" #include "platform_gpio.h" #if defined(RAD1O) #include "max2871.h" #else - #include "platform_detect.h" #include "rffc5071.h" #include "rffc5071_spi.h" #include "spi_bus.h" @@ -54,26 +54,18 @@ mixer_driver_t mixer = { void mixer_bus_setup(mixer_driver_t* const mixer) { -#if defined(RAD1O) (void) mixer; -#else - const platform_gpio_t* gpio = platform_gpio(); -#endif - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( + const platform_gpio_t* gpio = platform_gpio(); + rffc5071_spi_config = (rffc5071_spi_config_t){ .gpio_select = gpio->rffc5072_select, .gpio_clock = gpio->rffc5072_clock, .gpio_data = gpio->rffc5072_data, }; spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config); -#endif - break; - case MAX2871_VARIANT: - break; - } + ) } void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) @@ -83,106 +75,67 @@ void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) const platform_gpio_t* gpio = platform_gpio(); /* Mixer GPIO serial interface PinMux */ - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_PRALINE ( + mixer->rffc5071.gpio_ld = gpio->rffc5072_ld + ); + + IF_NOT_RAD1O ( mixer->rffc5071.gpio_reset = gpio->rffc5072_reset; - if (detected_platform() == BOARD_ID_PRALINE) { - #if defined(PRALINE) || defined(UNIVERSAL) - mixer->rffc5071.gpio_ld = gpio->rffc5072_ld; - #endif - } -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) + rffc5071_setup(&mixer->rffc5071); + ) + + IF_RAD1O ( mixer->max2871.gpio_vco_ce = gpio->vco_ce; mixer->max2871.gpio_vco_sclk = gpio->vco_sclk; mixer->max2871.gpio_vco_sdata = gpio->vco_sdata; mixer->max2871.gpio_vco_le = gpio->vco_le; mixer->max2871.gpio_synt_rfout_en = gpio->synt_rfout_en; mixer->max2871.gpio_vco_mux = gpio->vco_mux; -#endif - break; - } - - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) - rffc5071_setup(&mixer->rffc5071); -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) max2871_setup(&mixer->max2871); -#endif - break; - } + ) } fp_40_24_t mixer_set_frequency(mixer_driver_t* const mixer, fp_40_24_t lo, bool program) { - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( return rffc5071_set_frequency(&mixer->rffc5071, lo, program); -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) - return max2871_set_frequency(&mixer->max2871, lo, program); -#endif - break; - } + ) - return 0; + IF_RAD1O ( + return max2871_set_frequency(&mixer->max2871, lo, program); + ) } void mixer_enable(mixer_driver_t* const mixer) { - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( rffc5071_enable(&mixer->rffc5071); -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) + ) + + IF_RAD1O ( max2871_enable(&mixer->max2871); -#endif - break; - } + ) } void mixer_disable(mixer_driver_t* const mixer) { - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( rffc5071_disable(&mixer->rffc5071); -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) + ) + + IF_RAD1O ( max2871_disable(&mixer->max2871); -#endif - break; - } + ) } void mixer_set_gpo(mixer_driver_t* const mixer, uint8_t gpo) { -#if defined(RAD1O) - (void) gpo; -#endif - - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( rffc5071_set_gpo(&mixer->rffc5071, gpo); -#endif - break; - case MAX2871_VARIANT: - break; - } + ) + + IF_RAD1O ( + (void) mixer; + (void) gpo; + ) } diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 21ce3d7e..82bb6400 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -98,8 +98,7 @@ void operacake_sctimer_init(void) SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); uint8_t sct_clock_input; - if (detected_platform() != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( // Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12 SGPIO_OUT_MUX_CFG12 = SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode @@ -110,9 +109,8 @@ void operacake_sctimer_init(void) GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1 sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1; -#endif - } else { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( // Configure pin P6_4 as SCT_IN_6 scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); @@ -120,8 +118,7 @@ void operacake_sctimer_init(void) GIMA_CTIN_6_IN = 0x0 << 4; sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6; -#endif - } + ) // We configure this register first, because the user manual says to SCT_CONFIG |= SCT_CONFIG_UNIFY_32_BIT | SCT_CONFIG_CLKMODE_PRESCALED_BUS_CLOCK | diff --git a/firmware/common/platform_detect.h b/firmware/common/platform_detect.h index 4f0f0f43..f8bc53b9 100644 --- a/firmware/common/platform_detect.h +++ b/firmware/common/platform_detect.h @@ -31,6 +31,117 @@ #define PLATFORM_HACKRF1_R9 (1 << 3) #define PLATFORM_PRALINE (1 << 4) +/* clang-format off */ + +/* Helper macros for platform-specific code. */ +#if defined(UNIVERSAL) + #define IF_PRALINE(...) \ + if (detected_platform() == BOARD_ID_PRALINE) { __VA_ARGS__; } + #define IF_NOT_PRALINE(...) \ + if (detected_platform() != BOARD_ID_PRALINE) { __VA_ARGS__; } + #define IF_HACKRF_ONE(...) \ + switch (detected_platform()) { \ + case BOARD_ID_HACKRF1_OG: \ + case BOARD_ID_HACKRF1_R9: \ + { __VA_ARGS__; } \ + break; \ + default: \ + break; \ + } + #define IF_NOT_HACKRF_ONE(...) \ + switch (detected_platform()) { \ + case BOARD_ID_HACKRF1_OG: \ + case BOARD_ID_HACKRF1_R9: \ + break; \ + default: \ + { __VA_ARGS__; } \ + break; \ + } + #define IF_H1_R9(...) \ + if (detected_platform() == BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } + #define IF_NOT_H1_R9(...) \ + if (detected_platform() != BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } + #define IF_RAD1O(...) + #define IF_NOT_RAD1O(...) { __VA_ARGS__; } + #define IF_JAWBREAKER(...) + #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } + #define IF_H1_OR_RAD1O(...) IF_HACKRF_ONE(__VA_ARGS__) + #define IF_H1_OR_JAWBREAKER(...) IF_HACKRF_ONE(__VA_ARGS__) + #define IF_FOUR_LEDS(...) IF_PRALINE(__VA_ARGS__) + #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } +#elif defined(HACKRF_ONE) + #define IF_PRALINE(...) + #define IF_NOT_PRALINE(...) { __VA_ARGS__; } + #define IF_HACKRF_ONE(...) { __VA_ARGS__; } + #define IF_NOT_HACKRF_ONE(...) + #define IF_H1_R9(...) \ + if (detected_platform() == BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } + #define IF_NOT_H1_R9(...) \ + if (detected_platform() != BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } + #define IF_RAD1O(...) + #define IF_NOT_RAD1O(...) { __VA_ARGS__; } + #define IF_JAWBREAKER(...) + #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } + #define IF_H1_OR_RAD1O(...) { __VA_ARGS__; } + #define IF_H1_OR_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_FOUR_LEDS(...) + #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } +#elif defined(PRALINE) + #define IF_PRALINE(...) { __VA_ARGS__; } + #define IF_NOT_PRALINE(...) + #define IF_HACKRF_ONE(...) + #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } + #define IF_H1_R9(...) + #define IF_NOT_H1_R9(...) { __VA_ARGS__; } + #define IF_RAD1O(...) + #define IF_NOT_RAD1O(...) { __VA_ARGS__; } + #define IF_JAWBREAKER(...) + #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } + #define IF_H1_OR_RAD1O(...) + #define IF_H1_OR_JAWBREAKER(...) + #define IF_FOUR_LEDS(...) + #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } +#elif defined(RAD1O) + #define IF_PRALINE(...) + #define IF_NOT_PRALINE(...) { __VA_ARGS__; } + #define IF_HACKRF_ONE(...) + #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } + #define IF_H1_R9(...) + #define IF_NOT_H1_R9(...) { __VA_ARGS__; } + #define IF_RAD1O(...) { __VA_ARGS__; } + #define IF_NOT_RAD1O(...) + #define IF_JAWBREAKER(...) + #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_H1_OR_PRALINE(...) + #define IF_H1_OR_RAD1O(...) { __VA_ARGS__; } + #define IF_H1_OR_JAWBREAKER(...) + #define IF_FOUR_LEDS(...) { __VA_ARGS__; } + #define IF_EXPANSION_COMPATIBLE(...) +#elif defined(JAWBREAKER) + #define IF_PRALINE(...) + #define IF_NOT_PRALINE(...) { __VA_ARGS__; } + #define IF_HACKRF_ONE(...) + #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } + #define IF_H1_R9(...) + #define IF_NOT_H1_R9(...) { __VA_ARGS__; } + #define IF_RAD1O(...) + #define IF_NOT_RAD1O(...) { __VA_ARGS__; } + #define IF_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_NOT_JAWBREAKER(...) + #define IF_H1_OR_PRALINE(...) + #define IF_H1_OR_RAD1O(...) + #define IF_H1_OR_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_FOUR_LEDS(...) + #define IF_EXPANSION_COMPATIBLE(...) +#else + #error "No recognised platform defined" +#endif + +/* clang-format on */ + typedef enum { BOARD_ID_JELLYBEAN = 0, BOARD_ID_JAWBREAKER = 1, diff --git a/firmware/common/platform_gpio.c b/firmware/common/platform_gpio.c index f9a44903..a5826cf2 100644 --- a/firmware/common/platform_gpio.c +++ b/firmware/common/platform_gpio.c @@ -34,93 +34,61 @@ const platform_gpio_t* platform_gpio(void) return _platform_gpio; } - board_id_t board_id = detected_platform(); static platform_gpio_t gpio; /* LEDs */ gpio.led[0] = &GPIO2_1; gpio.led[1] = &GPIO2_2; gpio.led[2] = &GPIO2_8; - switch (board_id) { - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( gpio.led[3] = &GPIO5_26; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( gpio.led[3] = &GPIO4_6; -#endif - break; - default: - break; - } + ) /* Power Supply Control */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( gpio.gpio_1v2_enable = &GPIO4_7; gpio.gpio_3v3aux_enable_n = &GPIO5_15; -#endif - break; - default: + ) + IF_NOT_PRALINE ( gpio.gpio_1v8_enable = &GPIO3_6; - break; - } + ) /* MAX283x GPIO (XCVR_CTL / CS_XCVR) PinMux */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( gpio.max283x_select = &GPIO6_28; gpio.max283x_enable = &GPIO7_1; gpio.max283x_rx_enable = &GPIO7_2; gpio.max2831_rxhp = &GPIO6_29; gpio.max2831_ld = &GPIO4_11; -#endif - break; - default: + ) + IF_NOT_PRALINE ( gpio.max283x_select = &GPIO0_15; gpio.max283x_enable = &GPIO2_6; gpio.max283x_rx_enable = &GPIO2_5; gpio.max283x_tx_enable = &GPIO2_4; - break; - } + ) /* MAX5864 SPI chip select (AD_CS / CS_AD) GPIO PinMux */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( gpio.max5864_select = &GPIO6_30; -#endif - break; - default: + ) + IF_NOT_PRALINE ( gpio.max5864_select = &GPIO2_7; - break; - } + ) /* RF supply (VAA) control */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( gpio.vaa_disable = &GPIO2_9; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( gpio.vaa_disable = &GPIO4_1; -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( gpio.vaa_enable = &GPIO2_9; -#endif - break; - default: - break; - } + ) /* W25Q80BV Flash */ gpio.w25q80bv_hold = &GPIO1_14; @@ -128,10 +96,7 @@ const platform_gpio_t* platform_gpio(void) gpio.w25q80bv_select = &GPIO5_11; /* RF switch control */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( gpio.hp = &GPIO2_0; gpio.lp = &GPIO2_10; gpio.tx_mix_bp = &GPIO2_11; @@ -145,13 +110,12 @@ const platform_gpio_t* platform_gpio(void) gpio.amp_bypass = &GPIO0_14; gpio.rx_amp = &GPIO1_11; gpio.no_rx_amp_pwr = &GPIO1_12; - // HackRF One rev 9 - gpio.h1r9_rx = &GPIO0_7; - gpio.h1r9_no_ant_pwr = &GPIO2_4; -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_H1_R9 ( + gpio.h1r9_rx = &GPIO0_7; + gpio.h1r9_no_ant_pwr = &GPIO2_4; + ) + ) + IF_RAD1O ( gpio.tx_rx_n = &GPIO1_11; gpio.tx_rx = &GPIO0_14; gpio.by_mix = &GPIO1_12; @@ -163,118 +127,69 @@ const platform_gpio_t* platform_gpio(void) gpio.low_high_filt_n = &GPIO2_12; gpio.tx_amp = &GPIO2_15; gpio.rx_lna = &GPIO5_15; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( gpio.tx_en = &GPIO3_4; gpio.mix_en_n = &GPIO3_2; gpio.mix_en_n_r1_0 = &GPIO5_6; gpio.lpf_en = &GPIO4_8; gpio.rf_amp_en = &GPIO4_9; gpio.ant_bias_en_n = &GPIO1_12; -#endif - break; - case BOARD_ID_JELLYBEAN: - case BOARD_ID_JAWBREAKER: - break; - default: - break; - } + ) /* CPLD JTAG interface GPIO pins_FPGA config pins in Praline */ gpio.cpld_tck = &GPIO3_0; - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( gpio.fpga_cfg_creset = &GPIO2_11; gpio.fpga_cfg_cdone = &GPIO5_14; gpio.fpga_cfg_spi_cs = &GPIO2_10; -#endif - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) + ) + IF_NOT_PRALINE ( gpio.cpld_tdo = &GPIO5_18; - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_RAD1O: + IF_H1_OR_RAD1O ( gpio.cpld_tms = &GPIO3_4; gpio.cpld_tdi = &GPIO3_1; - break; - case BOARD_ID_JAWBREAKER: + IF_JAWBREAKER ( gpio.cpld_tms = &GPIO3_1; gpio.cpld_tdi = &GPIO3_4; - break; - default: - break; - } -#endif - break; - } - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + ) + ) + IF_EXPANSION_COMPATIBLE ( gpio.cpld_pp_tms = &GPIO1_1; gpio.cpld_pp_tdo = &GPIO1_8; -#endif - break; - default: - break; - } + ) /* Other CPLD interface GPIO pins */ - switch (board_id) { - case BOARD_ID_PRALINE: - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( gpio.trigger_enable = &GPIO5_12; -#endif - break; - } + ) gpio.q_invert = &GPIO0_13; /* RFFC5071 GPIO serial interface PinMux */ - switch (board_id) { - case BOARD_ID_JAWBREAKER: - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_NOT_RAD1O ( gpio.rffc5072_select = &GPIO2_13; gpio.rffc5072_clock = &GPIO5_6; gpio.rffc5072_data = &GPIO3_3; gpio.rffc5072_reset = &GPIO2_14; -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( gpio.vco_ce = &GPIO2_13; gpio.vco_sclk = &GPIO5_6; gpio.vco_sdata = &GPIO3_3; gpio.vco_le = &GPIO2_14; gpio.vco_mux = &GPIO5_25; gpio.synt_rfout_en = &GPIO3_5; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( gpio.rffc5072_select = &GPIO2_13; gpio.rffc5072_clock = &GPIO5_18; gpio.rffc5072_data = &GPIO4_14; gpio.rffc5072_reset = &GPIO2_14; gpio.rffc5072_ld = &GPIO6_25; -#endif - break; - default: - break; - } + ) -#if defined(PRALINE) || defined(UNIVERSAL) - switch (board_id) { - case BOARD_ID_PRALINE: + /* Praline */ + IF_PRALINE ( gpio.p2_ctrl0 = &GPIO7_3; gpio.p2_ctrl1 = &GPIO7_4; gpio.p1_ctrl0 = &GPIO0_14; @@ -285,50 +200,33 @@ const platform_gpio_t* platform_gpio(void) gpio.trigger_in = &GPIO6_26; gpio.trigger_out = &GPIO5_6; gpio.pps_out = &GPIO5_5; - break; - default: - break; - } -#endif + ) -#if defined(HACKRF_ONE) || defined(UNIVERSAL) /* HackRF One r9 clock control */ - switch (board_id) { - case BOARD_ID_HACKRF1_R9: + IF_H1_R9 ( gpio.h1r9_clkin_en = &GPIO5_15; gpio.h1r9_clkout_en = &GPIO0_9; gpio.h1r9_mcu_clk_en = &GPIO0_8; - break; - default: - break; - } - - switch (board_id) { - case BOARD_ID_HACKRF1_R9: gpio.h1r9_1v8_enable = &GPIO2_9; gpio.h1r9_vaa_disable = &GPIO3_6; gpio.h1r9_trigger_enable = &GPIO5_5; - break; - default: - break; - } -#endif + ) /* rad1o LCD */ -#if defined(RAD1O) - gpio.lcd_cs = &GPIO4_12; /* P9_0 */ - gpio.lcd_bl_en = &GPIO0_8; /* P1_1 */ - gpio.lcd_reset = &GPIO5_17; /* P9_4 */ -#endif + IF_RAD1O ( + gpio.lcd_cs = &GPIO4_12; /* P9_0 */ + gpio.lcd_bl_en = &GPIO0_8; /* P1_1 */ + gpio.lcd_reset = &GPIO5_17; /* P9_4 */ + ) /* Portapack */ -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) - gpio.io_stbx = &GPIO5_0; /* P2_0 */ - gpio.addr = &GPIO5_1; /* P2_1 */ - gpio.lcd_rdx = &GPIO5_4; /* P2_4 */ - gpio.lcd_wrx = &GPIO1_10; /* P2_9 */ - gpio.dir = &GPIO1_13; /* P2_13 */ -#endif + IF_EXPANSION_COMPATIBLE ( + gpio.io_stbx = &GPIO5_0; /* P2_0 */ + gpio.addr = &GPIO5_1; /* P2_1 */ + gpio.lcd_rdx = &GPIO5_4; /* P2_4 */ + gpio.lcd_wrx = &GPIO1_10; /* P2_9 */ + gpio.dir = &GPIO1_13; /* P2_13 */ + ) _platform_gpio = &gpio; diff --git a/firmware/common/platform_scu.c b/firmware/common/platform_scu.c index edd6ee0e..9d3b0c5d 100644 --- a/firmware/common/platform_scu.c +++ b/firmware/common/platform_scu.c @@ -36,68 +36,40 @@ const platform_scu_t* platform_scu(void) return _platform_scu; } - board_id_t board_id = detected_platform(); static platform_scu_t scu; /* LED PinMux */ scu.PINMUX_LED1 = SCU_PINMUX_LED1; /* GPIO2[1] on P4_1 */ scu.PINMUX_LED2 = SCU_PINMUX_LED2; /* GPIO2[2] on P4_2 */ scu.PINMUX_LED3 = SCU_PINMUX_LED3; /* GPIO2[8] on P6_12 */ - switch (board_id) { - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( scu.PINMUX_LED4 = (PB_6); /* GPIO5[26] on PB_6 */ -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.PINMUX_LED4 = (P8_6); /* GPIO4[6] on P8_6 */ -#endif - break; - default: - break; - } + ) /* Power Supply PinMux */ scu.PINMUX_EN1V8 = (P6_10); /* GPIO3[6] on P6_10 */ scu.PINMUX_EN1V2 = (P8_7); /* GPIO4[7] on P8_7 */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu.PINMUX_EN3V3_AUX_N = (P6_7); /* GPIO5[15] on P6_7 */ scu.PINMUX_EN3V3_OC_N = (P6_11); /* GPIO3[7] on P6_11 */ -#endif - break; - default: - break; - } + ) /* GPIO Input PinMux */ scu.PINMUX_BOOT0 = (P1_1); /* GPIO0[8] on P1_1 */ scu.PINMUX_BOOT1 = (P1_2); /* GPIO0[9] on P1_2 */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - break; - default: -#if !defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_NOT_HACKRF_ONE ( scu.PINMUX_BOOT2 = (P2_8); /* GPIO5[7] on P2_8 */ scu.PINMUX_BOOT3 = (P2_9); /* GPIO1[10] on P2_9 */ -#endif - break; - } + ) /* USB peripheral */ - switch (board_id) { - case BOARD_ID_JAWBREAKER: -#if defined(JAWBREAKER) + IF_JAWBREAKER ( scu.PINMUX_USB_LED0 = (P6_8); scu.PINMUX_USB_LED1 = (P6_7); -#endif - break; - default: - break; - } + ) /* SSP1 Peripheral PinMux */ scu.SSP1_CIPO = (P1_3); /* P1_3 */ @@ -107,37 +79,25 @@ const platform_scu_t* platform_scu(void) /* CPLD JTAG interface */ scu.PINMUX_CPLD_TCK = (P6_1); /* GPIO3[ 0] */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu.PINMUX_FPGA_CRESET = (P5_2); /* GPIO2[11] on P5_2 */ scu.PINMUX_FPGA_CDONE = (P4_10); /* GPIO5[14] */ scu.PINMUX_FPGA_SPI_CS = (P5_1); /* GPIO2[10] */ -#endif - break; - default: - break; - } -#if !defined(PRALINE) || defined(UNIVERSAL) - scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ - switch (board_id) { - case BOARD_ID_RAD1O: - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: + ) + IF_NOT_PRALINE ( + scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ + ) + IF_H1_OR_RAD1O ( scu.PINMUX_CPLD_TMS = (P6_5); /* GPIO3[ 4] */ scu.PINMUX_CPLD_TDI = (P6_2); /* GPIO3[ 1] */ - break; - default: + ) + IF_JAWBREAKER ( scu.PINMUX_CPLD_TMS = (P6_2); /* GPIO3[ 1] */ scu.PINMUX_CPLD_TDI = (P6_5); /* GPIO3[ 4] */ - break; - } -#endif + ) /* CPLD SGPIO interface */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu.PINMUX_SGPIO0 = (P0_0); scu.PINMUX_SGPIO1 = (P0_1); scu.PINMUX_SGPIO2 = (P1_15); @@ -169,10 +129,8 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#endif - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) + ) + IF_NOT_PRALINE ( scu.PINMUX_SGPIO0 = (P0_0); scu.PINMUX_SGPIO1 = (P0_1); scu.PINMUX_SGPIO2 = (P1_15); @@ -204,22 +162,16 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#endif - break; - } + ) scu.TRIGGER_EN = (P4_8); /* GPIO5[12] on P4_8 */ /* MAX283x GPIO (XCVR_CTL) PinMux */ - switch (board_id) { - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( scu.XCVR_RXHP = P8_1; /* GPIO[] on P8_1 */ scu.XCVR_B6 = P8_2; /* GPIO[] on P8_2 */ scu.XCVR_B7 = P9_3; /* GPIO[] on P9_3 */ -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.XCVR_ENABLE = PE_1; /* GPIO7[1] on PE_1 */ scu.XCVR_RXENABLE = PE_2; /* GPIO7[2] on PE_2 */ scu.XCVR_CS = PD_14; /* GPIO6[28] on PD_14 */ @@ -232,12 +184,8 @@ const platform_scu_t* platform_scu(void) scu.XCVR_RXHP_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.XCVR_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | SCU_CONF_EPD_EN_PULLDOWN | SCU_CONF_EPUN_DIS_PULLUP); -#endif - break; - case BOARD_ID_JAWBREAKER: - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + ) + IF_H1_OR_JAWBREAKER ( scu.XCVR_ENABLE = P4_6; /* GPIO2[6] on P4_6 */ scu.XCVR_RXENABLE = P4_5; /* GPIO2[5] on P4_5 */ scu.XCVR_TXENABLE = P4_4; /* GPIO2[4] on P4_4 */ @@ -247,32 +195,20 @@ const platform_scu_t* platform_scu(void) scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST); scu.XCVR_TXENABLE_PINCFG = (SCU_GPIO_FAST); scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST); -#endif - break; - default: - break; - } + ) /* MAX5864 SPI chip select (AD_CS) GPIO PinMux */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu.AD_CS = (PD_16); /* GPIO6[30] on PD_16 */ scu.AD_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#endif - break; - default: + ) + IF_NOT_PRALINE ( scu.AD_CS = (P5_7); /* GPIO2[7] on P5_7 */ scu.AD_CS_PINCFG = (SCU_GPIO_FAST); - break; - } + ) /* RFFC5071 GPIO serial interface PinMux */ - switch (board_id) { - case BOARD_ID_JAWBREAKER: - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_OR_JAWBREAKER ( scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ scu.MIXER_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ scu.MIXER_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ @@ -280,10 +216,8 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ scu.MIXER_SCLK = (P9_5); /* GPIO5[18] on P9_5 */ scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ @@ -293,10 +227,8 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( scu.VCO_CE = (P5_4); /* GPIO2[13] on P5_4 */ scu.VCO_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ scu.VCO_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ @@ -304,44 +236,23 @@ const platform_scu_t* platform_scu(void) scu.VCO_MUX = (PB_5); /* GPIO5[25] on PB_5 */ scu.MIXER_EN = (P6_8); /* GPIO5[16] on P6_8 */ scu.SYNT_RFOUT_EN = (P6_9); /* GPIO3[5] on P6_9 */ -#endif - break; - default: - break; - } + ) /* RF LDO control */ - switch (board_id) { - case BOARD_ID_JAWBREAKER: -#if defined(JAWBREAKER) + IF_JAWBREAKER ( scu.RF_LDO_ENABLE = (P5_0); /* GPIO2[9] on P5_0 */ -#endif - break; - default: - break; - } + ) /* RF supply (VAA) control */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( scu.NO_VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.NO_VAA_ENABLE = P8_1; /* GPIO4[1] on P8_1 */ -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( scu.VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ -#endif - break; - default: - break; - } + ) /* SPI flash */ scu.SSP0_CIPO = (P3_6); @@ -352,10 +263,7 @@ const platform_scu_t* platform_scu(void) scu.FLASH_WP = (P3_5); /* GPIO1[15] on P3_5 */ /* RF switch control */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( scu.HP = P4_0; /* GPIO2[0] on P4_0 */ scu.LP = P5_1; /* GPIO2[10] on P5_1 */ scu.TX_MIX_BP = P5_2; /* GPIO2[11] on P5_2 */ @@ -369,10 +277,8 @@ const platform_scu_t* platform_scu(void) scu.AMP_BYPASS = P2_10; /* GPIO0[14] on P2_10 */ scu.RX_AMP = P2_11; /* GPIO1[11] on P2_11 */ scu.NO_RX_AMP_PWR = P2_12; /* GPIO1[12] on P2_12 */ -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( scu.BY_AMP = P1_7; /* GPIO1[0] on P1_7 */ scu.BY_AMP_N = P2_5; /* GPIO5[5] on P2_5 */ scu.TX_RX = P2_10; /* GPIO0[14] on P2_10 */ @@ -383,10 +289,8 @@ const platform_scu_t* platform_scu(void) scu.LOW_HIGH_FILT_N = P5_3; /* GPIO2[12] on P5_3 */ scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ scu.RX_LNA = P6_7; /* GPIO5[15] on P6_7 */ -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.TX_EN = P6_5; /* GPIO3[4] on P6_5 */ scu.MIX_EN_N = P6_3; /* GPIO3[2] on P6_3 */ scu.MIX_EN_N_R1_0 = P2_6; /* GPIO5[6] on P2_6 */ @@ -394,16 +298,10 @@ const platform_scu_t* platform_scu(void) scu.RF_AMP_EN = PA_2; /* GPIO4[9] on PA_2 */ scu.ANT_BIAS_EN_N = P2_12; /* GPIO1[12] on P2_12 */ scu.ANT_BIAS_OC_N = P2_11; /* GPIO1[11] on P2_11 */ -#endif - break; - default: - break; - } + ) /* Praline */ -#if defined(PRALINE) || defined(UNIVERSAL) - switch (board_id) { - case BOARD_ID_PRALINE: + IF_PRALINE ( scu.P2_CTRL0 = (PE_3); /* GPIO7[3] on PE_3 */ scu.P2_CTRL1 = (PE_4); /* GPIO7[4] on PE_4 */ scu.P1_CTRL0 = (P2_10); /* GPIO0[14] on P2_10 */ @@ -425,16 +323,10 @@ const platform_scu_t* platform_scu(void) scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - break; - default: - break; - } -#endif + ) /* HackRF One r9 */ -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - switch (board_id) { - case BOARD_ID_HACKRF1_R9: + IF_H1_R9 ( scu.H1R9_CLKIN_EN = P6_7; /* GPIO5[15] on P6_7 */ scu.H1R9_CLKOUT_EN = P1_2; /* GPIO0[9] on P1_2 = has boot pull-down; */ scu.H1R9_MCU_CLK_EN = P1_1; /* GPIO0[8] on P1_1 = has boot pull-up; */ @@ -443,54 +335,50 @@ const platform_scu_t* platform_scu(void) scu.H1R9_EN1V8 = P5_0; /* GPIO2[9] on P5_0 */ scu.H1R9_NO_VAA_EN = P6_10; /* GPIO3[6] on P6_10 */ scu.H1R9_TRIGGER_EN = P2_5; /* GPIO5[5] on P2_5 */ - break; - default: - break; - } -#endif + ) /* Expansion headers */ -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) - scu.PINMUX_PP_D0 = (P7_0); /* GPIO3[8] */ - scu.PINMUX_PP_D1 = (P7_1); /* GPIO3[9] */ - scu.PINMUX_PP_D2 = (P7_2); /* GPIO3[10] */ - scu.PINMUX_PP_D3 = (P7_3); /* GPIO3[11] */ - scu.PINMUX_PP_D4 = (P7_4); /* GPIO3[12] */ - scu.PINMUX_PP_D5 = (P7_5); /* GPIO3[13] */ - scu.PINMUX_PP_D6 = (P7_6); /* GPIO3[14] */ - scu.PINMUX_PP_D7 = (P7_7); /* GPIO3[15] */ + IF_EXPANSION_COMPATIBLE ( + scu.PINMUX_PP_D0 = (P7_0); /* GPIO3[8] */ + scu.PINMUX_PP_D1 = (P7_1); /* GPIO3[9] */ + scu.PINMUX_PP_D2 = (P7_2); /* GPIO3[10] */ + scu.PINMUX_PP_D3 = (P7_3); /* GPIO3[11] */ + scu.PINMUX_PP_D4 = (P7_4); /* GPIO3[12] */ + scu.PINMUX_PP_D5 = (P7_5); /* GPIO3[13] */ + scu.PINMUX_PP_D6 = (P7_6); /* GPIO3[14] */ + scu.PINMUX_PP_D7 = (P7_7); /* GPIO3[15] */ - scu.PINMUX_PP_LCD_TE = (P2_3); /* GPIO5[3] on P2_3 */ - scu.PINMUX_PP_LCD_RDX = (P2_4); /* GPIO5[4] on P2_4 */ - scu.PINMUX_PP_UNUSED = (P2_8); /* GPIO5[7] on P2_8 */ - scu.PINMUX_PP_LCD_WRX = (P2_9); /* GPIO1[10] on P2_9 */ - scu.PINMUX_PP_DIR = (P2_13); /* GPIO1[13] on P2_13 */ + scu.PINMUX_PP_LCD_TE = (P2_3); /* GPIO5[3] on P2_3 */ + scu.PINMUX_PP_LCD_RDX = (P2_4); /* GPIO5[4] on P2_4 */ + scu.PINMUX_PP_UNUSED = (P2_8); /* GPIO5[7] on P2_8 */ + scu.PINMUX_PP_LCD_WRX = (P2_9); /* GPIO1[10] on P2_9 */ + scu.PINMUX_PP_DIR = (P2_13); /* GPIO1[13] on P2_13 */ - scu.PINMUX_PP_TDO = (P1_5); /* GPIO1[8] */ - scu.PINMUX_SD_POW = (P1_5); /* GPIO1[8] */ - scu.PINMUX_SD_CMD = (P1_6); /* GPIO1[9] */ - scu.PINMUX_PP_TMS = (P1_8); /* GPIO1[1] */ - scu.PINMUX_SD_VOLT0 = (P1_8); /* GPIO1[1] */ - scu.PINMUX_SD_DAT0 = (P1_9); /* GPIO1[2] */ - scu.PINMUX_SD_DAT1 = (P1_10); /* GPIO1[3] */ - scu.PINMUX_SD_DAT2 = (P1_11); /* GPIO1[4] */ - scu.PINMUX_SD_DAT3 = (P1_12); /* GPIO1[5] */ - scu.PINMUX_SD_CD = (P1_13); /* GPIO1[6] */ + scu.PINMUX_PP_TDO = (P1_5); /* GPIO1[8] */ + scu.PINMUX_SD_POW = (P1_5); /* GPIO1[8] */ + scu.PINMUX_SD_CMD = (P1_6); /* GPIO1[9] */ + scu.PINMUX_PP_TMS = (P1_8); /* GPIO1[1] */ + scu.PINMUX_SD_VOLT0 = (P1_8); /* GPIO1[1] */ + scu.PINMUX_SD_DAT0 = (P1_9); /* GPIO1[2] */ + scu.PINMUX_SD_DAT1 = (P1_10); /* GPIO1[3] */ + scu.PINMUX_SD_DAT2 = (P1_11); /* GPIO1[4] */ + scu.PINMUX_SD_DAT3 = (P1_12); /* GPIO1[5] */ + scu.PINMUX_SD_CD = (P1_13); /* GPIO1[6] */ - scu.PINMUX_PP_IO_STBX = (P2_0); /* GPIO5[0] */ - scu.PINMUX_PP_ADDR = (P2_1); /* GPIO5[1] */ - scu.PINMUX_U0_TXD = (P2_0); /* GPIO5[0] */ - scu.PINMUX_U0_RXD = (P2_1); /* GPIO5[1] */ + scu.PINMUX_PP_IO_STBX = (P2_0); /* GPIO5[0] */ + scu.PINMUX_PP_ADDR = (P2_1); /* GPIO5[1] */ + scu.PINMUX_U0_TXD = (P2_0); /* GPIO5[0] */ + scu.PINMUX_U0_RXD = (P2_1); /* GPIO5[1] */ - scu.PINMUX_GPIO3_8 = (P7_0); /* GPIO3[8] */ - scu.PINMUX_GPIO3_9 = (P7_1); /* GPIO3[9] */ - scu.PINMUX_GPIO3_10 = (P7_2); /* GPIO3[10] */ - scu.PINMUX_GPIO3_11 = (P7_3); /* GPIO3[11] */ - scu.PINMUX_GPIO3_12 = (P7_4); /* GPIO3[12] */ - scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ - scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ - scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ -#endif + scu.PINMUX_GPIO3_8 = (P7_0); /* GPIO3[8] */ + scu.PINMUX_GPIO3_9 = (P7_1); /* GPIO3[9] */ + scu.PINMUX_GPIO3_10 = (P7_2); /* GPIO3[10] */ + scu.PINMUX_GPIO3_11 = (P7_3); /* GPIO3[11] */ + scu.PINMUX_GPIO3_12 = (P7_4); /* GPIO3[12] */ + scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ + scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ + scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ + ) scu.PINMUX_ISP = (P2_7); /* GPIO0[7] */ diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 2a6a46c7..584934cb 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -200,22 +200,18 @@ static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank) case TRANSCEIVER_MODE_SS: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) { - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( fpga_set_tx_interpolation_ratio(&fpga, n); -#endif - } + ) radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX] = n; } break; default: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) { - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( fpga_set_rx_decimation_ratio(&fpga, n); -#endif - } + ) radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX] = n; } } @@ -366,7 +362,9 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) const uint64_t requested_if = bank[RADIO_FREQUENCY_IF]; const uint64_t requested_lo = bank[RADIO_FREQUENCY_LO]; const uint64_t requested_img_reject = bank[RADIO_IMAGE_REJECT]; +#if defined(PRALINE) || defined(UNIVERSAL) const uint64_t requested_rotation = bank[RADIO_ROTATION]; +#endif const uint64_t applied_rf = radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_RF]; const uint64_t applied_if = radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_IF]; @@ -405,7 +403,7 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) if (requested_lo != RADIO_UNSET) { freq_lo = mixer_set_frequency(&mixer, freq_lo, false); } - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( if (requested_rotation != RADIO_UNSET) { rotation = requested_rotation; } @@ -421,9 +419,10 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) default: rotation = 0; } - } else { + ) + IF_NOT_PRALINE ( rotation = 0; - } + ) /* Handle requested RF (auto-tune). */ if (requested_rf != RADIO_UNSET) { @@ -433,8 +432,7 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) freq_rf = restrict_rf(requested_rf, img_reject); /* Look up settings appropriate for requested RF. */ - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( const tune_config_t* tune_config = select_tune_config(opmode, freq_rf); if (requested_rotation == RADIO_UNSET) { @@ -450,15 +448,15 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) } } high_lo = tune_config->high_lo; -#endif - } else { + ) + IF_NOT_PRALINE ( /* Use graduated or fixed IF on older platforms. */ if (requested_if == RADIO_UNSET) { freq_if = select_graduated_if(freq_rf, img_reject); } high_lo = (img_reject == RF_PATH_FILTER_LOW_PASS); analog_rf = freq_rf; - } + ) /* Compute precise LO. This is done first because the mixer LO is set in coarse steps. */ if (requested_lo == RADIO_UNSET) { @@ -514,11 +512,9 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) new_freq = true; } if ((rotation != applied_rotation) && (rotation != RADIO_UNSET)) { - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); -#endif - } + ) radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION] = rotation; new_freq = true; } @@ -554,9 +550,9 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { hackrf_ui()->set_frequency(freq_rf / FP_ONE_MHZ); } -#if defined(PRALINE) || defined(HACKRF_ONE) - operacake_set_range(freq_rf / FP_ONE_MHZ); -#endif + IF_EXPANSION_COMPATIBLE ( + operacake_set_range(freq_rf / FP_ONE_MHZ); + ) } return new_freq; } @@ -593,8 +589,7 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE]; } - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( /* Praline legacy mode always sets baseband bandwidth automatically. */ (void) bank; uint32_t lpf_bandwidth = auto_bandwidth(radio, opmode); @@ -640,9 +635,8 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) hpf_bandwidth; new_bw = true; } -#endif - } else { -#if !defined(PRALINE) || defined(UNIVERSAL) + ) + IF_NOT_PRALINE ( uint64_t lpf_bandwidth; lpf_bandwidth = bank[RADIO_XCVR_TX_LPF]; if (lpf_bandwidth == RADIO_UNSET) { @@ -678,8 +672,7 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) lpf_bandwidth; new_bw = true; } -#endif - } + ) return new_bw; } @@ -815,13 +808,7 @@ static bool radio_update_trigger(radio_t* const radio, uint64_t* bank) static bool radio_update_dc_block(radio_t* const radio, uint64_t* bank) { -#if !defined(PRALINE) && !defined(UNIVERSAL) - (void) radio; - (void) bank; - return false; -#endif - - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( const uint64_t requested = bank[RADIO_DC_BLOCK]; bool enable = requested; @@ -834,14 +821,14 @@ static bool radio_update_dc_block(radio_t* const radio, uint64_t* bank) return false; } -#if defined(PRALINE) || defined(UNIVERSAL) fpga_set_rx_dc_block_enable(&fpga, enable); -#endif radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; return true; - } else { - return false; - } + ) + + (void) radio; + (void) bank; + return false; } bool radio_update(radio_t* const radio) diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 68e05878..d5d4aec9 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -298,35 +298,19 @@ static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) { -#if defined(JAWBREAKER) - (void) rf_path; -#endif - - switch (detected_platform()) { - case BOARD_ID_JAWBREAKER: -#if defined(JAWBREAKER) + IF_JAWBREAKER ( + (void) rf_path; mixer_set_gpo(&mixer, gpo); -#endif - break; - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + ) + IF_HACKRF_ONE ( switchctrl_set_hackrf_one(rf_path, gpo); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( switchctrl_set_praline(rf_path, gpo); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( switchctrl_set_rad1o(rf_path, gpo); -#endif - break; - default: - break; - } + ) } void rf_path_pin_setup(rf_path_t* const rf_path) @@ -335,15 +319,11 @@ void rf_path_pin_setup(rf_path_t* const rf_path) (void) rf_path; #endif - board_id_t board_id = detected_platform(); #if !defined(JAWBREAKER) const platform_scu_t* scu = platform_scu(); #endif - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( /* Configure RF switch control signals */ // clang-format off scu_pinmux(scu->HP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); @@ -358,7 +338,7 @@ void rf_path_pin_setup(rf_path_t* const rf_path) scu_pinmux(scu->RX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux(scu->NO_RX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); // clang-format on - if (board_id == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( scu_pinmux(scu->H1R9_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux( scu->H1R9_NO_ANT_PWR, @@ -368,12 +348,13 @@ void rf_path_pin_setup(rf_path_t* const rf_path) scu_pinmux( scu->H1R9_NO_VAA_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } else { + ) + IF_NOT_H1_R9 ( scu_pinmux(scu->TX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu_pinmux(scu->RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); gpio_output(rf_path->gpio_tx); scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } + ) /* * Safe (initial) switch settings turn off both amplifiers and antenna port @@ -394,10 +375,8 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_no_tx_amp_pwr); gpio_output(rf_path->gpio_mix_bypass); gpio_output(rf_path->gpio_rx); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( /* Configure RF switch control signals */ // clang-format off scu_pinmux(scu->BY_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); @@ -434,10 +413,9 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_low_high_filt_n); gpio_output(rf_path->gpio_tx_amp); gpio_output(rf_path->gpio_rx_lna); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + + IF_PRALINE ( /* Configure RF switch control signals */ scu_pinmux(scu->TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); board_rev_t rev = detected_revision(); @@ -468,17 +446,11 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_mix_en_n); gpio_output(rf_path->gpio_lpf_en); gpio_output(rf_path->gpio_rf_amp_en); -#endif - break; - default: - break; - } + ) } void rf_path_init(rf_path_t* const rf_path) { - board_id_t board_id = detected_platform(); - ssp1_set_mode_max5864(); max5864_setup(&max5864); max5864_shutdown(&max5864); @@ -487,22 +459,14 @@ void rf_path_init(rf_path_t* const rf_path) max283x_setup(&max283x); max283x_start(&max283x); - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - // On HackRF One, the mixer is now set up earlier in boot. - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( mixer_setup(&mixer, MAX2871_VARIANT); -#endif - break; - default: -#if !defined(RAD1O) - mixer_setup(&mixer, RFFC5071_VARIANT); -#endif - break; - } + ) + IF_NOT_RAD1O ( + IF_NOT_HACKRF_ONE ( + mixer_setup(&mixer, RFFC5071_VARIANT); + ) + ) rf_path->switchctrl = SWITCHCTRL_SAFE; } diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index 89befe98..fbeb2d93 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -110,19 +110,15 @@ void rffc5071_init(rffc5071_driver_t* const drv) */ void rffc5071_setup(rffc5071_driver_t* const drv) { - board_id_t board_id = detected_platform(); - gpio_set(drv->gpio_reset); gpio_output(drv->gpio_reset); - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( /* Configure mixer PLL lock detect pin */ const platform_scu_t* scu = platform_scu(); scu_pinmux(scu->MIXER_LD, scu->MIXER_LD_PINCFG); gpio_input(drv->gpio_ld); -#endif - } + ) rffc5071_init(drv); @@ -140,16 +136,9 @@ void rffc5071_setup(rffc5071_driver_t* const drv) set_RFFC5071_FULLD(drv, 0); set_RFFC5071_MODE(drv, 1); - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: - /* Enable GPO Lock output signal */ + IF_H1_OR_PRALINE ( set_RFFC5071_LOCK(drv, 1); - break; - default: - break; - } + ) /* Enable reference oscillator standby */ set_RFFC5071_REFST(drv, 1); @@ -192,17 +181,15 @@ void rffc5071_lock_test(rffc5071_driver_t* const drv) bool rffc5071_check_lock(rffc5071_driver_t* const drv) { - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( return gpio_read(drv->gpio_ld); -#else - return false; -#endif - } else { + ) + IF_NOT_PRALINE ( set_RFFC5071_READSEL(drv, 0b0001); rffc5071_regs_commit(drv); return !!(rffc5071_reg_read(drv, RFFC5071_READBACK_REG) & 0x8000); - } + ) + return false; } static uint16_t rffc5071_spi_read(rffc5071_driver_t* const drv, uint8_t r) @@ -382,9 +369,9 @@ bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) // // For all other boards we'll just return true to avoid a situation where a // a caller is waiting for a lock signal that will never be detected. - if (detected_platform() != BOARD_ID_PRALINE) { + IF_NOT_PRALINE ( return true; - } + ) // The RFFC5072 can be configured to output PLL lock status on // GPO4. The lock detect signal is produced by a window detector @@ -451,9 +438,7 @@ bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) uint8_t rsm_state = (rb >> 11) & 0b11111; // get gpo4 lock detect signal - #if defined(PRALINE) || defined(UNIVERSAL) bool gpo4_ld = gpio_read(drv->gpio_ld); - #endif // parse state switch (rsm_state) { diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index ac755d51..86b6c58e 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -57,27 +57,24 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config) scu_pinmux(scu->PINMUX_SGPIO14, scu->PINMUX_SGPIO14_PINCFG); /* GPIO5[13] */ scu_pinmux(scu->PINMUX_SGPIO15, scu->PINMUX_SGPIO15_PINCFG); /* GPIO5[14] */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( scu_pinmux( scu->H1R9_TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[5] */ -#endif - } else { + ) + IF_NOT_H1_R9 ( scu_pinmux( scu->TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[12] */ - } + ) sgpio_cpld_set_mixer_invert(config, 0); gpio_output(config->gpio_q_invert); - if (detected_platform() != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( trigger_enable(false); gpio_output(config->gpio_trigger_enable); -#endif - } + ) } void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index b50aad81..dbedddf3 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -235,7 +235,6 @@ void si5351c_configure_multisynth( void si5351c_configure_clock_control(si5351c_driver_t* const drv) { uint8_t clkout_ctrl; - board_id_t board_id = detected_platform(); if (clkout_enabled) { clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | @@ -270,8 +269,7 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ }; - if (board_id == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); @@ -282,10 +280,8 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) data[4] = SI5351C_CLK_POWERDOWN; data[5] = SI5351C_CLK_POWERDOWN; data[6] = SI5351C_CLK_POWERDOWN; -#endif - } - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( /* CLK0: AFE_CLK */ data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | @@ -315,8 +311,7 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) /* CLK2: MCU_CLK */ data[3] = SI5351C_CLK_POWERDOWN; } -#endif - } + ) si5351c_write(drv, data, sizeof(data)); } @@ -331,11 +326,10 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) /* 7: Clock to CPU is deactivated as it is not used and creates noise */ /* 3: External clock output is deactivated by default */ - board_id_t board_id = detected_platform(); uint8_t clkout = 3; - uint8_t value; + uint8_t value = 0; - if (board_id == BOARD_ID_PRALINE) { + IF_PRALINE ( value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); @@ -345,21 +339,22 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) } else { value |= SI5351C_CLK_DISABLE(2); } - } else { + ) + IF_NOT_PRALINE ( value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); /* HackRF One r9 has only three clock generator outputs. */ - if (board_id == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( clkout = 2; value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); - } - } + ) + ) value |= (clkout_enabled) ? SI5351C_CLK_ENABLE(clkout) : SI5351C_CLK_DISABLE(clkout); @@ -367,16 +362,14 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) si5351c_write(drv, data, sizeof(data)); outputs_disabled = value; - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( const platform_gpio_t* gpio = platform_gpio(); if (clkout_enabled) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); } -#endif - } + ) } void si5351c_set_int_mode( @@ -406,8 +399,7 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source return; } si5351c_disable_all_outputs(drv); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) + IF_H1_R9 ( /* * HackRF One r9 always uses PLL A on the XTAL input * but externally switches that input to CLKIN. @@ -418,10 +410,10 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source } else { gpio_clear(platform_gpio()->h1r9_clkin_en); } -#endif - } else { + ) + IF_NOT_H1_R9 ( si5351c_configure_pll_sources(drv, source); - } + ) si5351c_configure_pll_multisynth(drv, source); active_clock_source = source; si5351c_reset_pll(drv, SI5351C_PLL_BOTH); @@ -476,8 +468,7 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -495,8 +486,7 @@ void si5351c_init(si5351c_driver_t* const drv) scu_pinmux(scu->H1R9_MCU_CLK_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); gpio_clear(gpio->h1r9_mcu_clk_en); gpio_output(gpio->h1r9_mcu_clk_en); -#endif - } + ) } /* diff --git a/firmware/common/tuning.c b/firmware/common/tuning.c index 9c863beb..347be0ae 100644 --- a/firmware/common/tuning.c +++ b/firmware/common/tuning.c @@ -38,12 +38,10 @@ static fp_40_24_t max_bypass_freq = FP_MHZ(2740); void tuning_setup(void) { -#if defined(PRALINE) || defined(UNIVERSAL) - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( min_bypass_freq = FP_MHZ(2320); max_bypass_freq = FP_MHZ(2580); - } -#endif + ) } fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) @@ -52,12 +50,13 @@ fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) switch (img_reject) { case RF_PATH_FILTER_LOW_PASS: - if (detected_platform() == BOARD_ID_RAD1O) { + IF_RAD1O ( freq_if = FP_MHZ(2300); - } else { + ) + IF_NOT_RAD1O ( /* IF is graduated from 2650 MHz to 2340 MHz */ freq_if = FP_MHZ(2650) - (freq_rf / 7); - } + ) break; case RF_PATH_FILTER_HIGH_PASS: if (freq_rf < MID1_HP_FREQ) { diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index fb441339..e2f75868 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -319,20 +319,9 @@ int main(void) } delay_us_at_mhz(10000, 96); pin_setup(); - if (board_id != BOARD_ID_PRALINE) { - enable_1v8_power(); - if (board_id != BOARD_ID_RAD1O) { - /* - * On rad1o, the clock generator power supply comes from the RF supply - * which is enabled later. On H1 and Jawbreaker, the clock generator is - * on the main 3V3 supply. - */ - clock_gen_init(); - } - } else { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( enable_3v3aux_power(); - #if !defined(DFU_MODE) && !defined(RAM_MODE) +#if !defined(DFU_MODE) && !defined(RAM_MODE) enable_1v2_power(); enable_rf_power(); /* @@ -340,59 +329,58 @@ int main(void) * which is enabled when 1V2FPGA is turned on. */ clock_gen_init(); - #endif #endif - } + ) + IF_NOT_PRALINE ( + enable_1v8_power(); + IF_NOT_RAD1O ( + /* + * On rad1o, the clock generator power supply comes from the RF supply + * which is enabled later. On H1 and Jawbreaker, the clock generator is + * on the main 3V3 supply. + */ + clock_gen_init(); + ) + ) tuning_setup(); - if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( // Set up mixer before enabling RF power, because its // GPO is used to control the antenna bias tee. mixer_setup(&mixer, RFFC5071_VARIANT); -#endif - } - if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9 || - board_id == BOARD_ID_RAD1O) { -#if defined(RAD1O) || defined(HACKRF_ONE) || defined(UNIVERSAL) + ) + IF_H1_OR_RAD1O ( enable_rf_power(); -#endif - } - if (board_id == BOARD_ID_RAD1O) { + ) + IF_RAD1O ( clock_gen_init(); - } + ) cpu_clock_init(); /* Wake the M0 */ ipc_halt_m0(); ipc_start_m0((uint32_t) &__ram_m0_start__); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( if (!cpld_jtag_sram_load(&jtag_cpld)) { halt_and_flash(6000000); } -#endif - } else { -#if defined(PRALINE) || defined(UNIVERSAL) - #if defined(DFU_MODE) || defined(RAM_MODE) + ) + IF_PRALINE ( +#if defined(DFU_MODE) || defined(RAM_MODE) selftest.fpga_image_load = SKIPPED; selftest.report.pass = false; - #else +#else fpga_image_load(&fpga_loader, 0); - #endif +#endif delay_us_at_mhz(100, 204); fpga_spi_selftest(); fpga_sgpio_selftest(); -#endif - } + ) radio_init(&radio); - if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9 || - board_id == BOARD_ID_PRALINE) { -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( portapack_init(); -#endif - } + ) #ifndef DFU_MODE usb_set_descriptor_by_serial_number(); @@ -401,35 +389,22 @@ int main(void) usb_set_configuration_changed_cb(usb_configuration_changed); usb_peripheral_reset(); - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( memcpy(&usb_device, &usb_device_hackrf_one, sizeof(usb_device_hackrf_one)); -#endif - break; - case BOARD_ID_JAWBREAKER: -#if defined(JAWBREAKER) + ) + IF_JAWBREAKER ( memcpy(&usb_device, &usb_device_jawbreaker, sizeof(usb_device_jawbreaker)); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( memcpy(&usb_device, &usb_device_rad1o, sizeof(usb_device_rad1o)); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( memcpy(&usb_device, &usb_device_praline, sizeof(usb_device_praline)); -#endif - break; - default: - break; - }; + ) usb_device_init(0, &usb_device); usb_queue_init(&usb_endpoint_control_out_queue); @@ -452,11 +427,9 @@ int main(void) rffc5071_lock_test(&mixer.rffc5071); #endif - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( fpga_if_xcvr_selftest(); -#endif - } + ) if (da7219_detect()) { operacake_skip_i2c_address(DA7219_ADDRESS); @@ -503,11 +476,9 @@ int main(void) sweep_mode(request.seq); break; case TRANSCEIVER_MODE_CPLD_UPDATE: - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( cpld_update(); -#endif - } + ) break; default: break; diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index 8e64ad7f..eb91e5c9 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -131,8 +131,7 @@ usb_request_status_t usb_vendor_request_reset( if (stage == USB_TRANSFER_STAGE_SETUP) { pin_shutdown(); clock_gen_shutdown(); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( /* * Set boot pins as inputs so that the bootloader reads them * correctly after the reset. @@ -141,8 +140,7 @@ usb_request_status_t usb_vendor_request_reset( gpio_input(gpio->h1r9_mcu_clk_en); gpio_input(gpio->h1r9_clkout_en); gpio_input(gpio->h1r9_rx); -#endif - } + ) wwdt_reset(100000); diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index b74589ec..bf7e4985 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -142,64 +142,60 @@ usb_request_status_t usb_vendor_request_write_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { -#if defined(RAD1O) - (void) endpoint; - (void) stage; - if (detected_platform() == BOARD_ID_RAD1O) { + IF_RAD1O ( + (void) endpoint; + (void) stage; return USB_REQUEST_STATUS_STALL; - } -#endif + ) - if (stage == USB_TRANSFER_STAGE_SETUP) { -#if !defined(RAD1O) - if (endpoint->setup.index < RFFC5071_NUM_REGS) { - rffc5071_reg_write( - &mixer.rffc5071, - endpoint->setup.index, - endpoint->setup.value); - usb_transfer_schedule_ack(endpoint->in); - return USB_REQUEST_STATUS_OK; + IF_NOT_RAD1O ( + if (stage == USB_TRANSFER_STAGE_SETUP) { + if (endpoint->setup.index < RFFC5071_NUM_REGS) { + rffc5071_reg_write( + &mixer.rffc5071, + endpoint->setup.index, + endpoint->setup.value); + usb_transfer_schedule_ack(endpoint->in); + return USB_REQUEST_STATUS_OK; + } + return USB_REQUEST_STATUS_STALL; } -#endif - return USB_REQUEST_STATUS_STALL; - } else { return USB_REQUEST_STATUS_OK; - } + ) } usb_request_status_t usb_vendor_request_read_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { -#if defined(RAD1O) - (void) endpoint; - (void) stage; - if (detected_platform() == BOARD_ID_RAD1O) { + IF_RAD1O ( + (void) endpoint; + (void) stage; return USB_REQUEST_STATUS_STALL; - } -#endif + ) - if (stage == USB_TRANSFER_STAGE_SETUP) { -#if !defined(RAD1O) + IF_NOT_RAD1O ( uint16_t value; - if (endpoint->setup.index < RFFC5071_NUM_REGS) { - value = rffc5071_reg_read(&mixer.rffc5071, endpoint->setup.index); - endpoint->buffer[0] = value & 0xff; - endpoint->buffer[1] = value >> 8; - usb_transfer_schedule_block( - endpoint->in, - &endpoint->buffer, - 2, - NULL, - NULL); - usb_transfer_schedule_ack(endpoint->out); - return USB_REQUEST_STATUS_OK; + if (stage == USB_TRANSFER_STAGE_SETUP) { + if (endpoint->setup.index < RFFC5071_NUM_REGS) { + value = rffc5071_reg_read( + &mixer.rffc5071, + endpoint->setup.index); + endpoint->buffer[0] = value & 0xff; + endpoint->buffer[1] = value >> 8; + usb_transfer_schedule_block( + endpoint->in, + &endpoint->buffer, + 2, + NULL, + NULL); + usb_transfer_schedule_ack(endpoint->out); + return USB_REQUEST_STATUS_OK; + } + return USB_REQUEST_STATUS_STALL; } -#endif - return USB_REQUEST_STATUS_STALL; - } else { return USB_REQUEST_STATUS_OK; - } + ) } usb_request_status_t usb_vendor_request_set_clkout_enable( @@ -286,47 +282,47 @@ usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( return USB_REQUEST_STATUS_OK; } +#if defined(PRALINE) || defined(UNIVERSAL) usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - if (detected_platform() != BOARD_ID_PRALINE) { - return USB_REQUEST_STATUS_STALL; - } - - if (stage == USB_TRANSFER_STAGE_SETUP) { -#if defined(PRALINE) || defined(UNIVERSAL) - fpga_reg_write(&fpga, endpoint->setup.index, endpoint->setup.value); -#endif - usb_transfer_schedule_ack(endpoint->in); + IF_PRALINE ( + if (stage == USB_TRANSFER_STAGE_SETUP) { + fpga_reg_write( + &fpga, + endpoint->setup.index, + endpoint->setup.value); + usb_transfer_schedule_ack(endpoint->in); + } return USB_REQUEST_STATUS_OK; - } - return USB_REQUEST_STATUS_OK; + ) + + return USB_REQUEST_STATUS_STALL; } usb_request_status_t usb_vendor_request_read_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - if (detected_platform() != BOARD_ID_PRALINE) { - return USB_REQUEST_STATUS_STALL; - } + IF_PRALINE ( + if (stage == USB_TRANSFER_STAGE_SETUP) { + const uint8_t value = fpga_reg_read(&fpga, endpoint->setup.index); + endpoint->buffer[0] = value; + usb_transfer_schedule_block( + endpoint->in, + &endpoint->buffer, + 1, + NULL, + NULL); + usb_transfer_schedule_ack(endpoint->out); + } + return USB_REQUEST_STATUS_OK; + ) - if (stage == USB_TRANSFER_STAGE_SETUP) { -#if defined(PRALINE) || defined(UNIVERSAL) - const uint8_t value = fpga_reg_read(&fpga, endpoint->setup.index); - endpoint->buffer[0] = value; - usb_transfer_schedule_block( - endpoint->in, - &endpoint->buffer, - 1, - NULL, - NULL); -#endif - usb_transfer_schedule_ack(endpoint->out); - } - return USB_REQUEST_STATUS_OK; + return USB_REQUEST_STATUS_STALL; } +#endif /* * Each register is transferred as a uint8_t register number followed by a diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index fa5267b5..e25f387a 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -80,18 +80,14 @@ static const char* test_result_to_str(test_result_t result) void generate_selftest_report(void) { - board_id_t board_id = detected_platform(); - char* s = &selftest.report.msg[0]; size_t c = sizeof(selftest.report.msg); - if (board_id == BOARD_ID_RAD1O) { -#if defined(RAD1O) + IF_RAD1O ( append(&s, &c, "Mixer: MAX2871, ID: "); append(&s, &c, itoa(selftest.mixer_id, 10)); append(&s, &c, "\n"); -#endif - } else { -#if !defined(RAD1O) + ) + IF_NOT_RAD1O ( append(&s, &c, "Mixer: RFFC5072, ID: "); append(&s, &c, itoa(selftest.mixer_id >> 3, 10)); append(&s, &c, ", Rev: "); @@ -108,8 +104,7 @@ void generate_selftest_report(void) append(&s, &c, " (FAIL)"); } append(&s, &c, "\n"); -#endif - } + ) append(&s, &c, "Clock: Si5351"); append(&s, &c, ", Rev: "); @@ -118,16 +113,19 @@ void generate_selftest_report(void) append(&s, &c, selftest.si5351_readback_ok ? "OK" : "FAIL"); append(&s, &c, "\n"); - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); append(&s, &c, "\n"); -#endif - } else { -#if !defined(PRALINE) || defined(UNIVERSAL) + ) + IF_NOT_PRALINE ( append(&s, &c, "Transceiver: "); - append(&s, &c, (board_id == BOARD_ID_HACKRF1_R9 ? "MAX2839" : "MAX2837")); + IF_H1_R9 ( + append(&s, &c, "MAX2839"); + ) + IF_NOT_H1_R9 ( + append(&s, &c, "MAX2837"); + ) append(&s, &c, ", readback success: "); append(&s, &c, itoa(selftest.max283x_readback_register_count, 10)); append(&s, &c, "/"); @@ -142,10 +140,8 @@ void generate_selftest_report(void) itoa(selftest.max283x_readback_expected_value, 10)); } append(&s, &c, "\n"); -#endif - } - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( append(&s, &c, "FPGA configuration: "); append(&s, &c, test_result_to_str(selftest.fpga_image_load)); append(&s, &c, "\n"); @@ -177,8 +173,7 @@ void generate_selftest_report(void) append(&s, &c, itoa(m->avg_mag_sq_q, 10)); append(&s, &c, "\n"); } -#endif - } + ) } usb_request_status_t usb_vendor_request_read_selftest( From 8ce78cbc8fef105ee95a084c2decd9181698a77b Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 9 Apr 2026 18:27:46 +0100 Subject: [PATCH 026/161] firmware: Separate compile-time and runtime platform checks. --- firmware/common/cpld_jtag.c | 36 ++- firmware/common/hackrf_core.c | 366 +++++++++++++++-------- firmware/common/hackrf_ui.c | 16 +- firmware/common/max283x.c | 24 +- firmware/common/mixer.c | 74 +++-- firmware/common/operacake_sctimer.c | 12 +- firmware/common/platform_detect.h | 145 +++------ firmware/common/platform_gpio.c | 175 +++++++---- firmware/common/platform_scu.c | 176 +++++++---- firmware/common/radio.c | 66 ++-- firmware/common/rf_path.c | 72 +++-- firmware/common/rffc5071.c | 30 +- firmware/common/sgpio.c | 18 +- firmware/common/si5351c.c | 54 ++-- firmware/common/tuning.c | 18 +- firmware/hackrf_usb/hackrf_usb.c | 98 +++--- firmware/hackrf_usb/usb_api_board_info.c | 6 +- firmware/hackrf_usb/usb_api_register.c | 36 ++- firmware/hackrf_usb/usb_api_selftest.c | 42 ++- 19 files changed, 925 insertions(+), 539 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 58359051..b277b462 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -42,30 +42,38 @@ void cpld_jtag_take(jtag_t* const jtag) /* Set initial GPIO state to the voltages of the internal or external pull-ups/downs, * to avoid any glitches. */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { gpio_set(gpio->gpio_pp_tms); - ) + } +#endif gpio_clear(gpio->gpio_tck); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_set(gpio->gpio_tms); gpio_set(gpio->gpio_tdi); - ) + } +#endif - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { /* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */ gpio_input(gpio->gpio_pp_tms); gpio_input(gpio->gpio_pp_tdo); - ) + } +#endif gpio_output(gpio->gpio_tck); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_output(gpio->gpio_tms); gpio_output(gpio->gpio_tdi); gpio_input(gpio->gpio_tdo); - ) + } +#endif } void cpld_jtag_release(jtag_t* const jtag) @@ -75,19 +83,23 @@ void cpld_jtag_release(jtag_t* const jtag) /* Make all pins inputs when JTAG interface not active. * Let the pull-ups/downs do the work. */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { /* Do not drive PortaPack-specific pins, initially, just to be cautious. */ gpio_input(gpio->gpio_pp_tms); gpio_input(gpio->gpio_pp_tdo); - ) + } +#endif gpio_input(gpio->gpio_tck); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_input(gpio->gpio_tms); gpio_input(gpio->gpio_tdi); gpio_input(gpio->gpio_tdo); - ) + } +#endif } #if !defined(PRALINE) || defined(UNIVERSAL) diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 689a2a30..0ac7ae65 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -341,7 +341,8 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) sgpio_cpld_stream_disable(&sgpio_config); } - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { /* Integer mode can be enabled if p1 is even and p2 is zero. */ if (p1 & 0x1 || p2) { si5351c_set_int_mode(&clock_gen, 0, 0); @@ -349,14 +350,17 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) si5351c_set_int_mode(&clock_gen, 0, 1); } - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { /* * On HackRF One r9 all sample clocks are externally derived * from MS1/CLK1 operating at twice the sample rate. */ si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { /* * On other platforms the clock generator produces three * different sample clocks, all derived from multisynth 0. @@ -381,9 +385,12 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) 0, 0, 0); //p1 doesn't matter - ) - ) - IF_PRALINE ( + } + #endif + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); @@ -409,7 +416,8 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) /* Reset PLL to synchronize output clock phase. */ si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); - ) + } +#endif if (streaming) { sgpio_cpld_stream_enable(&sgpio_config); @@ -561,7 +569,8 @@ void cpu_clock_init(void) CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { /* Disable unused clocks */ /* Start with PLLs */ CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); @@ -629,7 +638,8 @@ void cpu_clock_init(void) // CCU2_CLK_APB2_USART3_CFG = 0; // CCU2_CLK_APLL_CFG = 0; // CCU2_CLK_SDIO_CFG = 0; - ) + } +#endif } void clock_gen_init(void) @@ -670,7 +680,8 @@ void clock_gen_init(void) * CLK7 -> AUX_CLK2 */ - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { /* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */ si5351c_configure_multisynth( &clock_gen, @@ -679,8 +690,10 @@ void clock_gen_init(void) 0, 1, 0); /* 800/20 = 40MHz */ - ) - IF_NOT_H1_R9 ( + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { /* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */ si5351c_configure_multisynth( &clock_gen, @@ -697,7 +710,8 @@ void clock_gen_init(void) 0, 1, 0); /* 800/20 = 40MHz */ - ) + } +#endif /* MS6/CLK6 is unused. */ /* MS7/CLK7 is unused. */ @@ -722,12 +736,14 @@ void clock_gen_shutdown(void) clock_source_t activate_best_clock_source(void) { - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(false); } - ) + } +#endif clock_source_t source = CLOCK_SOURCE_HACKRF; @@ -735,7 +751,8 @@ clock_source_t activate_best_clock_source(void) if (si5351c_clkin_signal_valid(&clock_gen)) { source = CLOCK_SOURCE_EXTERNAL; } else { - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(true); @@ -746,7 +763,8 @@ clock_source_t activate_best_clock_source(void) portapack_reference_oscillator(false); } } - ) + } +#endif /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ } @@ -798,69 +816,94 @@ void pin_shutdown(void) * * LPC43xx pull-up and pull-down resistors are approximately 53K. */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - ) + } +#endif scu_pinmux(scu->PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); - ) + } +#endif /* Configure SCU Pin Mux as GPIO */ scu_pinmux(scu->PINMUX_LED1, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED2, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED3, SCU_GPIO_NOPULL); - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - ) + } +#endif /* Configure USB indicators */ - IF_JAWBREAKER ( +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3); scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3); - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { disable_1v2_power(); disable_3v3aux_power(); gpio_output(gpio->gpio_1v2_enable); gpio_output(gpio->gpio_3v3aux_enable_n); scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) + } +#endif - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { disable_1v8_power(); - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { gpio_output(gpio->h1r9_1v8_enable); scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { gpio_output(gpio->gpio_1v8_enable); scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - ) + } + #endif + } +#endif - IF_H1_OR_PRALINE ( +#ifdef IS_H1_OR_PRALINE + if (IS_H1_OR_PRALINE) { /* Safe state: start with VAA turned off: */ disable_rf_power(); /* Configure RF power supply (VAA) switch control signal as output */ - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { gpio_output(gpio->h1r9_vaa_disable); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { gpio_output(gpio->vaa_disable); - ) - ) + } + #endif + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { /* Safe state: start with VAA turned off: */ disable_rf_power(); @@ -873,9 +916,11 @@ void pin_shutdown(void) scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG); scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG); scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG); @@ -910,7 +955,8 @@ void pin_shutdown(void) gpio_clear(gpio->fpga_cfg_creset); gpio_output(gpio->fpga_cfg_creset); gpio_input(gpio->fpga_cfg_cdone); - ) + } +#endif /* enable input on SCL and SDA pins */ SCU_SFSI2C0 = SCU_I2C0_NOMINAL; @@ -927,26 +973,34 @@ void pin_setup(void) led_off(0); led_off(1); led_off(2); - IF_FOUR_LEDS ( +#ifdef IS_FOUR_LEDS + if (IS_FOUR_LEDS) { led_off(3); - ) + } +#endif gpio_output(gpio->led[0]); gpio_output(gpio->led[1]); gpio_output(gpio->led[2]); - IF_FOUR_LEDS ( +#ifdef IS_FOUR_LEDS + if (IS_FOUR_LEDS) { gpio_output(gpio->led[3]); - ) + } +#endif /* Configure drivers and driver pins */ ssp_config_max283x.gpio_select = gpio->max283x_select; - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { ssp_config_max283x.data_bits = SSP_DATA_16BITS; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words - ) + } +#endif ssp_config_max5864.gpio_select = gpio->max5864_select; @@ -956,40 +1010,51 @@ void pin_setup(void) sgpio_config.gpio_q_invert = gpio->q_invert; - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { sgpio_config.gpio_trigger_enable = gpio->trigger_enable; - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; ice40.gpio_select = gpio->fpga_cfg_spi_cs; ice40.gpio_creset = gpio->fpga_cfg_creset; ice40.gpio_cdone = gpio->fpga_cfg_cdone; - ) + } +#endif jtag_gpio_cpld.gpio_tck = gpio->cpld_tck; - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; - ) + } +#endif - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; - ) + } +#endif ssp1_set_mode_max283x(); mixer_bus_setup(&mixer); - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { sgpio_config.gpio_trigger_enable = gpio->h1r9_trigger_enable; - ) + } +#endif // initialize rf_path struct and assign gpio's - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { rf_path = (rf_path_t){ .switchctrl = 0, .gpio_hp = gpio->hp, @@ -1006,13 +1071,17 @@ void pin_setup(void) .gpio_rx_amp = gpio->rx_amp, .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, }; - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { rf_path.gpio_rx = gpio->h1r9_rx; rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; - ) - ) + } + #endif + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { rf_path = (rf_path_t){ .switchctrl = 0, .gpio_tx_rx_n = gpio->tx_rx_n, @@ -1027,9 +1096,11 @@ void pin_setup(void) .gpio_tx_amp = gpio->tx_amp, .gpio_rx_lna = gpio->rx_lna, }; - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { rf_path = (rf_path_t){ .switchctrl = 0, .gpio_tx_en = gpio->tx_en, @@ -1042,7 +1113,8 @@ void pin_setup(void) (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; } - ) + } +#endif rf_path_pin_setup(&rf_path); @@ -1055,55 +1127,75 @@ void pin_setup(void) #if defined(PRALINE) || defined(UNIVERSAL) void enable_1v2_power(void) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { gpio_set(platform_gpio()->gpio_1v2_enable); - ) + } + #endif } void disable_1v2_power(void) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { gpio_clear(platform_gpio()->gpio_1v2_enable); - ) + } + #endif } void enable_3v3aux_power(void) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); - ) + } + #endif } void disable_3v3aux_power(void) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { gpio_set(platform_gpio()->gpio_3v3aux_enable_n); - ) + } + #endif } #endif void enable_1v8_power(void) { - IF_NOT_PRALINE ( - IF_H1_R9 ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + #ifdef IS_H1_R9 + if (IS_H1_R9) { gpio_set(platform_gpio()->h1r9_1v8_enable); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { gpio_set(platform_gpio()->gpio_1v8_enable); - ) - ) + } + #endif + } +#endif } void disable_1v8_power(void) { - IF_NOT_PRALINE ( - IF_H1_R9 ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + #ifdef IS_H1_R9 + if (IS_H1_R9) { gpio_clear(platform_gpio()->h1r9_1v8_enable); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { gpio_clear(platform_gpio()->gpio_1v8_enable); - ) - ) + } + #endif + } +#endif } #if defined(HACKRF_ONE) || defined(UNIVERSAL) @@ -1166,48 +1258,68 @@ static inline void disable_rf_power_rad1o(void) void enable_rf_power(void) { - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { enable_rf_power_hackrf_one(); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { enable_rf_power_praline(); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { enable_rf_power_rad1o(); - ) + } +#endif } void disable_rf_power(void) { - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { disable_rf_power_hackrf_one(); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { disable_rf_power_praline(); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { disable_rf_power_rad1o(); - ) + } +#endif } void led_on(const led_t led) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio_clear(platform_gpio()->led[led]); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_set(platform_gpio()->led[led]); - ) + } +#endif } void led_off(const led_t led) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio_set(platform_gpio()->led[led]); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_clear(platform_gpio()->led[led]); - ) + } +#endif } void led_toggle(const led_t led) @@ -1218,28 +1330,38 @@ void led_toggle(const led_t led) void set_leds(const uint8_t state) { int num_leds = 3; - IF_FOUR_LEDS ( +#ifdef IS_FOUR_LEDS + if (IS_FOUR_LEDS) { num_leds = 4; - ) + } +#endif for (int i = 0; i < num_leds; i++) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); - ) + } +#endif } } void trigger_enable(const bool enable) { - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_write(sgpio_config.gpio_trigger_enable, enable); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_set_trigger_enable(&fpga, enable); - ) + } +#endif } void halt_and_flash(const uint32_t duration) diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index 092ec12b..b90ccc14 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -23,8 +23,10 @@ #include #include "hackrf_ui.h" -#include "platform_detect.h" #include "transceiver_mode.h" +#if !defined(JAWBREAKER) + #include "platform_detect.h" +#endif #if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) #include "ui_portapack.h" #endif @@ -83,16 +85,20 @@ const hackrf_ui_t* hackrf_ui(void) { /* Detect on first use. If no UI hardware is detected, use a stub function table. */ if (ui == NULL && ui_enabled) { - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { if (portapack_hackrf_ui_init) { ui = portapack_hackrf_ui_init(); } - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { if (rad1o_ui_setup) { ui = rad1o_ui_setup(); } - ) + } +#endif } if (ui == NULL) { diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index feb7837d..856c5d99 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -73,7 +73,8 @@ void max283x_setup(max283x_driver_t* const drv) const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { drv->type = MAX2831_VARIANT; max2831.gpio_enable = gpio->max283x_enable; max2831.gpio_rxtx = gpio->max283x_rx_enable; @@ -81,24 +82,31 @@ void max283x_setup(max283x_driver_t* const drv) max2831.gpio_ld = gpio->max2831_ld; memcpy(&drv->drv.max2831, &max2831, sizeof(max2831)); max2831_setup(&drv->drv.max2831); - ) - IF_NOT_PRALINE ( - IF_H1_R9 ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + #ifdef IS_H1_R9 + if (IS_H1_R9) { drv->type = MAX2839_VARIANT; max2839.gpio_enable = gpio->max283x_enable; max2839.gpio_rxtx = gpio->max283x_rx_enable; memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); max2839_setup(&drv->drv.max2839); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { drv->type = MAX2837_VARIANT; max2837.gpio_enable = gpio->max283x_enable; max2837.gpio_rx_enable = gpio->max283x_rx_enable; max2837.gpio_tx_enable = gpio->max283x_tx_enable; memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); max2837_setup(&drv->drv.max2837); - ) - ) + } + #endif + } +#endif } /* Macros to simplify dispatch of variant-specific operations. */ diff --git a/firmware/common/mixer.c b/firmware/common/mixer.c index 0706170b..fb921350 100644 --- a/firmware/common/mixer.c +++ b/firmware/common/mixer.c @@ -56,7 +56,8 @@ void mixer_bus_setup(mixer_driver_t* const mixer) { (void) mixer; - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { const platform_gpio_t* gpio = platform_gpio(); rffc5071_spi_config = (rffc5071_spi_config_t){ @@ -65,7 +66,8 @@ void mixer_bus_setup(mixer_driver_t* const mixer) .gpio_data = gpio->rffc5072_data, }; spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config); - ) + } +#endif } void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) @@ -75,16 +77,21 @@ void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) const platform_gpio_t* gpio = platform_gpio(); /* Mixer GPIO serial interface PinMux */ - IF_PRALINE ( - mixer->rffc5071.gpio_ld = gpio->rffc5072_ld - ); +#ifdef IS_PRALINE + if (IS_PRALINE) { + mixer->rffc5071.gpio_ld = gpio->rffc5072_ld; + } +#endif - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { mixer->rffc5071.gpio_reset = gpio->rffc5072_reset; rffc5071_setup(&mixer->rffc5071); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { mixer->max2871.gpio_vco_ce = gpio->vco_ce; mixer->max2871.gpio_vco_sclk = gpio->vco_sclk; mixer->max2871.gpio_vco_sdata = gpio->vco_sdata; @@ -92,50 +99,67 @@ void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) mixer->max2871.gpio_synt_rfout_en = gpio->synt_rfout_en; mixer->max2871.gpio_vco_mux = gpio->vco_mux; max2871_setup(&mixer->max2871); - ) + } +#endif } fp_40_24_t mixer_set_frequency(mixer_driver_t* const mixer, fp_40_24_t lo, bool program) { - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { return rffc5071_set_frequency(&mixer->rffc5071, lo, program); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { return max2871_set_frequency(&mixer->max2871, lo, program); - ) + } +#endif } void mixer_enable(mixer_driver_t* const mixer) { - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { rffc5071_enable(&mixer->rffc5071); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { max2871_enable(&mixer->max2871); - ) + } +#endif } void mixer_disable(mixer_driver_t* const mixer) { - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { rffc5071_disable(&mixer->rffc5071); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { max2871_disable(&mixer->max2871); - ) + } +#endif } void mixer_set_gpo(mixer_driver_t* const mixer, uint8_t gpo) { - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { rffc5071_set_gpo(&mixer->rffc5071, gpo); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { (void) mixer; (void) gpo; - ) + } +#endif } diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 82bb6400..2dbe07d9 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -98,7 +98,8 @@ void operacake_sctimer_init(void) SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); uint8_t sct_clock_input; - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { // Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12 SGPIO_OUT_MUX_CFG12 = SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode @@ -109,8 +110,10 @@ void operacake_sctimer_init(void) GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1 sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { // Configure pin P6_4 as SCT_IN_6 scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); @@ -118,7 +121,8 @@ void operacake_sctimer_init(void) GIMA_CTIN_6_IN = 0x0 << 4; sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6; - ) + } +#endif // We configure this register first, because the user manual says to SCT_CONFIG |= SCT_CONFIG_UNIFY_32_BIT | SCT_CONFIG_CLKMODE_PRESCALED_BUS_CLOCK | diff --git a/firmware/common/platform_detect.h b/firmware/common/platform_detect.h index f8bc53b9..0331fe9c 100644 --- a/firmware/common/platform_detect.h +++ b/firmware/common/platform_detect.h @@ -21,6 +21,7 @@ #pragma once +#include #include #define BOARD_REV_GSG (0x80) @@ -35,107 +36,57 @@ /* Helper macros for platform-specific code. */ #if defined(UNIVERSAL) - #define IF_PRALINE(...) \ - if (detected_platform() == BOARD_ID_PRALINE) { __VA_ARGS__; } - #define IF_NOT_PRALINE(...) \ - if (detected_platform() != BOARD_ID_PRALINE) { __VA_ARGS__; } - #define IF_HACKRF_ONE(...) \ - switch (detected_platform()) { \ - case BOARD_ID_HACKRF1_OG: \ - case BOARD_ID_HACKRF1_R9: \ - { __VA_ARGS__; } \ - break; \ - default: \ - break; \ - } - #define IF_NOT_HACKRF_ONE(...) \ - switch (detected_platform()) { \ - case BOARD_ID_HACKRF1_OG: \ - case BOARD_ID_HACKRF1_R9: \ - break; \ - default: \ - { __VA_ARGS__; } \ - break; \ - } - #define IF_H1_R9(...) \ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } - #define IF_NOT_H1_R9(...) \ - if (detected_platform() != BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } - #define IF_RAD1O(...) - #define IF_NOT_RAD1O(...) { __VA_ARGS__; } - #define IF_JAWBREAKER(...) - #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } - #define IF_H1_OR_RAD1O(...) IF_HACKRF_ONE(__VA_ARGS__) - #define IF_H1_OR_JAWBREAKER(...) IF_HACKRF_ONE(__VA_ARGS__) - #define IF_FOUR_LEDS(...) IF_PRALINE(__VA_ARGS__) - #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } + #define IS_PRALINE (detected_platform() == BOARD_ID_PRALINE) + #define IS_NOT_PRALINE (!IS_PRALINE) + #define IS_HACKRF_ONE ( \ + detected_platform() == BOARD_ID_HACKRF1_OG || \ + detected_platform() == BOARD_ID_HACKRF1_R9 \ + ) + #define IS_NOT_HACKRF_ONE (!IS_HACKRF_ONE) + #define IS_H1_R9 (detected_platform() == BOARD_ID_HACKRF1_R9) + #define IS_NOT_H1_R9 (!IS_H1_R9) + #define IS_NOT_RAD1O true + #define IS_NOT_JAWBREAKER true + #define IS_H1_OR_PRALINE true + #define IS_H1_OR_RAD1O IS_HACKRF_ONE + #define IS_H1_OR_JAWBREAKER IS_HACKRF_ONE + #define IS_FOUR_LEDS IS_PRALINE + #define IS_EXPANSION_COMPATIBLE true #elif defined(HACKRF_ONE) - #define IF_PRALINE(...) - #define IF_NOT_PRALINE(...) { __VA_ARGS__; } - #define IF_HACKRF_ONE(...) { __VA_ARGS__; } - #define IF_NOT_HACKRF_ONE(...) - #define IF_H1_R9(...) \ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } - #define IF_NOT_H1_R9(...) \ - if (detected_platform() != BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } - #define IF_RAD1O(...) - #define IF_NOT_RAD1O(...) { __VA_ARGS__; } - #define IF_JAWBREAKER(...) - #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } - #define IF_H1_OR_RAD1O(...) { __VA_ARGS__; } - #define IF_H1_OR_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_FOUR_LEDS(...) - #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } + #define IS_NOT_PRALINE true + #define IS_HACKRF_ONE true + #define IS_H1_R9 (detected_platform() == BOARD_ID_HACKRF1_R9) + #define IS_NOT_H1_R9 (!IS_H1_R9) + #define IS_NOT_RAD1O true + #define IS_NOT_JAWBREAKER true + #define IS_H1_OR_PRALINE true + #define IS_H1_OR_RAD1O true + #define IS_H1_OR_JAWBREAKER true + #define IS_EXPANSION_COMPATIBLE true #elif defined(PRALINE) - #define IF_PRALINE(...) { __VA_ARGS__; } - #define IF_NOT_PRALINE(...) - #define IF_HACKRF_ONE(...) - #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } - #define IF_H1_R9(...) - #define IF_NOT_H1_R9(...) { __VA_ARGS__; } - #define IF_RAD1O(...) - #define IF_NOT_RAD1O(...) { __VA_ARGS__; } - #define IF_JAWBREAKER(...) - #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } - #define IF_H1_OR_RAD1O(...) - #define IF_H1_OR_JAWBREAKER(...) - #define IF_FOUR_LEDS(...) - #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } + #define IS_PRALINE true + #define IS_NOT_HACKRF_ONE true + #define IS_NOT_H1_R9 true + #define IS_NOT_RAD1O true + #define IS_NOT_JAWBREAKER true + #define IS_H1_OR_PRALINE true + #define IS_FOUR_LEDS true + #define IS_EXPANSION_COMPATIBLE true #elif defined(RAD1O) - #define IF_PRALINE(...) - #define IF_NOT_PRALINE(...) { __VA_ARGS__; } - #define IF_HACKRF_ONE(...) - #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } - #define IF_H1_R9(...) - #define IF_NOT_H1_R9(...) { __VA_ARGS__; } - #define IF_RAD1O(...) { __VA_ARGS__; } - #define IF_NOT_RAD1O(...) - #define IF_JAWBREAKER(...) - #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_H1_OR_PRALINE(...) - #define IF_H1_OR_RAD1O(...) { __VA_ARGS__; } - #define IF_H1_OR_JAWBREAKER(...) - #define IF_FOUR_LEDS(...) { __VA_ARGS__; } - #define IF_EXPANSION_COMPATIBLE(...) + #define IS_NOT_PRALINE true + #define IS_NOT_HACKRF_ONE true + #define IS_NOT_H1_R9 true + #define IS_RAD1O true + #define IS_NOT_JAWBREAKER true + #define IS_H1_OR_RAD1O true + #define IS_FOUR_LEDS true #elif defined(JAWBREAKER) - #define IF_PRALINE(...) - #define IF_NOT_PRALINE(...) { __VA_ARGS__; } - #define IF_HACKRF_ONE(...) - #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } - #define IF_H1_R9(...) - #define IF_NOT_H1_R9(...) { __VA_ARGS__; } - #define IF_RAD1O(...) - #define IF_NOT_RAD1O(...) { __VA_ARGS__; } - #define IF_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_NOT_JAWBREAKER(...) - #define IF_H1_OR_PRALINE(...) - #define IF_H1_OR_RAD1O(...) - #define IF_H1_OR_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_FOUR_LEDS(...) - #define IF_EXPANSION_COMPATIBLE(...) + #define IS_NOT_PRALINE true + #define IS_NOT_HACKRF_ONE true + #define IS_NOT_H1_R9 true + #define IS_NOT_RAD1O true + #define IS_JAWBREAKER true + #define IS_H1_OR_JAWBREAKER true #else #error "No recognised platform defined" #endif diff --git a/firmware/common/platform_gpio.c b/firmware/common/platform_gpio.c index a5826cf2..a5b892e8 100644 --- a/firmware/common/platform_gpio.c +++ b/firmware/common/platform_gpio.c @@ -40,55 +40,77 @@ const platform_gpio_t* platform_gpio(void) gpio.led[0] = &GPIO2_1; gpio.led[1] = &GPIO2_2; gpio.led[2] = &GPIO2_8; - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.led[3] = &GPIO5_26; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.led[3] = &GPIO4_6; - ) + } +#endif /* Power Supply Control */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.gpio_1v2_enable = &GPIO4_7; gpio.gpio_3v3aux_enable_n = &GPIO5_15; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.gpio_1v8_enable = &GPIO3_6; - ) + } +#endif /* MAX283x GPIO (XCVR_CTL / CS_XCVR) PinMux */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.max283x_select = &GPIO6_28; gpio.max283x_enable = &GPIO7_1; gpio.max283x_rx_enable = &GPIO7_2; gpio.max2831_rxhp = &GPIO6_29; gpio.max2831_ld = &GPIO4_11; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.max283x_select = &GPIO0_15; gpio.max283x_enable = &GPIO2_6; gpio.max283x_rx_enable = &GPIO2_5; gpio.max283x_tx_enable = &GPIO2_4; - ) + } +#endif /* MAX5864 SPI chip select (AD_CS / CS_AD) GPIO PinMux */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.max5864_select = &GPIO6_30; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.max5864_select = &GPIO2_7; - ) + } +#endif /* RF supply (VAA) control */ - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { gpio.vaa_disable = &GPIO2_9; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.vaa_disable = &GPIO4_1; - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.vaa_enable = &GPIO2_9; - ) + } +#endif /* W25Q80BV Flash */ gpio.w25q80bv_hold = &GPIO1_14; @@ -96,7 +118,8 @@ const platform_gpio_t* platform_gpio(void) gpio.w25q80bv_select = &GPIO5_11; /* RF switch control */ - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { gpio.hp = &GPIO2_0; gpio.lp = &GPIO2_10; gpio.tx_mix_bp = &GPIO2_11; @@ -110,12 +133,16 @@ const platform_gpio_t* platform_gpio(void) gpio.amp_bypass = &GPIO0_14; gpio.rx_amp = &GPIO1_11; gpio.no_rx_amp_pwr = &GPIO1_12; - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { gpio.h1r9_rx = &GPIO0_7; gpio.h1r9_no_ant_pwr = &GPIO2_4; - ) - ) - IF_RAD1O ( + } +#endif + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.tx_rx_n = &GPIO1_11; gpio.tx_rx = &GPIO0_14; gpio.by_mix = &GPIO1_12; @@ -127,69 +154,92 @@ const platform_gpio_t* platform_gpio(void) gpio.low_high_filt_n = &GPIO2_12; gpio.tx_amp = &GPIO2_15; gpio.rx_lna = &GPIO5_15; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.tx_en = &GPIO3_4; gpio.mix_en_n = &GPIO3_2; gpio.mix_en_n_r1_0 = &GPIO5_6; gpio.lpf_en = &GPIO4_8; gpio.rf_amp_en = &GPIO4_9; gpio.ant_bias_en_n = &GPIO1_12; - ) + } +#endif /* CPLD JTAG interface GPIO pins_FPGA config pins in Praline */ gpio.cpld_tck = &GPIO3_0; - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.fpga_cfg_creset = &GPIO2_11; gpio.fpga_cfg_cdone = &GPIO5_14; gpio.fpga_cfg_spi_cs = &GPIO2_10; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.cpld_tdo = &GPIO5_18; - IF_H1_OR_RAD1O ( - gpio.cpld_tms = &GPIO3_4; - gpio.cpld_tdi = &GPIO3_1; - IF_JAWBREAKER ( - gpio.cpld_tms = &GPIO3_1; - gpio.cpld_tdi = &GPIO3_4; - ) - ) - IF_EXPANSION_COMPATIBLE ( + } +#endif +#ifdef IS_H1_OR_RAD1O + if (IS_H1_OR_RAD1O) { + gpio.cpld_tms = &GPIO3_4; + gpio.cpld_tdi = &GPIO3_1; + } +#endif +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { + gpio.cpld_tms = &GPIO3_1; + gpio.cpld_tdi = &GPIO3_4; + } +#endif +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { gpio.cpld_pp_tms = &GPIO1_1; gpio.cpld_pp_tdo = &GPIO1_8; - ) + } +#endif /* Other CPLD interface GPIO pins */ - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.trigger_enable = &GPIO5_12; - ) + } +#endif gpio.q_invert = &GPIO0_13; /* RFFC5071 GPIO serial interface PinMux */ - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { gpio.rffc5072_select = &GPIO2_13; gpio.rffc5072_clock = &GPIO5_6; gpio.rffc5072_data = &GPIO3_3; gpio.rffc5072_reset = &GPIO2_14; - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.vco_ce = &GPIO2_13; gpio.vco_sclk = &GPIO5_6; gpio.vco_sdata = &GPIO3_3; gpio.vco_le = &GPIO2_14; gpio.vco_mux = &GPIO5_25; gpio.synt_rfout_en = &GPIO3_5; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.rffc5072_select = &GPIO2_13; gpio.rffc5072_clock = &GPIO5_18; gpio.rffc5072_data = &GPIO4_14; gpio.rffc5072_reset = &GPIO2_14; gpio.rffc5072_ld = &GPIO6_25; - ) + } +#endif /* Praline */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.p2_ctrl0 = &GPIO7_3; gpio.p2_ctrl1 = &GPIO7_4; gpio.p1_ctrl0 = &GPIO0_14; @@ -200,33 +250,40 @@ const platform_gpio_t* platform_gpio(void) gpio.trigger_in = &GPIO6_26; gpio.trigger_out = &GPIO5_6; gpio.pps_out = &GPIO5_5; - ) + } +#endif /* HackRF One r9 clock control */ - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { gpio.h1r9_clkin_en = &GPIO5_15; gpio.h1r9_clkout_en = &GPIO0_9; gpio.h1r9_mcu_clk_en = &GPIO0_8; gpio.h1r9_1v8_enable = &GPIO2_9; gpio.h1r9_vaa_disable = &GPIO3_6; gpio.h1r9_trigger_enable = &GPIO5_5; - ) + } +#endif /* rad1o LCD */ - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.lcd_cs = &GPIO4_12; /* P9_0 */ gpio.lcd_bl_en = &GPIO0_8; /* P1_1 */ gpio.lcd_reset = &GPIO5_17; /* P9_4 */ - ) + } +#endif /* Portapack */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { gpio.io_stbx = &GPIO5_0; /* P2_0 */ gpio.addr = &GPIO5_1; /* P2_1 */ gpio.lcd_rdx = &GPIO5_4; /* P2_4 */ gpio.lcd_wrx = &GPIO1_10; /* P2_9 */ gpio.dir = &GPIO1_13; /* P2_13 */ - ) + } +#endif _platform_gpio = &gpio; diff --git a/firmware/common/platform_scu.c b/firmware/common/platform_scu.c index 9d3b0c5d..ef894745 100644 --- a/firmware/common/platform_scu.c +++ b/firmware/common/platform_scu.c @@ -42,34 +42,44 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_LED1 = SCU_PINMUX_LED1; /* GPIO2[1] on P4_1 */ scu.PINMUX_LED2 = SCU_PINMUX_LED2; /* GPIO2[2] on P4_2 */ scu.PINMUX_LED3 = SCU_PINMUX_LED3; /* GPIO2[8] on P6_12 */ - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.PINMUX_LED4 = (PB_6); /* GPIO5[26] on PB_6 */ - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.PINMUX_LED4 = (P8_6); /* GPIO4[6] on P8_6 */ - ) + } +#endif /* Power Supply PinMux */ scu.PINMUX_EN1V8 = (P6_10); /* GPIO3[6] on P6_10 */ scu.PINMUX_EN1V2 = (P8_7); /* GPIO4[7] on P8_7 */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.PINMUX_EN3V3_AUX_N = (P6_7); /* GPIO5[15] on P6_7 */ scu.PINMUX_EN3V3_OC_N = (P6_11); /* GPIO3[7] on P6_11 */ - ) + } +#endif /* GPIO Input PinMux */ scu.PINMUX_BOOT0 = (P1_1); /* GPIO0[8] on P1_1 */ scu.PINMUX_BOOT1 = (P1_2); /* GPIO0[9] on P1_2 */ - IF_NOT_HACKRF_ONE ( +#ifdef IS_NOT_HACKRF_ONE + if (IS_NOT_HACKRF_ONE) { scu.PINMUX_BOOT2 = (P2_8); /* GPIO5[7] on P2_8 */ scu.PINMUX_BOOT3 = (P2_9); /* GPIO1[10] on P2_9 */ - ) + } +#endif /* USB peripheral */ - IF_JAWBREAKER ( +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { scu.PINMUX_USB_LED0 = (P6_8); scu.PINMUX_USB_LED1 = (P6_7); - ) + } +#endif /* SSP1 Peripheral PinMux */ scu.SSP1_CIPO = (P1_3); /* P1_3 */ @@ -79,25 +89,34 @@ const platform_scu_t* platform_scu(void) /* CPLD JTAG interface */ scu.PINMUX_CPLD_TCK = (P6_1); /* GPIO3[ 0] */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.PINMUX_FPGA_CRESET = (P5_2); /* GPIO2[11] on P5_2 */ scu.PINMUX_FPGA_CDONE = (P4_10); /* GPIO5[14] */ scu.PINMUX_FPGA_SPI_CS = (P5_1); /* GPIO2[10] */ - ) - IF_NOT_PRALINE ( - scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ - ) - IF_H1_OR_RAD1O ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ + } +#endif +#ifdef IS_H1_OR_RAD1O + if (IS_H1_OR_RAD1O) { scu.PINMUX_CPLD_TMS = (P6_5); /* GPIO3[ 4] */ scu.PINMUX_CPLD_TDI = (P6_2); /* GPIO3[ 1] */ - ) - IF_JAWBREAKER ( + } +#endif +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { scu.PINMUX_CPLD_TMS = (P6_2); /* GPIO3[ 1] */ scu.PINMUX_CPLD_TDI = (P6_5); /* GPIO3[ 4] */ - ) + } +#endif /* CPLD SGPIO interface */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.PINMUX_SGPIO0 = (P0_0); scu.PINMUX_SGPIO1 = (P0_1); scu.PINMUX_SGPIO2 = (P1_15); @@ -129,8 +148,10 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { scu.PINMUX_SGPIO0 = (P0_0); scu.PINMUX_SGPIO1 = (P0_1); scu.PINMUX_SGPIO2 = (P1_15); @@ -162,16 +183,20 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) + } +#endif scu.TRIGGER_EN = (P4_8); /* GPIO5[12] on P4_8 */ /* MAX283x GPIO (XCVR_CTL) PinMux */ - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.XCVR_RXHP = P8_1; /* GPIO[] on P8_1 */ scu.XCVR_B6 = P8_2; /* GPIO[] on P8_2 */ scu.XCVR_B7 = P9_3; /* GPIO[] on P9_3 */ - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.XCVR_ENABLE = PE_1; /* GPIO7[1] on PE_1 */ scu.XCVR_RXENABLE = PE_2; /* GPIO7[2] on PE_2 */ scu.XCVR_CS = PD_14; /* GPIO6[28] on PD_14 */ @@ -184,8 +209,10 @@ const platform_scu_t* platform_scu(void) scu.XCVR_RXHP_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.XCVR_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | SCU_CONF_EPD_EN_PULLDOWN | SCU_CONF_EPUN_DIS_PULLUP); - ) - IF_H1_OR_JAWBREAKER ( + } +#endif +#ifdef IS_H1_OR_JAWBREAKER + if (IS_H1_OR_JAWBREAKER) { scu.XCVR_ENABLE = P4_6; /* GPIO2[6] on P4_6 */ scu.XCVR_RXENABLE = P4_5; /* GPIO2[5] on P4_5 */ scu.XCVR_TXENABLE = P4_4; /* GPIO2[4] on P4_4 */ @@ -195,20 +222,26 @@ const platform_scu_t* platform_scu(void) scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST); scu.XCVR_TXENABLE_PINCFG = (SCU_GPIO_FAST); scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST); - ) + } +#endif /* MAX5864 SPI chip select (AD_CS) GPIO PinMux */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.AD_CS = (PD_16); /* GPIO6[30] on PD_16 */ scu.AD_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { scu.AD_CS = (P5_7); /* GPIO2[7] on P5_7 */ scu.AD_CS_PINCFG = (SCU_GPIO_FAST); - ) + } +#endif /* RFFC5071 GPIO serial interface PinMux */ - IF_H1_OR_JAWBREAKER ( +#ifdef IS_H1_OR_JAWBREAKER + if (IS_H1_OR_JAWBREAKER) { scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ scu.MIXER_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ scu.MIXER_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ @@ -216,8 +249,10 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ scu.MIXER_SCLK = (P9_5); /* GPIO5[18] on P9_5 */ scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ @@ -227,8 +262,10 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.VCO_CE = (P5_4); /* GPIO2[13] on P5_4 */ scu.VCO_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ scu.VCO_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ @@ -236,23 +273,32 @@ const platform_scu_t* platform_scu(void) scu.VCO_MUX = (PB_5); /* GPIO5[25] on PB_5 */ scu.MIXER_EN = (P6_8); /* GPIO5[16] on P6_8 */ scu.SYNT_RFOUT_EN = (P6_9); /* GPIO3[5] on P6_9 */ - ) + } +#endif /* RF LDO control */ - IF_JAWBREAKER ( +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { scu.RF_LDO_ENABLE = (P5_0); /* GPIO2[9] on P5_0 */ - ) + } +#endif /* RF supply (VAA) control */ - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { scu.NO_VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.NO_VAA_ENABLE = P8_1; /* GPIO4[1] on P8_1 */ - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ - ) + } +#endif /* SPI flash */ scu.SSP0_CIPO = (P3_6); @@ -263,7 +309,8 @@ const platform_scu_t* platform_scu(void) scu.FLASH_WP = (P3_5); /* GPIO1[15] on P3_5 */ /* RF switch control */ - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { scu.HP = P4_0; /* GPIO2[0] on P4_0 */ scu.LP = P5_1; /* GPIO2[10] on P5_1 */ scu.TX_MIX_BP = P5_2; /* GPIO2[11] on P5_2 */ @@ -277,8 +324,10 @@ const platform_scu_t* platform_scu(void) scu.AMP_BYPASS = P2_10; /* GPIO0[14] on P2_10 */ scu.RX_AMP = P2_11; /* GPIO1[11] on P2_11 */ scu.NO_RX_AMP_PWR = P2_12; /* GPIO1[12] on P2_12 */ - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.BY_AMP = P1_7; /* GPIO1[0] on P1_7 */ scu.BY_AMP_N = P2_5; /* GPIO5[5] on P2_5 */ scu.TX_RX = P2_10; /* GPIO0[14] on P2_10 */ @@ -289,8 +338,10 @@ const platform_scu_t* platform_scu(void) scu.LOW_HIGH_FILT_N = P5_3; /* GPIO2[12] on P5_3 */ scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ scu.RX_LNA = P6_7; /* GPIO5[15] on P6_7 */ - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.TX_EN = P6_5; /* GPIO3[4] on P6_5 */ scu.MIX_EN_N = P6_3; /* GPIO3[2] on P6_3 */ scu.MIX_EN_N_R1_0 = P2_6; /* GPIO5[6] on P2_6 */ @@ -298,10 +349,12 @@ const platform_scu_t* platform_scu(void) scu.RF_AMP_EN = PA_2; /* GPIO4[9] on PA_2 */ scu.ANT_BIAS_EN_N = P2_12; /* GPIO1[12] on P2_12 */ scu.ANT_BIAS_OC_N = P2_11; /* GPIO1[11] on P2_11 */ - ) + } +#endif /* Praline */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.P2_CTRL0 = (PE_3); /* GPIO7[3] on PE_3 */ scu.P2_CTRL1 = (PE_4); /* GPIO7[4] on PE_4 */ scu.P1_CTRL0 = (P2_10); /* GPIO0[14] on P2_10 */ @@ -323,10 +376,12 @@ const platform_scu_t* platform_scu(void) scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) + } +#endif /* HackRF One r9 */ - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { scu.H1R9_CLKIN_EN = P6_7; /* GPIO5[15] on P6_7 */ scu.H1R9_CLKOUT_EN = P1_2; /* GPIO0[9] on P1_2 = has boot pull-down; */ scu.H1R9_MCU_CLK_EN = P1_1; /* GPIO0[8] on P1_1 = has boot pull-up; */ @@ -335,10 +390,12 @@ const platform_scu_t* platform_scu(void) scu.H1R9_EN1V8 = P5_0; /* GPIO2[9] on P5_0 */ scu.H1R9_NO_VAA_EN = P6_10; /* GPIO3[6] on P6_10 */ scu.H1R9_TRIGGER_EN = P2_5; /* GPIO5[5] on P2_5 */ - ) + } +#endif /* Expansion headers */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { scu.PINMUX_PP_D0 = (P7_0); /* GPIO3[8] */ scu.PINMUX_PP_D1 = (P7_1); /* GPIO3[9] */ scu.PINMUX_PP_D2 = (P7_2); /* GPIO3[10] */ @@ -378,7 +435,8 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ - ) + } +#endif scu.PINMUX_ISP = (P2_7); /* GPIO0[7] */ diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 584934cb..dddecb16 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -200,18 +200,22 @@ static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank) case TRANSCEIVER_MODE_SS: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_set_tx_interpolation_ratio(&fpga, n); - ) + } +#endif radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX] = n; } break; default: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_set_rx_decimation_ratio(&fpga, n); - ) + } +#endif radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX] = n; } } @@ -403,7 +407,8 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) if (requested_lo != RADIO_UNSET) { freq_lo = mixer_set_frequency(&mixer, freq_lo, false); } - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { if (requested_rotation != RADIO_UNSET) { rotation = requested_rotation; } @@ -419,10 +424,13 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) default: rotation = 0; } - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { rotation = 0; - ) + } +#endif /* Handle requested RF (auto-tune). */ if (requested_rf != RADIO_UNSET) { @@ -432,7 +440,8 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) freq_rf = restrict_rf(requested_rf, img_reject); /* Look up settings appropriate for requested RF. */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { const tune_config_t* tune_config = select_tune_config(opmode, freq_rf); if (requested_rotation == RADIO_UNSET) { @@ -448,15 +457,18 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) } } high_lo = tune_config->high_lo; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { /* Use graduated or fixed IF on older platforms. */ if (requested_if == RADIO_UNSET) { freq_if = select_graduated_if(freq_rf, img_reject); } high_lo = (img_reject == RF_PATH_FILTER_LOW_PASS); analog_rf = freq_rf; - ) + } +#endif /* Compute precise LO. This is done first because the mixer LO is set in coarse steps. */ if (requested_lo == RADIO_UNSET) { @@ -512,9 +524,11 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) new_freq = true; } if ((rotation != applied_rotation) && (rotation != RADIO_UNSET)) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); - ) + } +#endif radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION] = rotation; new_freq = true; } @@ -550,9 +564,11 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { hackrf_ui()->set_frequency(freq_rf / FP_ONE_MHZ); } - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { operacake_set_range(freq_rf / FP_ONE_MHZ); - ) + } +#endif } return new_freq; } @@ -589,7 +605,8 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE]; } - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { /* Praline legacy mode always sets baseband bandwidth automatically. */ (void) bank; uint32_t lpf_bandwidth = auto_bandwidth(radio, opmode); @@ -635,8 +652,10 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) hpf_bandwidth; new_bw = true; } - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { uint64_t lpf_bandwidth; lpf_bandwidth = bank[RADIO_XCVR_TX_LPF]; if (lpf_bandwidth == RADIO_UNSET) { @@ -672,7 +691,8 @@ static bool radio_update_bandwidth(radio_t* const radio, uint64_t* bank) lpf_bandwidth; new_bw = true; } - ) + } +#endif return new_bw; } @@ -808,7 +828,8 @@ static bool radio_update_trigger(radio_t* const radio, uint64_t* bank) static bool radio_update_dc_block(radio_t* const radio, uint64_t* bank) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { const uint64_t requested = bank[RADIO_DC_BLOCK]; bool enable = requested; @@ -824,7 +845,8 @@ static bool radio_update_dc_block(radio_t* const radio, uint64_t* bank) fpga_set_rx_dc_block_enable(&fpga, enable); radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; return true; - ) + } +#endif (void) radio; (void) bank; diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index d5d4aec9..a9396300 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -298,19 +298,27 @@ static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) { - IF_JAWBREAKER ( +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { (void) rf_path; mixer_set_gpo(&mixer, gpo); - ) - IF_HACKRF_ONE ( + } +#endif +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { switchctrl_set_hackrf_one(rf_path, gpo); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { switchctrl_set_praline(rf_path, gpo); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { switchctrl_set_rad1o(rf_path, gpo); - ) + } +#endif } void rf_path_pin_setup(rf_path_t* const rf_path) @@ -323,7 +331,8 @@ void rf_path_pin_setup(rf_path_t* const rf_path) const platform_scu_t* scu = platform_scu(); #endif - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { /* Configure RF switch control signals */ // clang-format off scu_pinmux(scu->HP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); @@ -338,7 +347,8 @@ void rf_path_pin_setup(rf_path_t* const rf_path) scu_pinmux(scu->RX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux(scu->NO_RX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); // clang-format on - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { scu_pinmux(scu->H1R9_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux( scu->H1R9_NO_ANT_PWR, @@ -348,13 +358,16 @@ void rf_path_pin_setup(rf_path_t* const rf_path) scu_pinmux( scu->H1R9_NO_VAA_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { scu_pinmux(scu->TX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu_pinmux(scu->RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); gpio_output(rf_path->gpio_tx); scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) + } + #endif /* * Safe (initial) switch settings turn off both amplifiers and antenna port @@ -375,8 +388,10 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_no_tx_amp_pwr); gpio_output(rf_path->gpio_mix_bypass); gpio_output(rf_path->gpio_rx); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { /* Configure RF switch control signals */ // clang-format off scu_pinmux(scu->BY_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); @@ -413,9 +428,11 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_low_high_filt_n); gpio_output(rf_path->gpio_tx_amp); gpio_output(rf_path->gpio_rx_lna); - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { /* Configure RF switch control signals */ scu_pinmux(scu->TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); board_rev_t rev = detected_revision(); @@ -446,7 +463,8 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_mix_en_n); gpio_output(rf_path->gpio_lpf_en); gpio_output(rf_path->gpio_rf_amp_en); - ) + } +#endif } void rf_path_init(rf_path_t* const rf_path) @@ -459,14 +477,20 @@ void rf_path_init(rf_path_t* const rf_path) max283x_setup(&max283x); max283x_start(&max283x); - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { mixer_setup(&mixer, MAX2871_VARIANT); - ) - IF_NOT_RAD1O ( - IF_NOT_HACKRF_ONE ( + } +#endif +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { + #ifdef IS_NOT_HACKRF_ONE + if (IS_NOT_HACKRF_ONE) { mixer_setup(&mixer, RFFC5071_VARIANT); - ) - ) + } + #endif + } +#endif rf_path->switchctrl = SWITCHCTRL_SAFE; } diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index fbeb2d93..ab0717ea 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -113,12 +113,14 @@ void rffc5071_setup(rffc5071_driver_t* const drv) gpio_set(drv->gpio_reset); gpio_output(drv->gpio_reset); - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { /* Configure mixer PLL lock detect pin */ const platform_scu_t* scu = platform_scu(); scu_pinmux(scu->MIXER_LD, scu->MIXER_LD_PINCFG); gpio_input(drv->gpio_ld); - ) + } +#endif rffc5071_init(drv); @@ -136,9 +138,11 @@ void rffc5071_setup(rffc5071_driver_t* const drv) set_RFFC5071_FULLD(drv, 0); set_RFFC5071_MODE(drv, 1); - IF_H1_OR_PRALINE ( +#ifdef IS_H1_OR_PRALINE + if (IS_H1_OR_PRALINE) { set_RFFC5071_LOCK(drv, 1); - ) + } +#endif /* Enable reference oscillator standby */ set_RFFC5071_REFST(drv, 1); @@ -181,14 +185,18 @@ void rffc5071_lock_test(rffc5071_driver_t* const drv) bool rffc5071_check_lock(rffc5071_driver_t* const drv) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { return gpio_read(drv->gpio_ld); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { set_RFFC5071_READSEL(drv, 0b0001); rffc5071_regs_commit(drv); return !!(rffc5071_reg_read(drv, RFFC5071_READBACK_REG) & 0x8000); - ) + } +#endif return false; } @@ -369,9 +377,11 @@ bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) // // For all other boards we'll just return true to avoid a situation where a // a caller is waiting for a lock signal that will never be detected. - IF_NOT_PRALINE ( + #ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { return true; - ) + } + #endif // The RFFC5072 can be configured to output PLL lock status on // GPO4. The lock detect signal is produced by a window detector diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index 86b6c58e..4aa98265 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -57,24 +57,30 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config) scu_pinmux(scu->PINMUX_SGPIO14, scu->PINMUX_SGPIO14_PINCFG); /* GPIO5[13] */ scu_pinmux(scu->PINMUX_SGPIO15, scu->PINMUX_SGPIO15_PINCFG); /* GPIO5[14] */ - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { scu_pinmux( scu->H1R9_TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[5] */ - ) - IF_NOT_H1_R9 ( + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { scu_pinmux( scu->TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[12] */ - ) + } +#endif sgpio_cpld_set_mixer_invert(config, 0); gpio_output(config->gpio_q_invert); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { trigger_enable(false); gpio_output(config->gpio_trigger_enable); - ) + } +#endif } void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index dbedddf3..a1466393 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -269,7 +269,8 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ }; - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); @@ -280,8 +281,10 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) data[4] = SI5351C_CLK_POWERDOWN; data[5] = SI5351C_CLK_POWERDOWN; data[6] = SI5351C_CLK_POWERDOWN; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { /* CLK0: AFE_CLK */ data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | @@ -311,7 +314,8 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) /* CLK2: MCU_CLK */ data[3] = SI5351C_CLK_POWERDOWN; } - ) + } +#endif si5351c_write(drv, data, sizeof(data)); } @@ -329,7 +333,8 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) uint8_t clkout = 3; uint8_t value = 0; - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); @@ -339,22 +344,27 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) } else { value |= SI5351C_CLK_DISABLE(2); } - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); /* HackRF One r9 has only three clock generator outputs. */ - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { clkout = 2; value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); - ) - ) + } + #endif + } +#endif value |= (clkout_enabled) ? SI5351C_CLK_ENABLE(clkout) : SI5351C_CLK_DISABLE(clkout); @@ -362,14 +372,16 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) si5351c_write(drv, data, sizeof(data)); outputs_disabled = value; - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); if (clkout_enabled) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); } - ) + } +#endif } void si5351c_set_int_mode( @@ -399,7 +411,8 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source return; } si5351c_disable_all_outputs(drv); - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { /* * HackRF One r9 always uses PLL A on the XTAL input * but externally switches that input to CLKIN. @@ -410,10 +423,13 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source } else { gpio_clear(platform_gpio()->h1r9_clkin_en); } - ) - IF_NOT_H1_R9 ( + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { si5351c_configure_pll_sources(drv, source); - ) + } +#endif si5351c_configure_pll_multisynth(drv, source); active_clock_source = source; si5351c_reset_pll(drv, SI5351C_PLL_BOTH); @@ -468,7 +484,8 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -486,7 +503,8 @@ void si5351c_init(si5351c_driver_t* const drv) scu_pinmux(scu->H1R9_MCU_CLK_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); gpio_clear(gpio->h1r9_mcu_clk_en); gpio_output(gpio->h1r9_mcu_clk_en); - ) + } +#endif } /* diff --git a/firmware/common/tuning.c b/firmware/common/tuning.c index 347be0ae..9e3c41ba 100644 --- a/firmware/common/tuning.c +++ b/firmware/common/tuning.c @@ -38,10 +38,12 @@ static fp_40_24_t max_bypass_freq = FP_MHZ(2740); void tuning_setup(void) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { min_bypass_freq = FP_MHZ(2320); max_bypass_freq = FP_MHZ(2580); - ) + } +#endif } fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) @@ -50,13 +52,17 @@ fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) switch (img_reject) { case RF_PATH_FILTER_LOW_PASS: - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { freq_if = FP_MHZ(2300); - ) - IF_NOT_RAD1O ( + } +#endif +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { /* IF is graduated from 2650 MHz to 2340 MHz */ freq_if = FP_MHZ(2650) - (freq_rf / 7); - ) + } +#endif break; case RF_PATH_FILTER_HIGH_PASS: if (freq_rf < MID1_HP_FREQ) { diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index e2f75868..e48eae57 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -319,9 +319,10 @@ int main(void) } delay_us_at_mhz(10000, 96); pin_setup(); - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { enable_3v3aux_power(); -#if !defined(DFU_MODE) && !defined(RAM_MODE) + #if !defined(DFU_MODE) && !defined(RAM_MODE) enable_1v2_power(); enable_rf_power(); /* @@ -329,58 +330,75 @@ int main(void) * which is enabled when 1V2FPGA is turned on. */ clock_gen_init(); + #endif + } #endif - ) - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { enable_1v8_power(); - IF_NOT_RAD1O ( + #ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { /* * On rad1o, the clock generator power supply comes from the RF supply * which is enabled later. On H1 and Jawbreaker, the clock generator is * on the main 3V3 supply. */ clock_gen_init(); - ) - ) + } + #endif + } +#endif tuning_setup(); - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { // Set up mixer before enabling RF power, because its // GPO is used to control the antenna bias tee. mixer_setup(&mixer, RFFC5071_VARIANT); - ) - IF_H1_OR_RAD1O ( + } +#endif +#ifdef IS_H1_OR_RAD1O + if (IS_H1_OR_RAD1O) { enable_rf_power(); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { clock_gen_init(); - ) + } +#endif cpu_clock_init(); /* Wake the M0 */ ipc_halt_m0(); ipc_start_m0((uint32_t) &__ram_m0_start__); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { if (!cpld_jtag_sram_load(&jtag_cpld)) { halt_and_flash(6000000); } - ) - IF_PRALINE ( -#if defined(DFU_MODE) || defined(RAM_MODE) + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { + #if defined(DFU_MODE) || defined(RAM_MODE) selftest.fpga_image_load = SKIPPED; selftest.report.pass = false; -#else + #else fpga_image_load(&fpga_loader, 0); -#endif + #endif delay_us_at_mhz(100, 204); fpga_spi_selftest(); fpga_sgpio_selftest(); - ) + } +#endif radio_init(&radio); - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { portapack_init(); - ) + } +#endif #ifndef DFU_MODE usb_set_descriptor_by_serial_number(); @@ -389,22 +407,30 @@ int main(void) usb_set_configuration_changed_cb(usb_configuration_changed); usb_peripheral_reset(); - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { memcpy(&usb_device, &usb_device_hackrf_one, sizeof(usb_device_hackrf_one)); - ) - IF_JAWBREAKER ( + } +#endif +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { memcpy(&usb_device, &usb_device_jawbreaker, sizeof(usb_device_jawbreaker)); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { memcpy(&usb_device, &usb_device_rad1o, sizeof(usb_device_rad1o)); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { memcpy(&usb_device, &usb_device_praline, sizeof(usb_device_praline)); - ) + } +#endif usb_device_init(0, &usb_device); usb_queue_init(&usb_endpoint_control_out_queue); @@ -427,9 +453,11 @@ int main(void) rffc5071_lock_test(&mixer.rffc5071); #endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_if_xcvr_selftest(); - ) + } +#endif if (da7219_detect()) { operacake_skip_i2c_address(DA7219_ADDRESS); @@ -476,9 +504,11 @@ int main(void) sweep_mode(request.seq); break; case TRANSCEIVER_MODE_CPLD_UPDATE: - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { cpld_update(); - ) + } +#endif break; default: break; diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index eb91e5c9..393b102d 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -131,7 +131,8 @@ usb_request_status_t usb_vendor_request_reset( if (stage == USB_TRANSFER_STAGE_SETUP) { pin_shutdown(); clock_gen_shutdown(); - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { /* * Set boot pins as inputs so that the bootloader reads them * correctly after the reset. @@ -140,7 +141,8 @@ usb_request_status_t usb_vendor_request_reset( gpio_input(gpio->h1r9_mcu_clk_en); gpio_input(gpio->h1r9_clkout_en); gpio_input(gpio->h1r9_rx); - ) + } +#endif wwdt_reset(100000); diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index bf7e4985..d17a87d8 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -142,13 +142,16 @@ usb_request_status_t usb_vendor_request_write_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { (void) endpoint; (void) stage; return USB_REQUEST_STATUS_STALL; - ) + } +#endif - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { if (stage == USB_TRANSFER_STAGE_SETUP) { if (endpoint->setup.index < RFFC5071_NUM_REGS) { rffc5071_reg_write( @@ -161,20 +164,24 @@ usb_request_status_t usb_vendor_request_write_rffc5071( return USB_REQUEST_STATUS_STALL; } return USB_REQUEST_STATUS_OK; - ) + } +#endif } usb_request_status_t usb_vendor_request_read_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { (void) endpoint; (void) stage; return USB_REQUEST_STATUS_STALL; - ) + } +#endif - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { uint16_t value; if (stage == USB_TRANSFER_STAGE_SETUP) { if (endpoint->setup.index < RFFC5071_NUM_REGS) { @@ -195,7 +202,8 @@ usb_request_status_t usb_vendor_request_read_rffc5071( return USB_REQUEST_STATUS_STALL; } return USB_REQUEST_STATUS_OK; - ) + } +#endif } usb_request_status_t usb_vendor_request_set_clkout_enable( @@ -287,7 +295,8 @@ usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { if (stage == USB_TRANSFER_STAGE_SETUP) { fpga_reg_write( &fpga, @@ -296,7 +305,8 @@ usb_request_status_t usb_vendor_request_write_fpga_reg( usb_transfer_schedule_ack(endpoint->in); } return USB_REQUEST_STATUS_OK; - ) + } + #endif return USB_REQUEST_STATUS_STALL; } @@ -305,7 +315,8 @@ usb_request_status_t usb_vendor_request_read_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { if (stage == USB_TRANSFER_STAGE_SETUP) { const uint8_t value = fpga_reg_read(&fpga, endpoint->setup.index); endpoint->buffer[0] = value; @@ -318,7 +329,8 @@ usb_request_status_t usb_vendor_request_read_fpga_reg( usb_transfer_schedule_ack(endpoint->out); } return USB_REQUEST_STATUS_OK; - ) + } + #endif return USB_REQUEST_STATUS_STALL; } diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index e25f387a..403f3753 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -82,12 +82,15 @@ void generate_selftest_report(void) { char* s = &selftest.report.msg[0]; size_t c = sizeof(selftest.report.msg); - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { append(&s, &c, "Mixer: MAX2871, ID: "); append(&s, &c, itoa(selftest.mixer_id, 10)); append(&s, &c, "\n"); - ) - IF_NOT_RAD1O ( + } +#endif +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { append(&s, &c, "Mixer: RFFC5072, ID: "); append(&s, &c, itoa(selftest.mixer_id >> 3, 10)); append(&s, &c, ", Rev: "); @@ -104,7 +107,8 @@ void generate_selftest_report(void) append(&s, &c, " (FAIL)"); } append(&s, &c, "\n"); - ) + } +#endif append(&s, &c, "Clock: Si5351"); append(&s, &c, ", Rev: "); @@ -113,19 +117,26 @@ void generate_selftest_report(void) append(&s, &c, selftest.si5351_readback_ok ? "OK" : "FAIL"); append(&s, &c, "\n"); - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); append(&s, &c, "\n"); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { append(&s, &c, "Transceiver: "); - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { append(&s, &c, "MAX2839"); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { append(&s, &c, "MAX2837"); - ) + } + #endif append(&s, &c, ", readback success: "); append(&s, &c, itoa(selftest.max283x_readback_register_count, 10)); append(&s, &c, "/"); @@ -140,8 +151,10 @@ void generate_selftest_report(void) itoa(selftest.max283x_readback_expected_value, 10)); } append(&s, &c, "\n"); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { append(&s, &c, "FPGA configuration: "); append(&s, &c, test_result_to_str(selftest.fpga_image_load)); append(&s, &c, "\n"); @@ -173,7 +186,8 @@ void generate_selftest_report(void) append(&s, &c, itoa(m->avg_mag_sq_q, 10)); append(&s, &c, "\n"); } - ) + } +#endif } usb_request_status_t usb_vendor_request_read_selftest( From 9e1126d9521623883077530bccfe48494db9ffdb Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 14 Apr 2026 11:52:21 +0100 Subject: [PATCH 027/161] Use IS macros for all conditional compilation. --- firmware/common/cpld_jtag.c | 13 ++++---- firmware/common/cpld_jtag.h | 5 +-- firmware/common/hackrf_core.c | 32 ++++++++----------- firmware/common/hackrf_core.h | 13 ++++---- firmware/common/hackrf_ui.c | 8 ++--- firmware/common/max283x.c | 22 ++++++------- firmware/common/max283x.h | 20 ++++++------ firmware/common/max2871.c | 5 +-- firmware/common/mixer.c | 9 +++--- firmware/common/mixer.h | 13 +++++--- firmware/common/operacake_sctimer.c | 12 +++---- firmware/common/platform_gpio.h | 33 +++++++++---------- firmware/common/platform_scu.h | 40 +++++++++++++----------- firmware/common/radio.c | 8 ++--- firmware/common/rf_path.c | 22 ++++++------- firmware/common/rf_path.h | 11 ++++--- firmware/common/rffc5071.c | 9 ++---- firmware/common/rffc5071.h | 5 +-- firmware/common/selftest.h | 8 +++-- firmware/common/sgpio.c | 2 +- firmware/common/sgpio.h | 3 +- firmware/common/si5351c.c | 7 ++--- firmware/common/tune_config.h | 4 ++- firmware/hackrf_usb/hackrf_usb.c | 26 +++++++-------- firmware/hackrf_usb/usb_api_board_info.c | 3 +- firmware/hackrf_usb/usb_api_register.c | 7 ++--- firmware/hackrf_usb/usb_api_register.h | 4 ++- firmware/hackrf_usb/usb_api_selftest.c | 11 ++++--- firmware/hackrf_usb/usb_descriptor.c | 34 ++++++++------------ firmware/hackrf_usb/usb_descriptor.h | 16 +++++----- firmware/hackrf_usb/usb_device.c | 9 +++--- firmware/hackrf_usb/usb_device.h | 10 +++--- 32 files changed, 209 insertions(+), 215 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index b277b462..034ba196 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -19,17 +19,16 @@ * Boston, MA 02110-1301, USA. */ -#if !defined(PRALINE) || defined(UNIVERSAL) - #include -#endif - #include "cpld_jtag.h" + #include "platform_detect.h" -#if !defined(PRALINE) || defined(UNIVERSAL) + +#ifdef IS_NOT_PRALINE + #include #include "xapp058/micro.h" #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE static refill_buffer_cb refill_buffer; static uint32_t xsvf_buffer_len, xsvf_pos; static unsigned char* xsvf_buffer; @@ -102,7 +101,7 @@ void cpld_jtag_release(jtag_t* const jtag) #endif } -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE /* return 0 if success else return error code see xsvfExecute() */ int cpld_jtag_program( jtag_t* const jtag, diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index 939e5d94..0994c858 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -24,15 +24,16 @@ #include #include "gpio.h" +#include "platform_detect.h" typedef struct { gpio_t gpio_tck; -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE gpio_t gpio_tms; gpio_t gpio_tdi; gpio_t gpio_tdo; #endif -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE gpio_t gpio_pp_tms; gpio_t gpio_pp_tdo; #endif diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 0ac7ae65..d5679942 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -25,7 +25,10 @@ #include #include #include -#if !defined(RAD1O) + +#include "platform_detect.h" + +#ifdef IS_NOT_RAD1O #include #endif @@ -36,15 +39,14 @@ #include "i2c_lpc.h" #include "max283x.h" #include "max5864_target.h" -#include "platform_detect.h" #include "platform_gpio.h" #include "platform_scu.h" #include "spi_bus.h" #include "w25q80bv_target.h" -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE #include "portapack.h" #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "ice40_spi.h" #endif @@ -142,7 +144,7 @@ sgpio_config_t sgpio_config = { .slice_mode_multislice = true, }; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE static ssp_config_t ssp_config_ice40_fpga = { .data_bits = SSP_DATA_8BITS, .spi_mode = SSP_CPOL_1_CPHA_1, @@ -786,7 +788,7 @@ void ssp1_set_mode_max5864(void) spi_bus_start(max5864.bus, &ssp_config_max5864); } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void ssp1_set_mode_ice40(void) { spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); @@ -1124,41 +1126,33 @@ void pin_setup(void) sgpio_configure_pin_functions(&sgpio_config); } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void enable_1v2_power(void) { - #ifdef IS_PRALINE if (IS_PRALINE) { gpio_set(platform_gpio()->gpio_1v2_enable); } - #endif } void disable_1v2_power(void) { - #ifdef IS_PRALINE if (IS_PRALINE) { gpio_clear(platform_gpio()->gpio_1v2_enable); } - #endif } void enable_3v3aux_power(void) { - #ifdef IS_PRALINE if (IS_PRALINE) { gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); } - #endif } void disable_3v3aux_power(void) { - #ifdef IS_PRALINE if (IS_PRALINE) { gpio_set(platform_gpio()->gpio_3v3aux_enable_n); } - #endif } #endif @@ -1198,7 +1192,7 @@ void disable_1v8_power(void) #endif } -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE static inline void enable_rf_power_hackrf_one(void) { const platform_gpio_t* gpio = platform_gpio(); @@ -1226,7 +1220,7 @@ static inline void disable_rf_power_hackrf_one(void) } #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE static inline void enable_rf_power_praline(void) { gpio_clear(platform_gpio()->vaa_disable); @@ -1241,7 +1235,7 @@ static inline void disable_rf_power_praline(void) } #endif -#if defined(RAD1O) +#ifdef IS_RAD1O static inline void enable_rf_power_rad1o(void) { gpio_set(platform_gpio()->vaa_enable); @@ -1379,7 +1373,7 @@ void halt_and_flash(const uint32_t duration) } } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void p1_ctrl_set(const p1_ctrl_signal_t signal) { const platform_gpio_t* gpio = platform_gpio(); diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 93bc44c9..56221f6b 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -36,13 +36,14 @@ extern "C" { #include "max283x.h" #include "max5864.h" #include "mixer.h" +#include "platform_detect.h" // IWYU pragma: keep #include "radio.h" #include "rf_path.h" #include "sgpio.h" #include "si5351c.h" #include "spi_ssp.h" #include "w25q80bv.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "fpga.h" #include "ice40_spi.h" #endif @@ -52,7 +53,7 @@ extern si5351c_driver_t clock_gen; extern ssp_config_t ssp_config_w25q80bv; extern max283x_driver_t max283x; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif @@ -71,14 +72,14 @@ void clock_gen_init(void); void clock_gen_shutdown(void); void ssp1_set_mode_max283x(void); void ssp1_set_mode_max5864(void); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void ssp1_set_mode_ice40(void); #endif void pin_shutdown(void); void pin_setup(void); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void enable_1v2_power(void); void disable_1v2_power(void); void enable_3v3aux_power(void); @@ -91,7 +92,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program); clock_source_t activate_best_clock_source(void); -#if defined(RAD1O) || defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_RAD1O) || defined(IS_HACKRF_ONE) || defined(IS_PRALINE) void enable_rf_power(void); void disable_rf_power(void); #endif @@ -112,7 +113,7 @@ void trigger_enable(const bool enable); void halt_and_flash(const uint32_t duration); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE typedef enum { P1_SIGNAL_TRIGGER_IN = 0, P1_SIGNAL_AUX_CLK1 = 1, diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index b90ccc14..4224d40f 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -23,14 +23,12 @@ #include #include "hackrf_ui.h" +#include "platform_detect.h" // IWYU pragma: keep #include "transceiver_mode.h" -#if !defined(JAWBREAKER) - #include "platform_detect.h" -#endif -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE #include "ui_portapack.h" #endif -#if defined(RAD1O) +#ifdef IS_RAD1O #include "ui_rad1o.h" #endif diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 856c5d99..3b49ab6e 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -31,19 +31,19 @@ #include "platform_gpio.h" #include "spi_bus.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "max2831_target.h" #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE #include "max2837_target.h" #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_H1_R9 #include "max2839_target.h" #endif extern spi_bus_t spi_bus_ssp1; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE max2831_driver_t max2831 = { .bus = &spi_bus_ssp1, .target_init = max2831_target_init, @@ -51,7 +51,7 @@ max2831_driver_t max2831 = { }; #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE max2837_driver_t max2837 = { .bus = &spi_bus_ssp1, .target_init = max2837_target_init, @@ -59,7 +59,7 @@ max2837_driver_t max2837 = { }; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE max2839_driver_t max2839 = { .bus = &spi_bus_ssp1, .target_init = max2839_target_init, @@ -113,7 +113,7 @@ void max283x_setup(max283x_driver_t* const drv) /* clang-format off */ -#if defined(UNIVERSAL) +#if UNIVERSAL // UNIVERSAL: all variants #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ if (_drv->type == MAX2831_VARIANT) { \ @@ -123,7 +123,7 @@ void max283x_setup(max283x_driver_t* const drv) } else { \ _max2839; \ } -#elif defined(HACKRF_ONE) +#elif HACKRF_ONE // HACKRF_ONE: MAX2837 and MAX2839 only #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ if (_drv->type == MAX2837_VARIANT) { \ @@ -131,7 +131,7 @@ void max283x_setup(max283x_driver_t* const drv) } else { \ _max2839; \ } -#elif defined(PRALINE) +#elif PRALINE // PRALINE: MAX2831 only #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ (void) drv; \ @@ -251,9 +251,7 @@ uint32_t max283x_set_lpf_bandwidth( const max283x_mode_t mode, const uint32_t bandwidth_hz) { -#if !defined(PRALINE) (void) mode; -#endif DISPATCH( drv, return max2831_set_lpf_bandwidth( @@ -294,9 +292,7 @@ void max283x_set_rx_hpf_frequency( max283x_driver_t* const drv, const max283x_rx_hpf_freq_t freq) { -#if !defined(PRALINE) && !defined(UNIVERSAL) (void) freq; -#endif PRALINE_ONLY( drv, max2831_set_rx_hpf_frequency( diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index bcafff06..01bb40b0 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -27,13 +27,15 @@ #include #include "fixed_point.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#include "platform_detect.h" + +#ifdef IS_PRALINE #include "max2831.h" #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE #include "max2837.h" #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_H1_R9 #include "max2839.h" #endif @@ -55,13 +57,13 @@ typedef enum { } max283x_rx_hpf_freq_t; typedef enum { -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE MAX2831_VARIANT, #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE MAX2837_VARIANT, #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_H1_R9 MAX2839_VARIANT, #endif } max283x_variant_t; @@ -70,13 +72,13 @@ typedef struct { max283x_variant_t type; union { -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE max2831_driver_t max2831; #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE max2837_driver_t max2837; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_H1_R9 max2839_driver_t max2839; #endif } drv; diff --git a/firmware/common/max2871.c b/firmware/common/max2871.c index 4c5a94b2..05840678 100644 --- a/firmware/common/max2871.c +++ b/firmware/common/max2871.c @@ -24,17 +24,18 @@ #include #include +#include + #include "fixed_point.h" #include "max2871_regs.h" -#include "selftest.h" #include "platform_scu.h" +#include "selftest.h" #if (defined DEBUG) #include #define LOG printf #else #define LOG(x, ...) - #include #endif #define MIN(x, y) ((x) < (y) ? (x) : (y)) diff --git a/firmware/common/mixer.c b/firmware/common/mixer.c index fb921350..e38c28bf 100644 --- a/firmware/common/mixer.c +++ b/firmware/common/mixer.c @@ -26,15 +26,16 @@ #include "fixed_point.h" #include "platform_detect.h" #include "platform_gpio.h" -#if defined(RAD1O) +#ifdef IS_RAD1O #include "max2871.h" -#else +#endif +#ifdef IS_NOT_RAD1O #include "rffc5071.h" #include "rffc5071_spi.h" #include "spi_bus.h" #endif -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O static rffc5071_spi_config_t rffc5071_spi_config; static spi_bus_t spi_bus_rffc5071 = { @@ -47,7 +48,7 @@ static spi_bus_t spi_bus_rffc5071 = { #endif mixer_driver_t mixer = { -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O .rffc5071.bus = &spi_bus_rffc5071, #endif }; diff --git a/firmware/common/mixer.h b/firmware/common/mixer.h index 0afacb6b..f7861725 100644 --- a/firmware/common/mixer.h +++ b/firmware/common/mixer.h @@ -23,12 +23,15 @@ #pragma once #include +#include #include "fixed_point.h" +#include "platform_detect.h" -#if defined(RAD1O) +#ifdef IS_RAD1O #include "max2871.h" -#else +#endif +#ifdef IS_NOT_RAD1O #include "rffc5071.h" #endif @@ -41,15 +44,15 @@ typedef struct { mixer_variant_t type; union { -#if defined(RAD1O) +#ifdef IS_RAD1O max2871_driver_t max2871; -#else +#endif +#ifdef IS_NOT_RAD1O rffc5071_driver_t rffc5071; #endif }; } mixer_driver_t; -#include extern void mixer_bus_setup(mixer_driver_t* const mixer); extern void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type); diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 2dbe07d9..3b95f6a1 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -25,16 +25,16 @@ #include #include #include -#if !defined(PRALINE) + +#include "delay.h" +#include "platform_detect.h" +#include "sct.h" + +#ifdef IS_NOT_PRALINE #include #include #endif -#include - -#include "delay.h" -#include "sct.h" - #define U1CTRL_SET SCT_OUT14_SET #define U1CTRL_CLR SCT_OUT14_CLR #define U2CTRL0_SET SCT_OUT13_SET diff --git a/firmware/common/platform_gpio.h b/firmware/common/platform_gpio.h index 16767136..eeb37b05 100644 --- a/firmware/common/platform_gpio.h +++ b/firmware/common/platform_gpio.h @@ -27,10 +27,11 @@ extern "C" { #include "gpio.h" #include "gpio_lpc.h" +#include "platform_detect.h" typedef struct { /* LEDs */ -#if defined(PRALINE) || defined(UNIVERSAL) || defined(RAD1O) +#ifdef IS_FOUR_LEDS gpio_t led[4]; #else gpio_t led[3]; @@ -38,7 +39,7 @@ typedef struct { /* Power Supply Control */ gpio_t gpio_1v8_enable; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t gpio_1v2_enable; gpio_t gpio_3v3aux_enable_n; #endif @@ -48,7 +49,7 @@ typedef struct { gpio_t max283x_enable; gpio_t max283x_rx_enable; gpio_t max283x_tx_enable; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t max2831_rxhp; gpio_t max2831_ld; #endif @@ -58,7 +59,7 @@ typedef struct { /* RF supply (VAA) control */ gpio_t vaa_disable; -#if defined(RAD1O) +#ifdef IS_RAD1O gpio_t vaa_enable; #endif @@ -68,7 +69,7 @@ typedef struct { gpio_t w25q80bv_select; /* RF switch control */ -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE gpio_t hp; gpio_t lp; gpio_t tx_mix_bp; @@ -85,7 +86,7 @@ typedef struct { gpio_t h1r9_no_ant_pwr; // HACKRF_ONE r9 gpio_t h1r9_rx; // HACKRF_ONE r9 #endif -#if defined(RAD1O) +#ifdef IS_RAD1O gpio_t tx_rx_n; gpio_t tx_rx; gpio_t by_mix; @@ -98,7 +99,7 @@ typedef struct { gpio_t tx_amp; gpio_t rx_lna; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t tx_en; gpio_t mix_en_n; gpio_t mix_en_n_r1_0; @@ -109,23 +110,23 @@ typedef struct { /* CPLD JTAG interface GPIO pins, FPGA config pins in Praline */ gpio_t cpld_tck; -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE + gpio_t cpld_tdo; gpio_t cpld_tms; gpio_t cpld_tdi; - gpio_t cpld_tdo; #endif -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE gpio_t cpld_pp_tms; gpio_t cpld_pp_tdo; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t fpga_cfg_creset; gpio_t fpga_cfg_cdone; gpio_t fpga_cfg_spi_cs; #endif /* Other CPLD interface GPIO pins */ -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE gpio_t trigger_enable; #endif gpio_t q_invert; @@ -144,7 +145,7 @@ typedef struct { gpio_t synt_rfout_en; // RAD1O /* Praline */ -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t p2_ctrl0; gpio_t p2_ctrl1; gpio_t p1_ctrl0; @@ -157,7 +158,7 @@ typedef struct { gpio_t pps_out; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE /* HackRF One r9 clock control */ gpio_t h1r9_clkin_en; gpio_t h1r9_clkout_en; @@ -169,14 +170,14 @@ typedef struct { #endif /* RAD1O */ -#if defined(RAD1O) +#ifdef IS_RAD1O gpio_t lcd_cs; gpio_t lcd_bl_en; gpio_t lcd_reset; #endif /* Portapack */ -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE gpio_t io_stbx; gpio_t addr; gpio_t lcd_rdx; diff --git a/firmware/common/platform_scu.h b/firmware/common/platform_scu.h index 2c5365e3..1ffb224c 100644 --- a/firmware/common/platform_scu.h +++ b/firmware/common/platform_scu.h @@ -29,6 +29,8 @@ extern "C" { #include +#include "platform_detect.h" + /* * SCU PinMux */ @@ -38,13 +40,13 @@ typedef struct { scu_grp_pin_t PINMUX_LED1; scu_grp_pin_t PINMUX_LED2; scu_grp_pin_t PINMUX_LED3; -#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_FOUR_LEDS scu_grp_pin_t PINMUX_LED4; #endif scu_grp_pin_t PINMUX_EN1V8; scu_grp_pin_t PINMUX_EN1V2; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t PINMUX_EN3V3_AUX_N; scu_grp_pin_t PINMUX_EN3V3_OC_N; #endif @@ -52,7 +54,7 @@ typedef struct { /* GPIO Input PinMux */ scu_grp_pin_t PINMUX_BOOT0; scu_grp_pin_t PINMUX_BOOT1; -#if !defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_NOT_HACKRF_ONE scu_grp_pin_t PINMUX_BOOT2; scu_grp_pin_t PINMUX_BOOT3; #endif @@ -63,7 +65,7 @@ typedef struct { scu_grp_pin_t PINMUX_PP_DIR; /* USB peripheral */ -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER scu_grp_pin_t PINMUX_USB_LED0; scu_grp_pin_t PINMUX_USB_LED1; #endif @@ -75,7 +77,7 @@ typedef struct { scu_grp_pin_t SSP1_CS; /* CPLD JTAG interface */ -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t PINMUX_FPGA_CRESET; scu_grp_pin_t PINMUX_FPGA_CDONE; scu_grp_pin_t PINMUX_FPGA_SPI_CS; @@ -127,15 +129,15 @@ typedef struct { uint32_t XCVR_RXENABLE_PINCFG; uint32_t XCVR_TXENABLE_PINCFG; uint32_t XCVR_CS_PINCFG; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t XCVR_LD; uint32_t XCVR_LD_PINCFG; uint32_t XCVR_RXHP_PINCFG; #endif -#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_RAD1O) || defined(IS_PRALINE) scu_grp_pin_t XCVR_RXHP; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O scu_grp_pin_t XCVR_B6; scu_grp_pin_t XCVR_B7; #endif @@ -145,7 +147,7 @@ typedef struct { scu_grp_pin_t AD_CS_PINCFG; /* RFFC5071 GPIO serial interface PinMux */ -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O scu_grp_pin_t MIXER_ENX; scu_grp_pin_t MIXER_SCLK; scu_grp_pin_t MIXER_SDATA; @@ -153,11 +155,11 @@ typedef struct { uint32_t MIXER_SCLK_PINCFG; uint32_t MIXER_SDATA_PINCFG; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t MIXER_LD; uint32_t MIXER_LD_PINCFG; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O scu_grp_pin_t VCO_CE; scu_grp_pin_t VCO_SCLK; scu_grp_pin_t VCO_SDATA; @@ -168,15 +170,15 @@ typedef struct { #endif /* RF LDO control */ -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER scu_grp_pin_t RF_LDO_ENABLE; #endif /* RF supply (VAA) control */ -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) +#if defined(IS_PRALINE) || defined(IS_HACKRF_ONE) scu_grp_pin_t NO_VAA_ENABLE; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O scu_grp_pin_t VAA_ENABLE; #endif @@ -189,7 +191,7 @@ typedef struct { scu_grp_pin_t FLASH_WP; /* RF switch control */ -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t TX_EN; scu_grp_pin_t MIX_EN_N; scu_grp_pin_t MIX_EN_N_R1_0; @@ -198,7 +200,7 @@ typedef struct { scu_grp_pin_t ANT_BIAS_EN_N; scu_grp_pin_t ANT_BIAS_OC_N; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE scu_grp_pin_t HP; scu_grp_pin_t LP; scu_grp_pin_t TX_MIX_BP; @@ -213,7 +215,7 @@ typedef struct { scu_grp_pin_t RX_AMP; scu_grp_pin_t NO_RX_AMP_PWR; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O scu_grp_pin_t BY_AMP; scu_grp_pin_t BY_AMP_N; scu_grp_pin_t TX_RX; @@ -227,7 +229,7 @@ typedef struct { #endif /* Praline */ -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t P2_CTRL0; scu_grp_pin_t P2_CTRL1; scu_grp_pin_t P1_CTRL0; @@ -252,7 +254,7 @@ typedef struct { #endif /* HackRF One r9 */ -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE scu_grp_pin_t H1R9_CLKIN_EN; scu_grp_pin_t H1R9_CLKOUT_EN; scu_grp_pin_t H1R9_MCU_CLK_EN; diff --git a/firmware/common/radio.c b/firmware/common/radio.c index dddecb16..3990462b 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -35,11 +35,11 @@ #include "sgpio.h" #include "transceiver_mode.h" #include "tuning.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "fpga.h" #include "tune_config.h" #endif -#if defined(HACKRF_ONE) || defined(PRALINE) +#ifdef IS_EXPANSION_COMPATIBLE #include "operacake.h" #endif @@ -305,7 +305,7 @@ static fp_40_24_t digital_from_analog_rf( return drf; } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE /* * Convert center frequency of digital baseband (as seen by MCU) to center * frequency of analog baseband (as seen by ADC/DAC). @@ -366,7 +366,7 @@ static bool radio_update_frequency(radio_t* const radio, uint64_t* bank) const uint64_t requested_if = bank[RADIO_FREQUENCY_IF]; const uint64_t requested_lo = bank[RADIO_FREQUENCY_LO]; const uint64_t requested_img_reject = bank[RADIO_IMAGE_REJECT]; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE const uint64_t requested_rotation = bank[RADIO_ROTATION]; #endif diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index a9396300..5b81b178 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -23,10 +23,6 @@ #include "rf_path.h" -#if !defined(JAWBREAKER) - #include -#endif - #include "hackrf_core.h" #include "hackrf_ui.h" #include "max283x.h" @@ -35,7 +31,9 @@ #include "sgpio.h" #include "platform_detect.h" #include "transceiver_mode.h" -#if !defined(JAWBREAKER) + +#ifdef IS_NOT_JAWBREAKER + #include #include "gpio.h" #include "platform_scu.h" #endif @@ -96,7 +94,7 @@ #define SWITCHCTRL_ANT_PWR (1 << 6) /* turn on antenna port power */ -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) { board_id_t board_id = detected_platform(); @@ -190,7 +188,7 @@ static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) } #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl) { if (ctrl & SWITCHCTRL_TX) { @@ -231,7 +229,7 @@ static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl) } #endif -#if defined(RAD1O) +#ifdef IS_RAD1O static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) { if (ctrl & SWITCHCTRL_TX) { @@ -323,11 +321,9 @@ static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) void rf_path_pin_setup(rf_path_t* const rf_path) { -#if defined(JAWBREAKER) +#ifdef JAWBREAKER (void) rf_path; -#endif - -#if !defined(JAWBREAKER) +#else const platform_scu_t* scu = platform_scu(); #endif @@ -536,7 +532,7 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX); break; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE case RF_PATH_DIRECTION_TX_CALIBRATION: case RF_PATH_DIRECTION_RX_CALIBRATION: rf_path->switchctrl &= ~SWITCHCTRL_TX; diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index 4eb91d3f..74b70579 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -25,8 +25,9 @@ #include +#include "platform_detect.h" // IWYU pragma: keep #include "transceiver_mode.h" -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_HACKRF_ONE) || defined(IS_RAD1O) || defined(IS_PRALINE) #include "gpio.h" #endif @@ -34,7 +35,7 @@ typedef enum { RF_PATH_DIRECTION_OFF, RF_PATH_DIRECTION_RX, RF_PATH_DIRECTION_TX, -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE RF_PATH_DIRECTION_TX_CALIBRATION, RF_PATH_DIRECTION_RX_CALIBRATION, #endif @@ -50,7 +51,7 @@ typedef struct { uint8_t switchctrl; struct { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE gpio_t gpio_hp; gpio_t gpio_lp; gpio_t gpio_tx_mix_bp; @@ -68,7 +69,7 @@ typedef struct { gpio_t gpio_h1r9_no_ant_pwr; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O gpio_t gpio_tx_rx_n; gpio_t gpio_tx_rx; gpio_t gpio_by_mix; @@ -82,7 +83,7 @@ typedef struct { gpio_t gpio_rx_lna; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t gpio_tx_en; gpio_t gpio_mix_en_n; gpio_t gpio_lpf_en; diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index ab0717ea..2a1866d2 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -35,17 +35,14 @@ #include #include -#if defined(PRALINE) || defined(UNIVERSAL) - #include -#endif - #include "delay.h" #include "fixed_point.h" #include "platform_detect.h" #include "rffc5071.h" #include "rffc5071_regs.def" // private register def macros #include "selftest.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE + #include #include "platform_scu.h" #endif @@ -370,7 +367,7 @@ void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t gpo) rffc5071_regs_commit(drv); } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) { // This is only supported on Praline hardware. diff --git a/firmware/common/rffc5071.h b/firmware/common/rffc5071.h index 6a0c4ce2..c6b2c0c2 100644 --- a/firmware/common/rffc5071.h +++ b/firmware/common/rffc5071.h @@ -27,6 +27,7 @@ #include "fixed_point.h" #include "gpio.h" +#include "platform_detect.h" // IWYU pragma: keep #include "spi_bus.h" /* 31 registers, each containing 16 bits of data. */ @@ -35,7 +36,7 @@ typedef struct { spi_bus_t* const bus; gpio_t gpio_reset; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t gpio_ld; #endif uint16_t regs[RFFC5071_NUM_REGS]; @@ -70,7 +71,7 @@ extern void rffc5071_enable(rffc5071_driver_t* const drv); extern void rffc5071_disable(rffc5071_driver_t* const drv); extern void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE extern bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state); #endif extern bool rffc5071_check_lock(rffc5071_driver_t* const drv); diff --git a/firmware/common/selftest.h b/firmware/common/selftest.h index 8223a582..9fca53a6 100644 --- a/firmware/common/selftest.h +++ b/firmware/common/selftest.h @@ -24,6 +24,8 @@ #include #include +#include "platform_detect.h" // IWYU pragma: keep + #define NUM_LOCK_ATTEMPTS 3 enum { @@ -37,10 +39,10 @@ typedef uint8_t test_result_t; typedef struct { uint16_t mixer_id; -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O bool mixer_locks[NUM_LOCK_ATTEMPTS]; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE uint16_t max2831_mux_rssi_1; uint16_t max2831_mux_temp; uint16_t max2831_mux_rssi_2; @@ -52,7 +54,7 @@ typedef struct { uint8_t max283x_readback_total_registers; uint8_t si5351_rev_id; bool si5351_readback_ok; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE test_result_t fpga_image_load; test_result_t fpga_spi; test_result_t sgpio_rx; diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index 4aa98265..9d03924a 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -31,7 +31,7 @@ #include "platform_detect.h" #include "platform_scu.h" #include "sgpio.h" -#if !defined(PRALINE) +#ifdef IS_NOT_PRALINE #include "hackrf_core.h" #endif diff --git a/firmware/common/sgpio.h b/firmware/common/sgpio.h index f6fdf213..409cef0c 100644 --- a/firmware/common/sgpio.h +++ b/firmware/common/sgpio.h @@ -26,6 +26,7 @@ #include #include "gpio.h" +#include "platform_detect.h" // IWYU pragma: keep typedef enum { SGPIO_DIRECTION_RX, @@ -34,7 +35,7 @@ typedef enum { typedef struct { gpio_t gpio_q_invert; -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE gpio_t gpio_trigger_enable; #endif bool slice_mode_multislice; diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index a1466393..0acff63e 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -23,16 +23,13 @@ #include #include -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - #include -#endif - #include "clkin.h" #include "delay.h" #include "platform_detect.h" #include "selftest.h" #include "si5351c.h" -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE + #include #include "gpio.h" #include "platform_gpio.h" #include "platform_scu.h" diff --git a/firmware/common/tune_config.h b/firmware/common/tune_config.h index 34c692e6..dd81f62d 100644 --- a/firmware/common/tune_config.h +++ b/firmware/common/tune_config.h @@ -21,7 +21,9 @@ #pragma once -#if defined(PRALINE) || defined(UNIVERSAL) +#include "platform_detect.h" + +#ifdef IS_PRALINE #include "fpga.h" typedef struct { diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index e48eae57..032d58e7 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -46,14 +46,14 @@ #include #include #include -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE #include #endif -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O #include #include #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include #if !(defined(DFU_MODE) || defined(RAM_MODE)) #include @@ -61,7 +61,7 @@ #include #endif #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE #include #include #endif @@ -79,10 +79,10 @@ #include "usb_descriptor.h" #include "usb_device.h" #include "usb_endpoint.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "usb_api_praline.h" #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE #include "usb_api_cpld.h" #endif @@ -100,7 +100,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_si5351c, usb_vendor_request_set_sample_rate_frac, usb_vendor_request_set_baseband_filter_bandwidth, -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O usb_vendor_request_write_rffc5071, usb_vendor_request_read_rffc5071, #else @@ -120,7 +120,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_set_vga_gain, usb_vendor_request_set_txvga_gain, NULL, // was set_if_freq -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE) usb_vendor_request_set_antenna_enable, #else NULL, @@ -137,7 +137,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_spiflash_status, usb_vendor_request_spiflash_clear_status, usb_vendor_request_operacake_gpio_test, -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE usb_vendor_request_cpld_checksum, #else NULL, @@ -154,7 +154,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_supported_platform, usb_vendor_request_set_leds, usb_vendor_request_user_config_set_bias_t_opts, -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE usb_vendor_request_write_fpga_reg, usb_vendor_request_read_fpga_reg, usb_vendor_request_p2_ctrl, @@ -250,7 +250,7 @@ void usb_set_descriptor_by_serial_number(void) } } -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE static bool cpld_jtag_sram_load(jtag_t* const jtag) { cpld_jtag_take(jtag); @@ -278,7 +278,7 @@ static void m0_rom_to_ram(void) memcpy(dest, (uint32_t*) (base + src), len); } -#if (defined(PRALINE) || defined(UNIVERSAL)) && !(defined(DFU_MODE) || defined(RAM_MODE)) +#if defined(IS_PRALINE) && !(defined(DFU_MODE) || defined(RAM_MODE)) extern uint32_t _binary_fpga_bin_start; void fpga_loader_setup(void) @@ -449,7 +449,7 @@ int main(void) rf_path_init(&rf_path); -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O rffc5071_lock_test(&mixer.rffc5071); #endif diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index 393b102d..920ad749 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -20,7 +20,6 @@ * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ - #include "usb_api_board_info.h" #include @@ -34,7 +33,7 @@ #include #include #include -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE #include #include #endif diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index d17a87d8..b9498dab 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -21,7 +21,6 @@ * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ - #include "usb_api_register.h" #include @@ -36,10 +35,10 @@ #include #include #include -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include #endif -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O #include #include #endif @@ -290,7 +289,7 @@ usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( return USB_REQUEST_STATUS_OK; } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) diff --git a/firmware/hackrf_usb/usb_api_register.h b/firmware/hackrf_usb/usb_api_register.h index 58a84aff..aef02d03 100644 --- a/firmware/hackrf_usb/usb_api_register.h +++ b/firmware/hackrf_usb/usb_api_register.h @@ -23,6 +23,8 @@ #pragma once +#include "platform_detect.h" // IWYU pragma: keep + #include #include @@ -56,7 +58,7 @@ usb_request_status_t usb_vendor_request_set_leds( usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index 403f3753..4c812366 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -19,11 +19,10 @@ * Boston, MA 02110-1301, USA. */ +#include "usb_api_selftest.h" + #include #include -#if !defined(RAD1O) - #include -#endif #include #include @@ -34,7 +33,9 @@ #include #include -#include "usb_api_selftest.h" +#ifdef IS_NOT_RAD1O + #include +#endif static char* itoa(int val, int base) { @@ -61,7 +62,7 @@ void append(char** dest, size_t* capacity, const char* str) } } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE static const char* test_result_to_str(test_result_t result) { switch (result) { diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index f213f484..36531ebc 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -26,17 +26,9 @@ #include -#define USB_VENDOR_ID (0x1D50) +#include "platform_detect.h" -#if (defined HACKRF_ONE || defined PRALINE) - #define USB_PRODUCT_ID (0x6089) -#elif JAWBREAKER - #define USB_PRODUCT_ID (0x604B) -#elif RAD1O - #define USB_PRODUCT_ID (0xCC15) -#else - #define USB_PRODUCT_ID (0xFFFF) -#endif +#define USB_VENDOR_ID (0x1D50) #define USB_API_VERSION (0x0111) @@ -51,7 +43,7 @@ #define USB_STRING_LANGID (0x0409) -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE) uint8_t usb_descriptor_device_hackrf[] = { 18, // bLength USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType @@ -69,7 +61,7 @@ uint8_t usb_descriptor_device_hackrf[] = { 0x01 // bNumConfigurations }; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER uint8_t usb_descriptor_device_jawbreaker[] = { 18, // bLength USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType @@ -87,7 +79,7 @@ uint8_t usb_descriptor_device_jawbreaker[] = { 0x01 // bNumConfigurations }; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O uint8_t usb_descriptor_device_rad1o[] = { 18, // bLength USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType @@ -223,7 +215,7 @@ uint8_t usb_descriptor_string_manufacturer[] = { 's', 0x00, }; -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE uint8_t usb_descriptor_string_product_hackrf_one[] = { 22, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType @@ -239,7 +231,7 @@ uint8_t usb_descriptor_string_product_hackrf_one[] = { 'e', 0x00, }; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE uint8_t usb_descriptor_string_product_praline[] = { 22, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType @@ -255,7 +247,7 @@ uint8_t usb_descriptor_string_product_praline[] = { 'o', 0x00, }; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER uint8_t usb_descriptor_string_product_jawbreaker[] = { 36, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType @@ -278,7 +270,7 @@ uint8_t usb_descriptor_string_product_jawbreaker[] = { 'r', 0x00, }; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O uint8_t usb_descriptor_string_product_rad1o[] = { 12, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType @@ -329,7 +321,7 @@ uint8_t usb_descriptor_string_serial_number[] = { uint8_t usb_descriptor_string_serial_number[USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN]; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE uint8_t* usb_descriptor_strings_hackrf_one[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, @@ -339,7 +331,7 @@ uint8_t* usb_descriptor_strings_hackrf_one[] = { 0, // TERMINATOR }; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE uint8_t* usb_descriptor_strings_praline[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, @@ -349,7 +341,7 @@ uint8_t* usb_descriptor_strings_praline[] = { 0, // TERMINATOR }; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER uint8_t* usb_descriptor_strings_jawbreaker[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, @@ -359,7 +351,7 @@ uint8_t* usb_descriptor_strings_jawbreaker[] = { 0, // TERMINATOR }; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O uint8_t* usb_descriptor_strings_rad1o[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, diff --git a/firmware/hackrf_usb/usb_descriptor.h b/firmware/hackrf_usb/usb_descriptor.h index 93f3eb78..ef41d6ac 100644 --- a/firmware/hackrf_usb/usb_descriptor.h +++ b/firmware/hackrf_usb/usb_descriptor.h @@ -24,13 +24,15 @@ #include -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#include "platform_detect.h" + +#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE) extern uint8_t usb_descriptor_device_hackrf[]; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER extern uint8_t usb_descriptor_device_jawbreaker[]; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O extern uint8_t usb_descriptor_device_rad1o[]; #endif extern uint8_t usb_descriptor_device_qualifier[]; @@ -38,16 +40,16 @@ extern uint8_t usb_descriptor_configuration_full_speed[]; extern uint8_t usb_descriptor_configuration_high_speed[]; extern uint8_t usb_descriptor_string_languages[]; extern uint8_t usb_descriptor_string_manufacturer[]; -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE extern uint8_t usb_descriptor_string_product_hackrf_one[]; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE extern uint8_t usb_descriptor_string_product_praline[]; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER extern uint8_t usb_descriptor_string_product_jawbreaker[]; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O extern uint8_t usb_descriptor_string_product_rad1o[]; #endif diff --git a/firmware/hackrf_usb/usb_device.c b/firmware/hackrf_usb/usb_device.c index 4ad46424..a2af1158 100644 --- a/firmware/hackrf_usb/usb_device.c +++ b/firmware/hackrf_usb/usb_device.c @@ -25,6 +25,7 @@ #include +#include "platform_detect.h" #include "usb_descriptor.h" usb_configuration_t usb_configuration_high_speed = { @@ -47,7 +48,7 @@ usb_configuration_t* usb_configurations[] = { usb_device_t usb_device; -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE const usb_device_t usb_device_hackrf_one = { .descriptor = usb_descriptor_device_hackrf, .descriptor_strings = usb_descriptor_strings_hackrf_one, @@ -58,7 +59,7 @@ const usb_device_t usb_device_hackrf_one = { .wcid_feature_descriptor = wcid_feature_descriptor, }; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE const usb_device_t usb_device_praline = { .descriptor = usb_descriptor_device_hackrf, .descriptor_strings = usb_descriptor_strings_praline, @@ -69,7 +70,7 @@ const usb_device_t usb_device_praline = { .wcid_feature_descriptor = wcid_feature_descriptor, }; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER const usb_device_t usb_device_jawbreaker = { .descriptor = usb_descriptor_device_jawbreaker, .descriptor_strings = usb_descriptor_strings_jawbreaker, @@ -80,7 +81,7 @@ const usb_device_t usb_device_jawbreaker = { .wcid_feature_descriptor = wcid_feature_descriptor, }; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O const usb_device_t usb_device_rad1o = { .descriptor = usb_descriptor_device_rad1o, .descriptor_strings = usb_descriptor_strings_rad1o, diff --git a/firmware/hackrf_usb/usb_device.h b/firmware/hackrf_usb/usb_device.h index 528d084d..f442db37 100644 --- a/firmware/hackrf_usb/usb_device.h +++ b/firmware/hackrf_usb/usb_device.h @@ -25,17 +25,19 @@ #include +#include "platform_detect.h" + extern usb_device_t usb_device; -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE extern const usb_device_t usb_device_hackrf_one; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE extern const usb_device_t usb_device_praline; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER extern const usb_device_t usb_device_jawbreaker; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O extern const usb_device_t usb_device_rad1o; #endif From f8c3856d7cfc60365f9480bb6e56f004a433876b Mon Sep 17 00:00:00 2001 From: grvvy Date: Wed, 15 Apr 2026 22:57:47 -0600 Subject: [PATCH 028/161] Add temporary HIL CI fix - sleep 4s after programming the EUT --- ci-scripts/hackrf_pro_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ci-scripts/hackrf_pro_test.py b/ci-scripts/hackrf_pro_test.py index 1e9ac9f4..5cbc20b9 100644 --- a/ci-scripts/hackrf_pro_test.py +++ b/ci-scripts/hackrf_pro_test.py @@ -1594,6 +1594,7 @@ def main(): # TODO: check if the provided firmware directory exists if args.fwupdate: program(eut_host_dir, args.fwupdate, eut_sn, args.unattended) + time.sleep(4) eut_sn = find_sn("EUT", eut_host_dir, args.factory, [tester_sn]) if not args.rev: From e1dc0528158bdbb63dea2c6e4a7e5be5af56f3f8 Mon Sep 17 00:00:00 2001 From: grvvy Date: Wed, 15 Apr 2026 22:56:06 -0600 Subject: [PATCH 029/161] Jenkinsfile: ensure usb hub commands are wrapped in retry blocks --- Jenkinsfile | 175 ++++++++++++++++++++++++++-------------------------- 1 file changed, 88 insertions(+), 87 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 72f54209..b41d6714 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -20,6 +20,9 @@ pipeline { agent any stages { stage('Build Docker Image') { + options { + timeout(time: 20, unit: 'MINUTES') + } steps { sh 'docker build -t hackrf https://github.com/greatscottgadgets/hackrf.git' } @@ -32,35 +35,34 @@ pipeline { args docker_args } } + options { + timeout(time: 20, unit: 'MINUTES') + } steps { sh './ci-scripts/install_host.sh' sh './ci-scripts/build_firmware.sh HACKRF_ONE' script { - allOff(); + allOff() + reset('h1_eut') } + sh 'sleep 1s' retry(3) { - script { - reset('h1_eut') - } - sh 'sleep 1s' sh './ci-scripts/test_host.sh' } - retry(3) { - script { - reset('h1_tester h1_eut') - } - sh 'sleep 1s' - script { - // Allow 5 minutes for the test to run - runCommand(5, 'MINUTES', "HackRF One Test", h1_test) - allOff() - } + script { + reset('h1_tester h1_eut') } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) + } + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' retry(3) { - script { - reset('h1_eut') - } - sh 'sleep 1s' sh 'python3 ci-scripts/test_sgpio_debug.py' } } @@ -73,35 +75,34 @@ pipeline { args docker_args } } + options { + timeout(time: 20, unit: 'MINUTES') + } steps { sh './ci-scripts/install_host.sh' sh './ci-scripts/build_firmware.sh UNIVERSAL' script { - allOff(); + allOff() + reset('h1_eut') } + sh 'sleep 1s' retry(3) { - script { - reset('h1_eut') - } - sh 'sleep 1s' sh './ci-scripts/test_host.sh' } - retry(3) { - script { - reset('h1_tester h1_eut') - } - sh 'sleep 1s' - script { - // Allow 5 minutes for the test to run - runCommand(5, 'MINUTES', "HackRF One Test", h1_test) - allOff() - } + script { + reset('h1_tester h1_eut') } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) + } + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' retry(3) { - script { - reset('h1_eut') - } - sh 'sleep 1s' sh 'python3 ci-scripts/test_sgpio_debug.py' } } @@ -114,28 +115,27 @@ pipeline { args "$docker_args" } } + options { + timeout(time: 20, unit: 'MINUTES') + } steps { sh './ci-scripts/install_host.sh' sh './ci-scripts/build_firmware.sh PRALINE' script { - allOff(); + allOff() + reset('hpro_eut') } + sh 'sleep 1s' retry(3) { - script { - reset('hpro_eut') - } - sh 'sleep 1s' sh './ci-scripts/test_host.sh' } - retry(3) { - script { - reset('hpro_tester hpro_eut') - } - sh 'sleep 1s' - script { - // Allow 5 minutes for the test to run - runCommand(5, 'MINUTES', "HackRF Pro Test", hpro_test) - } + script { + reset('hpro_tester hpro_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) } } } @@ -147,28 +147,27 @@ pipeline { args "$docker_args" } } + options { + timeout(time: 20, unit: 'MINUTES') + } steps { sh './ci-scripts/install_host.sh' sh './ci-scripts/build_firmware.sh UNIVERSAL' script { - allOff(); + allOff() + reset('hpro_eut') } + sh 'sleep 1s' retry(3) { - script { - reset('hpro_eut') - } - sh 'sleep 1s' sh './ci-scripts/test_host.sh' } - retry(3) { - script { - reset('hpro_tester hpro_eut') - } - sh 'sleep 1s' - script { - // Allow 5 minutes for the test to run - runCommand(5, 'MINUTES', "HackRF Pro Test", hpro_test) - } + script { + reset('hpro_tester hpro_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) } } } @@ -184,32 +183,34 @@ pipeline { } def allOff() { - // Allow 20 seconds for the USB hub port power server to respond - runCommand(20, 'SECONDS', 'USB hub port power server command', "hubs all off") + // Allow up to 3 retries, 20 seconds each, for the USB hub port power server to respond appropriately + runCommand(3, 20, 'SECONDS', 'USB hub port power server command', "hubs all off") } def reset(devices) { - // Allow 20 seconds for the USB hub port power server to respond - runCommand(20, 'SECONDS', 'USB hub port power server command', "hubs ${devices} reset") + // Allow up to 3 retries, 20 seconds each, for the USB hub port power server to respond appropriately + runCommand(3, 20, 'SECONDS', 'USB hub port power server command', "hubs ${devices} reset") } -def runCommand(time, unit, title, cmd) { - try { - timeout(time: time, unit: unit) { - sh "${cmd}" +def runCommand(retries, time, unit, title, cmd) { + retry(retries) { + try { + timeout(time: time, unit: unit) { + sh "${cmd}" + } + } catch (FlowInterruptedException err) { + // Check if the cause was specifically an exceeded timeout + def cause = err.getCauses().get(0) + if (cause instanceof org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution.ExceededTimeout) { + echo "${title} timeout reached." + throw err // Re-throw the exception to fail the build + } else { + echo "Build interrupted for another reason." + throw err // Re-throw the exception to fail the build + } + } catch (Exception err) { + echo "An unrelated error occurred: ${err.getMessage()}" + throw err } - } catch (FlowInterruptedException err) { - // Check if the cause was specifically an exceeded timeout - def cause = err.getCauses().get(0) - if (cause instanceof org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution.ExceededTimeout) { - echo "${title} timeout reached." - throw err // Re-throw the exception to fail the build - } else { - echo "Build interrupted for another reason." - throw err // Re-throw the exception to fail the build - } - } catch (Exception err) { - echo "An unrelated error occurred: ${err.getMessage()}" - throw err } } From a36f5ca08ae97c021b42a3b2ab3da61179b944fc Mon Sep 17 00:00:00 2001 From: grvvy Date: Thu, 16 Apr 2026 16:30:16 -0600 Subject: [PATCH 030/161] ci: increase wait time after running spiflash, add support for building firmware for different boards --- Jenkinsfile | 155 +++++++++--------- ...ild_hpro_firmware.sh => build_firmware.sh} | 2 +- ci-scripts/build_h1_firmware.sh | 9 - ci-scripts/hackrf_pro_test.py | 4 +- 4 files changed, 82 insertions(+), 88 deletions(-) rename ci-scripts/{build_hpro_firmware.sh => build_firmware.sh} (80%) delete mode 100755 ci-scripts/build_h1_firmware.sh diff --git a/Jenkinsfile b/Jenkinsfile index 2b03c4de..bf79d51e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,100 +1,101 @@ import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException +def docker_args = '''--group-add=20 --group-add=46 --device-cgroup-rule="c 189:* rmw" \ + --device-cgroup-rule="c 166:* rmw" -v /dev/bus/usb:/dev/bus/usb \ + -v /tmp/req_pipe:/tmp/req_pipe -v /tmp/res_pipe:/tmp/res_pipe''' + +def h1_test = '''python3 ci-scripts/hackrf_test.py --ci --log log \ + --hostdir host/build/hackrf-tools/src/ \ + --fwupdate firmware/hackrf_usb/build/ \ + --tester 0000000000000000325866e629a25623 \ + --eut RunningFromRAM --unattended --rev r4''' + +def hpro_test = '''python3 ci-scripts/hackrf_pro_test.py --ci --log log \ + --hostdir host/build/hackrf-tools/src \ + --fwupdate firmware/hackrf_usb/build \ + --tester 0000000000000000a06063c82338145f \ + --eut RunningFromRAM -p --rev r1.2''' + pipeline { agent any stages { stage('Build Docker Image') { + options { + timeout(time: 20, unit: 'MINUTES') + } steps { sh 'docker build -t hackrf https://github.com/greatscottgadgets/hackrf.git' } } - stage('Test HackRF One') { + stage('Test HackRF One with BOARD=HACKRF_ONE') { agent { docker { image 'hackrf' reuseNode true - args '''--group-add=20 --group-add=46 --device-cgroup-rule="c 189:* rmw" \ - --device-cgroup-rule="c 166:* rmw" -v /dev/bus/usb:/dev/bus/usb \ - -v /tmp/req_pipe:/tmp/req_pipe -v /tmp/res_pipe:/tmp/res_pipe''' + args docker_args } } + options { + timeout(time: 20, unit: 'MINUTES') + } steps { sh './ci-scripts/install_host.sh' - sh './ci-scripts/build_h1_firmware.sh' + sh './ci-scripts/build_firmware.sh HACKRF_ONE' script { - allOff(); + allOff() + reset('h1_eut') } + sh 'sleep 1s' retry(3) { - script { - reset('h1_eut') - } - sh 'sleep 1s' sh './ci-scripts/test_host.sh' } - retry(3) { - script { - reset('h1_tester h1_eut') - } - sh 'sleep 1s' - script { - // Allow 5 minutes for the test to run - runCommand(5, 'MINUTES', "HackRF One Test", - '''python3 ci-scripts/hackrf_test.py --ci --log log \ - --hostdir host/build/hackrf-tools/src/ \ - --fwupdate firmware/hackrf_usb/build/ \ - --tester 0000000000000000325866e629a25623 \ - --eut RunningFromRAM --unattended --rev r4''' - ) - allOff() - } + script { + reset('h1_tester h1_eut') } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) + } + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' retry(3) { - script { - reset('h1_eut') - } - sh 'sleep 1s' sh 'python3 ci-scripts/test_sgpio_debug.py' } } } - stage('Test HackRF Pro') { + stage('Test HackRF Pro with BOARD=PRALINE') { agent { docker { image 'hackrf' reuseNode true - args '''--group-add=20 --group-add=46 --device-cgroup-rule="c 189:* rmw" \ - --device-cgroup-rule="c 166:* rmw" -v /dev/bus/usb:/dev/bus/usb \ - -v /tmp/req_pipe:/tmp/req_pipe -v /tmp/res_pipe:/tmp/res_pipe''' + args "$docker_args" } } + options { + timeout(time: 20, unit: 'MINUTES') + } steps { sh './ci-scripts/install_host.sh' - sh './ci-scripts/build_hpro_firmware.sh' + sh './ci-scripts/build_firmware.sh PRALINE' script { - allOff(); + allOff() + reset('hpro_eut') } + sh 'sleep 1s' retry(3) { - script { - reset('hpro_eut') - } - sh 'sleep 1s' sh './ci-scripts/test_host.sh' } - retry(3) { - script { - reset('hpro_tester hpro_eut') - } - sh 'sleep 1s' - script { - // Allow 5 minutes for the test to run - runCommand(5, 'MINUTES', "HackRF Pro Test", - '''python3 ci-scripts/hackrf_pro_test.py --ci --log log \ - --hostdir host/build/hackrf-tools/src \ - --fwupdate firmware/hackrf_usb/build \ - --tester 0000000000000000a06063c82338145f \ - --eut RunningFromRAM -p --rev r1.2''' - ) - } + script { + reset('hpro_tester hpro_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) } } } @@ -110,32 +111,34 @@ pipeline { } def allOff() { - // Allow 20 seconds for the USB hub port power server to respond - runCommand(20, 'SECONDS', 'USB hub port power server command', "hubs all off") + // Allow up to 3 retries, 20 seconds each, for the USB hub port power server to respond appropriately + runCommand(3, 20, 'SECONDS', 'USB hub port power server command', "hubs all off") } def reset(devices) { - // Allow 20 seconds for the USB hub port power server to respond - runCommand(20, 'SECONDS', 'USB hub port power server command', "hubs ${devices} reset") + // Allow up to 3 retries, 20 seconds each, for the USB hub port power server to respond appropriately + runCommand(3, 20, 'SECONDS', 'USB hub port power server command', "hubs ${devices} reset") } -def runCommand(time, unit, title, cmd) { - try { - timeout(time: time, unit: unit) { - sh "${cmd}" +def runCommand(retries, time, unit, title, cmd) { + retry(retries) { + try { + timeout(time: time, unit: unit) { + sh "${cmd}" + } + } catch (FlowInterruptedException err) { + // Check if the cause was specifically an exceeded timeout + def cause = err.getCauses().get(0) + if (cause instanceof org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution.ExceededTimeout) { + echo "${title} timeout reached." + throw err // Re-throw the exception to fail the build + } else { + echo "Build interrupted for another reason." + throw err // Re-throw the exception to fail the build + } + } catch (Exception err) { + echo "An unrelated error occurred: ${err.getMessage()}" + throw err } - } catch (FlowInterruptedException err) { - // Check if the cause was specifically an exceeded timeout - def cause = err.getCauses().get(0) - if (cause instanceof org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution.ExceededTimeout) { - echo "${title} timeout reached." - throw err // Re-throw the exception to fail the build - } else { - echo "Build interrupted for another reason." - throw err // Re-throw the exception to fail the build - } - } catch (Exception err) { - echo "An unrelated error occurred: ${err.getMessage()}" - throw err } } diff --git a/ci-scripts/build_hpro_firmware.sh b/ci-scripts/build_firmware.sh similarity index 80% rename from ci-scripts/build_hpro_firmware.sh rename to ci-scripts/build_firmware.sh index 830d4bf7..b6fc4ec4 100755 --- a/ci-scripts/build_hpro_firmware.sh +++ b/ci-scripts/build_firmware.sh @@ -4,6 +4,6 @@ git submodule init git submodule update cd firmware/hackrf_usb rm -rf build -cmake -DBOARD=PRALINE -B build +cmake -DBOARD=$1 -B build cmake --build build cd ../.. diff --git a/ci-scripts/build_h1_firmware.sh b/ci-scripts/build_h1_firmware.sh deleted file mode 100755 index 762d64de..00000000 --- a/ci-scripts/build_h1_firmware.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -e -git submodule init -git submodule update -cd firmware/hackrf_usb -rm -rf build -cmake -DBOARD=HACKRF_ONE -B build -cmake --build build -cd ../.. diff --git a/ci-scripts/hackrf_pro_test.py b/ci-scripts/hackrf_pro_test.py index 1e9ac9f4..3d0b1c63 100644 --- a/ci-scripts/hackrf_pro_test.py +++ b/ci-scripts/hackrf_pro_test.py @@ -1413,11 +1413,11 @@ def program(bin_dir, fw_dir, serial, unattended=False): if spiflash.returncode != 0: log(spiflash.stdout + spiflash.stderr) fail(70) + time.sleep(3) then = time.time() device_found = False while time.time() < (then + 5): - time.sleep(1) flash_info = subprocess.run([bin_dir + "/hackrf_info"], capture_output=True, timeout=TIMEOUT) if serial == "RunningFromRAM" and not unattended: @@ -1433,7 +1433,7 @@ def program(bin_dir, fw_dir, serial, unattended=False): if serial in flash_info.stdout.decode('utf-8', errors='ignore'): device_found = True break - time.sleep(0.1) + time.sleep(1) if not device_found: fail(75) From 5120383da39f6d8f0a7bdaf67900fbea5b0076dc Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 20:48:53 +0100 Subject: [PATCH 031/161] Universalize: platform_gpio.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/platform_gpio.c | 381 ++++++++++++++++++++------------ firmware/common/platform_gpio.h | 29 ++- 2 files changed, 255 insertions(+), 155 deletions(-) diff --git a/firmware/common/platform_gpio.c b/firmware/common/platform_gpio.c index 3e0db2c6..f9a44903 100644 --- a/firmware/common/platform_gpio.c +++ b/firmware/common/platform_gpio.c @@ -23,9 +23,7 @@ #include -#if defined(HACKRF_ONE) - #include "platform_detect.h" -#endif +#include "platform_detect.h" // clang-format off @@ -36,180 +34,287 @@ const platform_gpio_t* platform_gpio(void) return _platform_gpio; } + board_id_t board_id = detected_platform(); static platform_gpio_t gpio; /* LEDs */ gpio.led[0] = &GPIO2_1; gpio.led[1] = &GPIO2_2; gpio.led[2] = &GPIO2_8; + switch (board_id) { + case BOARD_ID_RAD1O: #if defined(RAD1O) - gpio.led[3] = &GPIO5_26; -#elif defined(PRALINE) - gpio.led[3] = &GPIO4_6; + gpio.led[3] = &GPIO5_26; #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.led[3] = &GPIO4_6; +#endif + break; + default: + break; + } /* Power Supply Control */ -#if defined(PRALINE) - gpio.gpio_1v2_enable = &GPIO4_7; - gpio.gpio_3v3aux_enable_n = &GPIO5_15; -#else - gpio.gpio_1v8_enable = &GPIO3_6; + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.gpio_1v2_enable = &GPIO4_7; + gpio.gpio_3v3aux_enable_n = &GPIO5_15; #endif + break; + default: + gpio.gpio_1v8_enable = &GPIO3_6; + break; + } /* MAX283x GPIO (XCVR_CTL / CS_XCVR) PinMux */ -#if defined(PRALINE) - gpio.max283x_select = &GPIO6_28; - gpio.max283x_enable = &GPIO7_1; - gpio.max283x_rx_enable = &GPIO7_2; - gpio.max2831_rxhp = &GPIO6_29; - gpio.max2831_ld = &GPIO4_11; -#else - gpio.max283x_select = &GPIO0_15; - gpio.max283x_enable = &GPIO2_6; - gpio.max283x_rx_enable = &GPIO2_5; - gpio.max283x_tx_enable = &GPIO2_4; + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.max283x_select = &GPIO6_28; + gpio.max283x_enable = &GPIO7_1; + gpio.max283x_rx_enable = &GPIO7_2; + gpio.max2831_rxhp = &GPIO6_29; + gpio.max2831_ld = &GPIO4_11; #endif + break; + default: + gpio.max283x_select = &GPIO0_15; + gpio.max283x_enable = &GPIO2_6; + gpio.max283x_rx_enable = &GPIO2_5; + gpio.max283x_tx_enable = &GPIO2_4; + break; + } /* MAX5864 SPI chip select (AD_CS / CS_AD) GPIO PinMux */ -#if defined(PRALINE) - gpio.max5864_select = &GPIO6_30; -#else - gpio.max5864_select = &GPIO2_7; + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.max5864_select = &GPIO6_30; #endif + break; + default: + gpio.max5864_select = &GPIO2_7; + break; + } /* RF supply (VAA) control */ -#if defined(HACKRF_ONE) - gpio.vaa_disable = &GPIO2_9; -#elif defined(PRALINE) - gpio.vaa_disable = &GPIO4_1; -#elif defined(RAD1O) - gpio.vaa_enable = &GPIO2_9; + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio.vaa_disable = &GPIO2_9; #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.vaa_disable = &GPIO4_1; +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + gpio.vaa_enable = &GPIO2_9; +#endif + break; + default: + break; + } /* W25Q80BV Flash */ - gpio.w25q80bv_hold = &GPIO1_14; - gpio.w25q80bv_wp = &GPIO1_15; - gpio.w25q80bv_select = &GPIO5_11; + gpio.w25q80bv_hold = &GPIO1_14; + gpio.w25q80bv_wp = &GPIO1_15; + gpio.w25q80bv_select = &GPIO5_11; /* RF switch control */ -#if defined(HACKRF_ONE) - gpio.hp = &GPIO2_0; - gpio.lp = &GPIO2_10; - gpio.tx_mix_bp = &GPIO2_11; - gpio.no_mix_bypass = &GPIO1_0; - gpio.rx_mix_bp = &GPIO2_12; - gpio.tx_amp = &GPIO2_15; - gpio.tx = &GPIO5_15; - gpio.mix_bypass = &GPIO5_16; - gpio.rx = &GPIO5_5; - gpio.no_tx_amp_pwr = &GPIO3_5; - gpio.amp_bypass = &GPIO0_14; - gpio.rx_amp = &GPIO1_11; - gpio.no_rx_amp_pwr = &GPIO1_12; - // HackRF One rev 9 - gpio.h1r9_rx = &GPIO0_7; - gpio.h1r9_no_ant_pwr = &GPIO2_4; -#elif defined(RAD1O) - gpio.tx_rx_n = &GPIO1_11; - gpio.tx_rx = &GPIO0_14; - gpio.by_mix = &GPIO1_12; - gpio.by_mix_n = &GPIO2_10; - gpio.by_amp = &GPIO1_0; - gpio.by_amp_n = &GPIO5_5; - gpio.mixer_en = &GPIO5_16; - gpio.low_high_filt = &GPIO2_11; - gpio.low_high_filt_n = &GPIO2_12; - gpio.tx_amp = &GPIO2_15; - gpio.rx_lna = &GPIO5_15; -#elif defined(PRALINE) - gpio.tx_en = &GPIO3_4; - gpio.mix_en_n = &GPIO3_2; - gpio.mix_en_n_r1_0 = &GPIO5_6; - gpio.lpf_en = &GPIO4_8; - gpio.rf_amp_en = &GPIO4_9; - gpio.ant_bias_en_n = &GPIO1_12; + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio.hp = &GPIO2_0; + gpio.lp = &GPIO2_10; + gpio.tx_mix_bp = &GPIO2_11; + gpio.no_mix_bypass = &GPIO1_0; + gpio.rx_mix_bp = &GPIO2_12; + gpio.tx_amp = &GPIO2_15; + gpio.tx = &GPIO5_15; + gpio.mix_bypass = &GPIO5_16; + gpio.rx = &GPIO5_5; + gpio.no_tx_amp_pwr = &GPIO3_5; + gpio.amp_bypass = &GPIO0_14; + gpio.rx_amp = &GPIO1_11; + gpio.no_rx_amp_pwr = &GPIO1_12; + // HackRF One rev 9 + gpio.h1r9_rx = &GPIO0_7; + gpio.h1r9_no_ant_pwr = &GPIO2_4; #endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + gpio.tx_rx_n = &GPIO1_11; + gpio.tx_rx = &GPIO0_14; + gpio.by_mix = &GPIO1_12; + gpio.by_mix_n = &GPIO2_10; + gpio.by_amp = &GPIO1_0; + gpio.by_amp_n = &GPIO5_5; + gpio.mixer_en = &GPIO5_16; + gpio.low_high_filt = &GPIO2_11; + gpio.low_high_filt_n = &GPIO2_12; + gpio.tx_amp = &GPIO2_15; + gpio.rx_lna = &GPIO5_15; +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.tx_en = &GPIO3_4; + gpio.mix_en_n = &GPIO3_2; + gpio.mix_en_n_r1_0 = &GPIO5_6; + gpio.lpf_en = &GPIO4_8; + gpio.rf_amp_en = &GPIO4_9; + gpio.ant_bias_en_n = &GPIO1_12; +#endif + break; + case BOARD_ID_JELLYBEAN: + case BOARD_ID_JAWBREAKER: + break; + default: + break; + } /* CPLD JTAG interface GPIO pins_FPGA config pins in Praline */ - gpio.cpld_tck = &GPIO3_0; -#if defined(PRALINE) - gpio.fpga_cfg_creset = &GPIO2_11; - gpio.fpga_cfg_cdone = &GPIO5_14; - gpio.fpga_cfg_spi_cs = &GPIO2_10; -#else - gpio.cpld_tdo = &GPIO5_18; + gpio.cpld_tck = &GPIO3_0; + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.fpga_cfg_creset = &GPIO2_11; + gpio.fpga_cfg_cdone = &GPIO5_14; + gpio.fpga_cfg_spi_cs = &GPIO2_10; #endif -#if defined(HACKRF_ONE) || defined(RAD1O) - gpio.cpld_tms = &GPIO3_4; - gpio.cpld_tdi = &GPIO3_1; + break; + default: +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio.cpld_tdo = &GPIO5_18; + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_RAD1O: + gpio.cpld_tms = &GPIO3_4; + gpio.cpld_tdi = &GPIO3_1; + break; + case BOARD_ID_JAWBREAKER: + gpio.cpld_tms = &GPIO3_1; + gpio.cpld_tdi = &GPIO3_4; + break; + default: + break; + } #endif -#if defined(JAWBREAKER) - gpio.cpld_tms = &GPIO3_1; - gpio.cpld_tdi = &GPIO3_4; -#endif -#if defined(HACKRF_ONE) || defined(PRALINE) - gpio.cpld_pp_tms = &GPIO1_1; - gpio.cpld_pp_tdo = &GPIO1_8; + break; + } + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + gpio.cpld_pp_tms = &GPIO1_1; + gpio.cpld_pp_tdo = &GPIO1_8; #endif + break; + default: + break; + } /* Other CPLD interface GPIO pins */ -#if !defined(PRALINE) - gpio.trigger_enable = &GPIO5_12; + switch (board_id) { + case BOARD_ID_PRALINE: + break; + default: +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio.trigger_enable = &GPIO5_12; #endif - gpio.q_invert = &GPIO0_13; + break; + } + gpio.q_invert = &GPIO0_13; /* RFFC5071 GPIO serial interface PinMux */ -#if defined(JAWBREAKER) || defined(HACKRF_ONE) - gpio.rffc5072_select = &GPIO2_13; - gpio.rffc5072_clock = &GPIO5_6; - gpio.rffc5072_data = &GPIO3_3; - gpio.rffc5072_reset = &GPIO2_14; -#elif defined(RAD1O) - gpio.vco_ce = &GPIO2_13; - gpio.vco_sclk = &GPIO5_6; - gpio.vco_sdata = &GPIO3_3; - gpio.vco_le = &GPIO2_14; - gpio.vco_mux = &GPIO5_25; - gpio.synt_rfout_en = &GPIO3_5; -#elif defined(PRALINE) - gpio.rffc5072_select = &GPIO2_13; - gpio.rffc5072_clock = &GPIO5_18; - gpio.rffc5072_data = &GPIO4_14; - gpio.rffc5072_reset = &GPIO2_14; - gpio.rffc5072_ld = &GPIO6_25; + switch (board_id) { + case BOARD_ID_JAWBREAKER: + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio.rffc5072_select = &GPIO2_13; + gpio.rffc5072_clock = &GPIO5_6; + gpio.rffc5072_data = &GPIO3_3; + gpio.rffc5072_reset = &GPIO2_14; #endif - - /* Praline */ -#if defined(PRALINE) - gpio.p2_ctrl0 = &GPIO7_3; - gpio.p2_ctrl1 = &GPIO7_4; - gpio.p1_ctrl0 = &GPIO0_14; - gpio.p1_ctrl1 = &GPIO5_16; - gpio.p1_ctrl2 = &GPIO3_5; - gpio.clkin_ctrl = &GPIO0_15; - gpio.aa_en = &GPIO1_7; - gpio.trigger_in = &GPIO6_26; - gpio.trigger_out = &GPIO5_6; - gpio.pps_out = &GPIO5_5; + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + gpio.vco_ce = &GPIO2_13; + gpio.vco_sclk = &GPIO5_6; + gpio.vco_sdata = &GPIO3_3; + gpio.vco_le = &GPIO2_14; + gpio.vco_mux = &GPIO5_25; + gpio.synt_rfout_en = &GPIO3_5; #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + gpio.rffc5072_select = &GPIO2_13; + gpio.rffc5072_clock = &GPIO5_18; + gpio.rffc5072_data = &GPIO4_14; + gpio.rffc5072_reset = &GPIO2_14; + gpio.rffc5072_ld = &GPIO6_25; +#endif + break; + default: + break; + } - /* HackRF One r9 */ -#if defined(HACKRF_ONE) - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - /* HackRF One r9 clock control */ - gpio.h1r9_clkin_en = &GPIO5_15; - gpio.h1r9_clkout_en = &GPIO0_9; - gpio.h1r9_mcu_clk_en = &GPIO0_8; - - /* HackRF One r9 power control */ - gpio.h1r9_1v8_enable = &GPIO2_9; - gpio.h1r9_vaa_disable = &GPIO3_6; - - gpio.h1r9_trigger_enable = &GPIO5_5; +#if defined(PRALINE) || defined(UNIVERSAL) + switch (board_id) { + case BOARD_ID_PRALINE: + gpio.p2_ctrl0 = &GPIO7_3; + gpio.p2_ctrl1 = &GPIO7_4; + gpio.p1_ctrl0 = &GPIO0_14; + gpio.p1_ctrl1 = &GPIO5_16; + gpio.p1_ctrl2 = &GPIO3_5; + gpio.clkin_ctrl = &GPIO0_15; + gpio.aa_en = &GPIO1_7; + gpio.trigger_in = &GPIO6_26; + gpio.trigger_out = &GPIO5_6; + gpio.pps_out = &GPIO5_5; + break; + default: + break; } #endif - /* RAD1O */ +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + /* HackRF One r9 clock control */ + switch (board_id) { + case BOARD_ID_HACKRF1_R9: + gpio.h1r9_clkin_en = &GPIO5_15; + gpio.h1r9_clkout_en = &GPIO0_9; + gpio.h1r9_mcu_clk_en = &GPIO0_8; + break; + default: + break; + } + + switch (board_id) { + case BOARD_ID_HACKRF1_R9: + gpio.h1r9_1v8_enable = &GPIO2_9; + gpio.h1r9_vaa_disable = &GPIO3_6; + gpio.h1r9_trigger_enable = &GPIO5_5; + break; + default: + break; + } +#endif + + /* rad1o LCD */ #if defined(RAD1O) gpio.lcd_cs = &GPIO4_12; /* P9_0 */ gpio.lcd_bl_en = &GPIO0_8; /* P1_1 */ @@ -217,11 +322,9 @@ const platform_gpio_t* platform_gpio(void) #endif /* Portapack */ -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) gpio.io_stbx = &GPIO5_0; /* P2_0 */ gpio.addr = &GPIO5_1; /* P2_1 */ - gpio.lcd_te = &GPIO5_3; /* P2_3 */ - gpio.unused = &GPIO5_7; /* P2_8 */ gpio.lcd_rdx = &GPIO5_4; /* P2_4 */ gpio.lcd_wrx = &GPIO1_10; /* P2_9 */ gpio.dir = &GPIO1_13; /* P2_13 */ diff --git a/firmware/common/platform_gpio.h b/firmware/common/platform_gpio.h index 9c32925c..16767136 100644 --- a/firmware/common/platform_gpio.h +++ b/firmware/common/platform_gpio.h @@ -30,7 +30,7 @@ extern "C" { typedef struct { /* LEDs */ -#if defined(PRALINE) || defined(RAD1O) +#if defined(PRALINE) || defined(UNIVERSAL) || defined(RAD1O) gpio_t led[4]; #else gpio_t led[3]; @@ -38,7 +38,7 @@ typedef struct { /* Power Supply Control */ gpio_t gpio_1v8_enable; -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_1v2_enable; gpio_t gpio_3v3aux_enable_n; #endif @@ -48,7 +48,7 @@ typedef struct { gpio_t max283x_enable; gpio_t max283x_rx_enable; gpio_t max283x_tx_enable; -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t max2831_rxhp; gpio_t max2831_ld; #endif @@ -68,7 +68,7 @@ typedef struct { gpio_t w25q80bv_select; /* RF switch control */ -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) gpio_t hp; gpio_t lp; gpio_t tx_mix_bp; @@ -98,7 +98,7 @@ typedef struct { gpio_t tx_amp; gpio_t rx_lna; #endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t tx_en; gpio_t mix_en_n; gpio_t mix_en_n_r1_0; @@ -109,23 +109,23 @@ typedef struct { /* CPLD JTAG interface GPIO pins, FPGA config pins in Praline */ gpio_t cpld_tck; -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(JAWBREAKER) - gpio_t cpld_tdo; +#if !defined(PRALINE) || defined(UNIVERSAL) gpio_t cpld_tms; gpio_t cpld_tdi; + gpio_t cpld_tdo; #endif -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) gpio_t cpld_pp_tms; gpio_t cpld_pp_tdo; #endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t fpga_cfg_creset; gpio_t fpga_cfg_cdone; gpio_t fpga_cfg_spi_cs; #endif /* Other CPLD interface GPIO pins */ -#if !defined(PRALINE) +#if !defined(PRALINE) || defined(UNIVERSAL) gpio_t trigger_enable; #endif gpio_t q_invert; @@ -144,7 +144,7 @@ typedef struct { gpio_t synt_rfout_en; // RAD1O /* Praline */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t p2_ctrl0; gpio_t p2_ctrl1; gpio_t p1_ctrl0; @@ -157,8 +157,7 @@ typedef struct { gpio_t pps_out; #endif - /* HackRF One r9 */ -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) /* HackRF One r9 clock control */ gpio_t h1r9_clkin_en; gpio_t h1r9_clkout_en; @@ -177,11 +176,9 @@ typedef struct { #endif /* Portapack */ -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) gpio_t io_stbx; gpio_t addr; - __attribute__((unused)) gpio_t lcd_te; - __attribute__((unused)) gpio_t unused; gpio_t lcd_rdx; gpio_t lcd_wrx; gpio_t dir; From 6176dbcdd52ba72cb4772f998329e1d269b9b90c Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 21:18:01 +0100 Subject: [PATCH 032/161] Universalize: platform_scu.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/hackrf_core.c | 2 +- firmware/common/platform_scu.c | 597 ++++++++++++++++++++------------- firmware/common/platform_scu.h | 76 +++-- 3 files changed, 405 insertions(+), 270 deletions(-) diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 1535e1e8..25e3b870 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -947,7 +947,7 @@ void pin_setup(void) #endif jtag_gpio_cpld.gpio_tck = gpio->cpld_tck; -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(JAWBREAKER) +#if !defined(PRALINE) || defined(UNIVERSAL) jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; diff --git a/firmware/common/platform_scu.c b/firmware/common/platform_scu.c index a4365635..edd6ee0e 100644 --- a/firmware/common/platform_scu.c +++ b/firmware/common/platform_scu.c @@ -25,6 +25,8 @@ #include +#include "platform_detect.h" + // clang-format off const platform_scu_t* platform_scu(void) @@ -34,44 +36,68 @@ const platform_scu_t* platform_scu(void) return _platform_scu; } + board_id_t board_id = detected_platform(); static platform_scu_t scu; /* LED PinMux */ scu.PINMUX_LED1 = SCU_PINMUX_LED1; /* GPIO2[1] on P4_1 */ scu.PINMUX_LED2 = SCU_PINMUX_LED2; /* GPIO2[2] on P4_2 */ scu.PINMUX_LED3 = SCU_PINMUX_LED3; /* GPIO2[8] on P6_12 */ -#if defined(PRALINE) - scu.PINMUX_LED4 = (P8_6); /* GPIO4[6] on P8_6 */ -#elif defined(RAD1O) - scu.PINMUX_LED4 = (PB_6); /* GPIO5[26] on PB_6 */ + switch (board_id) { + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.PINMUX_LED4 = (PB_6); /* GPIO5[26] on PB_6 */ #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_LED4 = (P8_6); /* GPIO4[6] on P8_6 */ +#endif + break; + default: + break; + } /* Power Supply PinMux */ - scu.PINMUX_EN1V8 = (P6_10); /* GPIO3[6] on P6_10 */ - scu.PINMUX_EN1V2 = (P8_7); /* GPIO4[7] on P8_7 */ -#if defined(PRALINE) - scu.PINMUX_EN3V3_AUX_N = (P6_7); /* GPIO5[15] on P6_7 */ - scu.PINMUX_EN3V3_OC_N = (P6_11); /* GPIO3[7] on P6_11 */ + scu.PINMUX_EN1V8 = (P6_10); /* GPIO3[6] on P6_10 */ + scu.PINMUX_EN1V2 = (P8_7); /* GPIO4[7] on P8_7 */ + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_EN3V3_AUX_N = (P6_7); /* GPIO5[15] on P6_7 */ + scu.PINMUX_EN3V3_OC_N = (P6_11); /* GPIO3[7] on P6_11 */ #endif + break; + default: + break; + } /* GPIO Input PinMux */ scu.PINMUX_BOOT0 = (P1_1); /* GPIO0[8] on P1_1 */ scu.PINMUX_BOOT1 = (P1_2); /* GPIO0[9] on P1_2 */ -#if !defined(HACKRF_ONE) - scu.PINMUX_BOOT2 = (P2_8); /* GPIO5[7] on P2_8 */ - scu.PINMUX_BOOT3 = (P2_9); /* GPIO1[10] on P2_9 */ + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + break; + default: +#if !defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.PINMUX_BOOT2 = (P2_8); /* GPIO5[7] on P2_8 */ + scu.PINMUX_BOOT3 = (P2_9); /* GPIO1[10] on P2_9 */ #endif - scu.PINMUX_PP_LCD_TE = (P2_3); /* GPIO5[3] on P2_3 */ - scu.PINMUX_PP_LCD_RDX = (P2_4); /* GPIO5[4] on P2_4 */ - scu.PINMUX_PP_UNUSED = (P2_8); /* GPIO5[7] on P2_8 */ - scu.PINMUX_PP_LCD_WRX = (P2_9); /* GPIO1[10] on P2_9 */ - scu.PINMUX_PP_DIR = (P2_13); /* GPIO1[13] on P2_13 */ + break; + } /* USB peripheral */ + switch (board_id) { + case BOARD_ID_JAWBREAKER: #if defined(JAWBREAKER) - scu.PINMUX_USB_LED0 = (P6_8); - scu.PINMUX_USB_LED1 = (P6_7); + scu.PINMUX_USB_LED0 = (P6_8); + scu.PINMUX_USB_LED1 = (P6_7); #endif + break; + default: + break; + } /* SSP1 Peripheral PinMux */ scu.SSP1_CIPO = (P1_3); /* P1_3 */ @@ -80,172 +106,242 @@ const platform_scu_t* platform_scu(void) scu.SSP1_CS = (P1_20); /* P1_20 */ /* CPLD JTAG interface */ - scu.PINMUX_CPLD_TCK = (P6_1); /* GPIO3[ 0] */ -#if defined(PRALINE) - scu.PINMUX_FPGA_CRESET = (P5_2); /* GPIO2[11] on P5_2 */ - scu.PINMUX_FPGA_CDONE = (P4_10); /* GPIO5[14] */ - scu.PINMUX_FPGA_SPI_CS = (P5_1); /* GPIO2[10] */ -#else - scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ + scu.PINMUX_CPLD_TCK = (P6_1); /* GPIO3[ 0] */ + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_FPGA_CRESET = (P5_2); /* GPIO2[11] on P5_2 */ + scu.PINMUX_FPGA_CDONE = (P4_10); /* GPIO5[14] */ + scu.PINMUX_FPGA_SPI_CS = (P5_1); /* GPIO2[10] */ #endif -#if defined(RAD1O) || defined(HACKRF_ONE) - scu.PINMUX_CPLD_TMS = (P6_5); /* GPIO3[ 4] */ - scu.PINMUX_CPLD_TDI = (P6_2); /* GPIO3[ 1] */ -#endif -#if defined(JAWBREAKER) - scu.PINMUX_CPLD_TMS = (P6_2); /* GPIO3[ 1] */ - scu.PINMUX_CPLD_TDI = (P6_5); /* GPIO3[ 4] */ + break; + default: + break; + } +#if !defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ + switch (board_id) { + case BOARD_ID_RAD1O: + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + scu.PINMUX_CPLD_TMS = (P6_5); /* GPIO3[ 4] */ + scu.PINMUX_CPLD_TDI = (P6_2); /* GPIO3[ 1] */ + break; + default: + scu.PINMUX_CPLD_TMS = (P6_2); /* GPIO3[ 1] */ + scu.PINMUX_CPLD_TDI = (P6_5); /* GPIO3[ 4] */ + break; + } #endif /* CPLD SGPIO interface */ -#if defined(PRALINE) - scu.PINMUX_SGPIO0 = (P0_0); - scu.PINMUX_SGPIO1 = (P0_1); - scu.PINMUX_SGPIO2 = (P1_15); - scu.PINMUX_SGPIO3 = (P1_16); - scu.PINMUX_SGPIO4 = (P9_4); - scu.PINMUX_SGPIO5 = (P6_6); - scu.PINMUX_SGPIO6 = (P2_2); - scu.PINMUX_SGPIO7 = (P1_0); - scu.PINMUX_SGPIO8 = (P8_0); - scu.PINMUX_SGPIO9 = (P9_3); - scu.PINMUX_SGPIO10 = (P8_2); - scu.PINMUX_SGPIO11 = (P1_17); - scu.PINMUX_SGPIO12 = (P1_18); - scu.PINMUX_SGPIO14 = (P1_18); - scu.PINMUX_SGPIO15 = (P1_18); + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_SGPIO0 = (P0_0); + scu.PINMUX_SGPIO1 = (P0_1); + scu.PINMUX_SGPIO2 = (P1_15); + scu.PINMUX_SGPIO3 = (P1_16); + scu.PINMUX_SGPIO4 = (P9_4); + scu.PINMUX_SGPIO5 = (P6_6); + scu.PINMUX_SGPIO6 = (P2_2); + scu.PINMUX_SGPIO7 = (P1_0); + scu.PINMUX_SGPIO8 = (P8_0); + scu.PINMUX_SGPIO9 = (P9_3); + scu.PINMUX_SGPIO10 = (P8_2); + scu.PINMUX_SGPIO11 = (P1_17); + scu.PINMUX_SGPIO12 = (P1_18); + scu.PINMUX_SGPIO14 = (P1_18); + scu.PINMUX_SGPIO15 = (P1_18); - scu.PINMUX_SGPIO0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); - scu.PINMUX_SGPIO1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); - scu.PINMUX_SGPIO2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO3_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO4_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO5_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO6_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO7_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO8_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.PINMUX_SGPIO9_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO10_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.PINMUX_SGPIO11_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#else - scu.PINMUX_SGPIO0 = (P0_0); - scu.PINMUX_SGPIO1 = (P0_1); - scu.PINMUX_SGPIO2 = (P1_15); - scu.PINMUX_SGPIO3 = (P1_16); - scu.PINMUX_SGPIO4 = (P6_3); - scu.PINMUX_SGPIO5 = (P6_6); - scu.PINMUX_SGPIO6 = (P2_2); - scu.PINMUX_SGPIO7 = (P1_0); - scu.PINMUX_SGPIO8 = (P9_6); - scu.PINMUX_SGPIO9 = (P4_3); - scu.PINMUX_SGPIO10 = (P1_14); - scu.PINMUX_SGPIO11 = (P1_17); - scu.PINMUX_SGPIO12 = (P1_18); - scu.PINMUX_SGPIO14 = (P4_9); - scu.PINMUX_SGPIO15 = (P4_10); - - scu.PINMUX_SGPIO0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); - scu.PINMUX_SGPIO1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); - scu.PINMUX_SGPIO2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO3_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO4_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO5_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); - scu.PINMUX_SGPIO6_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO7_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO8_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO9_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION7); - scu.PINMUX_SGPIO10_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO11_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); - scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PINMUX_SGPIO0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); + scu.PINMUX_SGPIO1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); + scu.PINMUX_SGPIO2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO3_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO4_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO5_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO6_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO7_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO8_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PINMUX_SGPIO9_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO10_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PINMUX_SGPIO11_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); #endif + break; + default: +#if !defined(PRALINE) || defined(UNIVERSAL) + scu.PINMUX_SGPIO0 = (P0_0); + scu.PINMUX_SGPIO1 = (P0_1); + scu.PINMUX_SGPIO2 = (P1_15); + scu.PINMUX_SGPIO3 = (P1_16); + scu.PINMUX_SGPIO4 = (P6_3); + scu.PINMUX_SGPIO5 = (P6_6); + scu.PINMUX_SGPIO6 = (P2_2); + scu.PINMUX_SGPIO7 = (P1_0); + scu.PINMUX_SGPIO8 = (P9_6); + scu.PINMUX_SGPIO9 = (P4_3); + scu.PINMUX_SGPIO10 = (P1_14); + scu.PINMUX_SGPIO11 = (P1_17); + scu.PINMUX_SGPIO12 = (P1_18); + scu.PINMUX_SGPIO14 = (P4_9); + scu.PINMUX_SGPIO15 = (P4_10); + + scu.PINMUX_SGPIO0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); + scu.PINMUX_SGPIO1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION3); + scu.PINMUX_SGPIO2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO3_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO4_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO5_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION2); + scu.PINMUX_SGPIO6_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO7_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO8_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO9_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION7); + scu.PINMUX_SGPIO10_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO11_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION6); + scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); +#endif + break; + } scu.TRIGGER_EN = (P4_8); /* GPIO5[12] on P4_8 */ /* MAX283x GPIO (XCVR_CTL) PinMux */ -#if defined(PRALINE) - scu.XCVR_ENABLE = PE_1; /* GPIO7[1] on PE_1 */ - scu.XCVR_RXENABLE = PE_2; /* GPIO7[2] on PE_2 */ - scu.XCVR_CS = PD_14; /* GPIO6[28] on PD_14 */ - scu.XCVR_RXHP = PD_15; /* GPIO6[29] on PD_15 */ - scu.XCVR_LD = P9_6; /* GPIO4[11] on P9_6 */ - - scu.XCVR_ENABLE_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.XCVR_RXHP_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.XCVR_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | - SCU_CONF_EPD_EN_PULLDOWN | SCU_CONF_EPUN_DIS_PULLUP); -#elif defined(JAWBREAKER) || defined(HACKRF_ONE) - scu.XCVR_ENABLE = P4_6; /* GPIO2[6] on P4_6 */ - scu.XCVR_RXENABLE = P4_5; /* GPIO2[5] on P4_5 */ - scu.XCVR_TXENABLE = P4_4; /* GPIO2[4] on P4_4 */ - scu.XCVR_CS = P1_20; /* GPIO0[15] on P1_20 */ - - scu.XCVR_ENABLE_PINCFG = (SCU_GPIO_FAST); - scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST); - scu.XCVR_TXENABLE_PINCFG = (SCU_GPIO_FAST); - scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST); -#elif defined(RAD1O) - scu.XCVR_RXHP = P8_1; /* GPIO[] on P8_1 */ - scu.XCVR_B6 = P8_2; /* GPIO[] on P8_2 */ - scu.XCVR_B7 = P9_3; /* GPIO[] on P9_3 */ + switch (board_id) { + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.XCVR_RXHP = P8_1; /* GPIO[] on P8_1 */ + scu.XCVR_B6 = P8_2; /* GPIO[] on P8_2 */ + scu.XCVR_B7 = P9_3; /* GPIO[] on P9_3 */ #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.XCVR_ENABLE = PE_1; /* GPIO7[1] on PE_1 */ + scu.XCVR_RXENABLE = PE_2; /* GPIO7[2] on PE_2 */ + scu.XCVR_CS = PD_14; /* GPIO6[28] on PD_14 */ + scu.XCVR_RXHP = PD_15; /* GPIO6[29] on PD_15 */ + scu.XCVR_LD = P9_6; /* GPIO4[11] on P9_6 */ + + scu.XCVR_ENABLE_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.XCVR_RXHP_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.XCVR_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | + SCU_CONF_EPD_EN_PULLDOWN | SCU_CONF_EPUN_DIS_PULLUP); +#endif + break; + case BOARD_ID_JAWBREAKER: + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.XCVR_ENABLE = P4_6; /* GPIO2[6] on P4_6 */ + scu.XCVR_RXENABLE = P4_5; /* GPIO2[5] on P4_5 */ + scu.XCVR_TXENABLE = P4_4; /* GPIO2[4] on P4_4 */ + scu.XCVR_CS = P1_20; /* GPIO0[15] on P1_20 */ + + scu.XCVR_ENABLE_PINCFG = (SCU_GPIO_FAST); + scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST); + scu.XCVR_TXENABLE_PINCFG = (SCU_GPIO_FAST); + scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST); +#endif + break; + default: + break; + } /* MAX5864 SPI chip select (AD_CS) GPIO PinMux */ -#if defined(PRALINE) - scu.AD_CS = (PD_16); /* GPIO6[30] on PD_16 */ - scu.AD_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#else - scu.AD_CS = (P5_7); /* GPIO2[7] on P5_7 */ - scu.AD_CS_PINCFG = (SCU_GPIO_FAST); + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.AD_CS = (PD_16); /* GPIO6[30] on PD_16 */ + scu.AD_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); #endif + break; + default: + scu.AD_CS = (P5_7); /* GPIO2[7] on P5_7 */ + scu.AD_CS_PINCFG = (SCU_GPIO_FAST); + break; + } /* RFFC5071 GPIO serial interface PinMux */ -#if defined(PRALINE) - scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ - scu.MIXER_SCLK = (P9_5); /* GPIO5[18] on P9_5 */ - scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ - scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ - scu.MIXER_LD = (PD_11); /* GPIO6[25] on PD_11 */ + switch (board_id) { + case BOARD_ID_JAWBREAKER: + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ + scu.MIXER_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ + scu.MIXER_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ + scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ - scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#elif defined(JAWBREAKER) || defined(HACKRF_ONE) - scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ - scu.MIXER_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ - scu.MIXER_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ - scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ - - scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#elif defined(RAD1O) - scu.VCO_CE = (P5_4); /* GPIO2[13] on P5_4 */ - scu.VCO_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ - scu.VCO_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ - scu.VCO_LE = (P5_5); /* GPIO2[14] on P5_5 */ - scu.VCO_MUX = (PB_5); /* GPIO5[25] on PB_5 */ - scu.MIXER_EN = (P6_8); /* GPIO5[16] on P6_8 */ - scu.SYNT_RFOUT_EN = (P6_9); /* GPIO3[5] on P6_9 */ + scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ + scu.MIXER_SCLK = (P9_5); /* GPIO5[18] on P9_5 */ + scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ + scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ + scu.MIXER_LD = (PD_11); /* GPIO6[25] on PD_11 */ + + scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.VCO_CE = (P5_4); /* GPIO2[13] on P5_4 */ + scu.VCO_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ + scu.VCO_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ + scu.VCO_LE = (P5_5); /* GPIO2[14] on P5_5 */ + scu.VCO_MUX = (PB_5); /* GPIO5[25] on PB_5 */ + scu.MIXER_EN = (P6_8); /* GPIO5[16] on P6_8 */ + scu.SYNT_RFOUT_EN = (P6_9); /* GPIO3[5] on P6_9 */ +#endif + break; + default: + break; + } /* RF LDO control */ + switch (board_id) { + case BOARD_ID_JAWBREAKER: #if defined(JAWBREAKER) - scu.RF_LDO_ENABLE = (P5_0); /* GPIO2[9] on P5_0 */ + scu.RF_LDO_ENABLE = (P5_0); /* GPIO2[9] on P5_0 */ #endif + break; + default: + break; + } /* RF supply (VAA) control */ -#if defined(PRALINE) - scu.NO_VAA_ENABLE = P8_1; /* GPIO4[1] on P8_1 */ -#elif defined(HACKRF_ONE) - scu.NO_VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ -#elif defined(RAD1O) - scu.VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.NO_VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.NO_VAA_ENABLE = P8_1; /* GPIO4[1] on P8_1 */ +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ +#endif + break; + default: + break; + } /* SPI flash */ scu.SSP0_CIPO = (P3_6); @@ -256,79 +352,105 @@ const platform_scu_t* platform_scu(void) scu.FLASH_WP = (P3_5); /* GPIO1[15] on P3_5 */ /* RF switch control */ -#if defined(PRALINE) - scu.TX_EN = P6_5; /* GPIO3[4] on P6_5 */ - scu.MIX_EN_N = P6_3; /* GPIO3[2] on P6_3 */ - scu.MIX_EN_N_R1_0 = P2_6; /* GPIO5[6] on P2_6 */ - scu.LPF_EN = PA_1; /* GPIO4[8] on PA_1 */ - scu.RF_AMP_EN = PA_2; /* GPIO4[9] on PA_2 */ - scu.ANT_BIAS_EN_N = P2_12; /* GPIO1[12] on P2_12 */ - scu.ANT_BIAS_OC_N = P2_11; /* GPIO1[11] on P2_11 */ -#elif defined(HACKRF_ONE) - scu.HP = P4_0; /* GPIO2[0] on P4_0 */ - scu.LP = P5_1; /* GPIO2[10] on P5_1 */ - scu.TX_MIX_BP = P5_2; /* GPIO2[11] on P5_2 */ - scu.NO_MIX_BYPASS = P1_7; /* GPIO1[0] on P1_7 */ - scu.RX_MIX_BP = P5_3; /* GPIO2[12] on P5_3 */ - scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ - scu.TX = P6_7; /* GPIO5[15] on P6_7 */ - scu.MIX_BYPASS = P6_8; /* GPIO5[16] on P6_8 */ - scu.RX = P2_5; /* GPIO5[5] on P2_5 */ - scu.NO_TX_AMP_PWR = P6_9; /* GPIO3[5] on P6_9 */ - scu.AMP_BYPASS = P2_10; /* GPIO0[14] on P2_10 */ - scu.RX_AMP = P2_11; /* GPIO1[11] on P2_11 */ - scu.NO_RX_AMP_PWR = P2_12; /* GPIO1[12] on P2_12 */ -#elif defined(RAD1O) - scu.BY_AMP = P1_7; /* GPIO1[0] on P1_7 */ - scu.BY_AMP_N = P2_5; /* GPIO5[5] on P2_5 */ - scu.TX_RX = P2_10; /* GPIO0[14] on P2_10 */ - scu.TX_RX_N = P2_11; /* GPIO1[11] on P2_11 */ - scu.BY_MIX = P2_12; /* GPIO1[12] on P2_12 */ - scu.BY_MIX_N = P5_1; /* GPIO2[10] on P5_1 */ - scu.LOW_HIGH_FILT = P5_2; /* GPIO2[11] on P5_2 */ - scu.LOW_HIGH_FILT_N = P5_3; /* GPIO2[12] on P5_3 */ - scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ - scu.RX_LNA = P6_7; /* GPIO5[15] on P6_7 */ + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + scu.HP = P4_0; /* GPIO2[0] on P4_0 */ + scu.LP = P5_1; /* GPIO2[10] on P5_1 */ + scu.TX_MIX_BP = P5_2; /* GPIO2[11] on P5_2 */ + scu.NO_MIX_BYPASS = P1_7; /* GPIO1[0] on P1_7 */ + scu.RX_MIX_BP = P5_3; /* GPIO2[12] on P5_3 */ + scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ + scu.TX = P6_7; /* GPIO5[15] on P6_7 */ + scu.MIX_BYPASS = P6_8; /* GPIO5[16] on P6_8 */ + scu.RX = P2_5; /* GPIO5[5] on P2_5 */ + scu.NO_TX_AMP_PWR = P6_9; /* GPIO3[5] on P6_9 */ + scu.AMP_BYPASS = P2_10; /* GPIO0[14] on P2_10 */ + scu.RX_AMP = P2_11; /* GPIO1[11] on P2_11 */ + scu.NO_RX_AMP_PWR = P2_12; /* GPIO1[12] on P2_12 */ #endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu.BY_AMP = P1_7; /* GPIO1[0] on P1_7 */ + scu.BY_AMP_N = P2_5; /* GPIO5[5] on P2_5 */ + scu.TX_RX = P2_10; /* GPIO0[14] on P2_10 */ + scu.TX_RX_N = P2_11; /* GPIO1[11] on P2_11 */ + scu.BY_MIX = P2_12; /* GPIO1[12] on P2_12 */ + scu.BY_MIX_N = P5_1; /* GPIO2[10] on P5_1 */ + scu.LOW_HIGH_FILT = P5_2; /* GPIO2[11] on P5_2 */ + scu.LOW_HIGH_FILT_N = P5_3; /* GPIO2[12] on P5_3 */ + scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ + scu.RX_LNA = P6_7; /* GPIO5[15] on P6_7 */ +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu.TX_EN = P6_5; /* GPIO3[4] on P6_5 */ + scu.MIX_EN_N = P6_3; /* GPIO3[2] on P6_3 */ + scu.MIX_EN_N_R1_0 = P2_6; /* GPIO5[6] on P2_6 */ + scu.LPF_EN = PA_1; /* GPIO4[8] on PA_1 */ + scu.RF_AMP_EN = PA_2; /* GPIO4[9] on PA_2 */ + scu.ANT_BIAS_EN_N = P2_12; /* GPIO1[12] on P2_12 */ + scu.ANT_BIAS_OC_N = P2_11; /* GPIO1[11] on P2_11 */ +#endif + break; + default: + break; + } /* Praline */ -#if defined(PRALINE) - scu.P2_CTRL0 = (PE_3); /* GPIO7[3] on PE_3 */ - scu.P2_CTRL1 = (PE_4); /* GPIO7[4] on PE_4 */ - scu.P1_CTRL0 = (P2_10); /* GPIO0[14] on P2_10 */ - scu.P1_CTRL1 = (P6_8); /* GPIO5[16] on P6_8 */ - scu.P1_CTRL2 = (P6_9); /* GPIO3[5] on P6_9 */ - scu.CLKIN_CTRL = (P1_20); /* GPIO0[15] on P1_20 */ - scu.AA_EN = (P1_14); /* GPIO1[7] on P1_14 */ - scu.TRIGGER_IN = (PD_12); /* GPIO6[26] on PD_12 */ - scu.TRIGGER_OUT = (P2_6); /* GPIO5[6] on P2_6 */ - scu.PPS_OUT = (P2_5); /* GPIO5[5] on P2_5 */ +#if defined(PRALINE) || defined(UNIVERSAL) + switch (board_id) { + case BOARD_ID_PRALINE: + scu.P2_CTRL0 = (PE_3); /* GPIO7[3] on PE_3 */ + scu.P2_CTRL1 = (PE_4); /* GPIO7[4] on PE_4 */ + scu.P1_CTRL0 = (P2_10); /* GPIO0[14] on P2_10 */ + scu.P1_CTRL1 = (P6_8); /* GPIO5[16] on P6_8 */ + scu.P1_CTRL2 = (P6_9); /* GPIO3[5] on P6_9 */ + scu.CLKIN_CTRL = (P1_20); /* GPIO0[15] on P1_20 */ + scu.AA_EN = (P1_14); /* GPIO1[7] on P1_14 */ + scu.TRIGGER_IN = (PD_12); /* GPIO6[26] on PD_12 */ + scu.TRIGGER_OUT = (P2_6); /* GPIO5[6] on P2_6 */ + scu.PPS_OUT = (P2_5); /* GPIO5[5] on P2_5 */ - scu.P2_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.P2_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.P1_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.P1_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.P1_CTRL2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.CLKIN_CTRL_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.AA_EN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.P2_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.P2_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.P1_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.P1_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.P1_CTRL2_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.CLKIN_CTRL_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.AA_EN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + break; + default: + break; + } #endif /* HackRF One r9 */ -#if defined(HACKRF_ONE) - scu.H1R9_CLKIN_EN = P6_7; /* GPIO5[15] on P6_7 */ - scu.H1R9_CLKOUT_EN = P1_2; /* GPIO0[9] on P1_2 = has boot pull-down; */ - scu.H1R9_MCU_CLK_EN = P1_1; /* GPIO0[8] on P1_1 = has boot pull-up; */ - scu.H1R9_RX = P2_7; /* GPIO0[7] on P4_4 = has boot pull-up; */ - scu.H1R9_NO_ANT_PWR = P4_4; /* GPIO2[4] on P4_4 */ - scu.H1R9_EN1V8 = P5_0; /* GPIO2[9] on P5_0 */ - scu.H1R9_NO_VAA_EN = P6_10; /* GPIO3[6] on P6_10 */ - scu.H1R9_TRIGGER_EN = P2_5; /* GPIO5[5] on P2_5 */ +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + switch (board_id) { + case BOARD_ID_HACKRF1_R9: + scu.H1R9_CLKIN_EN = P6_7; /* GPIO5[15] on P6_7 */ + scu.H1R9_CLKOUT_EN = P1_2; /* GPIO0[9] on P1_2 = has boot pull-down; */ + scu.H1R9_MCU_CLK_EN = P1_1; /* GPIO0[8] on P1_1 = has boot pull-up; */ + scu.H1R9_RX = P2_7; /* GPIO0[7] on P4_4 = has boot pull-up; */ + scu.H1R9_NO_ANT_PWR = P4_4; /* GPIO2[4] on P4_4 */ + scu.H1R9_EN1V8 = P5_0; /* GPIO2[9] on P5_0 */ + scu.H1R9_NO_VAA_EN = P6_10; /* GPIO3[6] on P6_10 */ + scu.H1R9_TRIGGER_EN = P2_5; /* GPIO5[5] on P2_5 */ + break; + default: + break; + } #endif - /* Miscellaneous */ + /* Expansion headers */ +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) scu.PINMUX_PP_D0 = (P7_0); /* GPIO3[8] */ scu.PINMUX_PP_D1 = (P7_1); /* GPIO3[9] */ scu.PINMUX_PP_D2 = (P7_2); /* GPIO3[10] */ @@ -338,16 +460,11 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_PP_D6 = (P7_6); /* GPIO3[14] */ scu.PINMUX_PP_D7 = (P7_7); /* GPIO3[15] */ - /* TODO add other Pins */ - - scu.PINMUX_GPIO3_8 = (P7_0); /* GPIO3[8] */ - scu.PINMUX_GPIO3_9 = (P7_1); /* GPIO3[9] */ - scu.PINMUX_GPIO3_10 = (P7_2); /* GPIO3[10] */ - scu.PINMUX_GPIO3_11 = (P7_3); /* GPIO3[11] */ - scu.PINMUX_GPIO3_12 = (P7_4); /* GPIO3[12] */ - scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ - scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ - scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ + scu.PINMUX_PP_LCD_TE = (P2_3); /* GPIO5[3] on P2_3 */ + scu.PINMUX_PP_LCD_RDX = (P2_4); /* GPIO5[4] on P2_4 */ + scu.PINMUX_PP_UNUSED = (P2_8); /* GPIO5[7] on P2_8 */ + scu.PINMUX_PP_LCD_WRX = (P2_9); /* GPIO1[10] on P2_9 */ + scu.PINMUX_PP_DIR = (P2_13); /* GPIO1[13] on P2_13 */ scu.PINMUX_PP_TDO = (P1_5); /* GPIO1[8] */ scu.PINMUX_SD_POW = (P1_5); /* GPIO1[8] */ @@ -365,6 +482,16 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_U0_TXD = (P2_0); /* GPIO5[0] */ scu.PINMUX_U0_RXD = (P2_1); /* GPIO5[1] */ + scu.PINMUX_GPIO3_8 = (P7_0); /* GPIO3[8] */ + scu.PINMUX_GPIO3_9 = (P7_1); /* GPIO3[9] */ + scu.PINMUX_GPIO3_10 = (P7_2); /* GPIO3[10] */ + scu.PINMUX_GPIO3_11 = (P7_3); /* GPIO3[11] */ + scu.PINMUX_GPIO3_12 = (P7_4); /* GPIO3[12] */ + scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ + scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ + scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ +#endif + scu.PINMUX_ISP = (P2_7); /* GPIO0[7] */ scu.PINMUX_GP_CLKIN = (P4_7); diff --git a/firmware/common/platform_scu.h b/firmware/common/platform_scu.h index 2b4b42f0..2c5365e3 100644 --- a/firmware/common/platform_scu.h +++ b/firmware/common/platform_scu.h @@ -34,18 +34,17 @@ extern "C" { */ typedef struct { - /* LED PinMux */ + /* GPIO Output PinMux */ scu_grp_pin_t PINMUX_LED1; scu_grp_pin_t PINMUX_LED2; scu_grp_pin_t PINMUX_LED3; -#if defined(RAD1O) || defined(PRALINE) +#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t PINMUX_LED4; #endif - /* Power Supply PinMux */ scu_grp_pin_t PINMUX_EN1V8; scu_grp_pin_t PINMUX_EN1V2; -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t PINMUX_EN3V3_AUX_N; scu_grp_pin_t PINMUX_EN3V3_OC_N; #endif @@ -53,7 +52,7 @@ typedef struct { /* GPIO Input PinMux */ scu_grp_pin_t PINMUX_BOOT0; scu_grp_pin_t PINMUX_BOOT1; -#if !defined(HACKRF_ONE) +#if !defined(HACKRF_ONE) || defined(UNIVERSAL) scu_grp_pin_t PINMUX_BOOT2; scu_grp_pin_t PINMUX_BOOT3; #endif @@ -76,16 +75,15 @@ typedef struct { scu_grp_pin_t SSP1_CS; /* CPLD JTAG interface */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t PINMUX_FPGA_CRESET; scu_grp_pin_t PINMUX_FPGA_CDONE; scu_grp_pin_t PINMUX_FPGA_SPI_CS; -#else +#endif scu_grp_pin_t PINMUX_CPLD_TDO; + scu_grp_pin_t PINMUX_CPLD_TCK; scu_grp_pin_t PINMUX_CPLD_TMS; scu_grp_pin_t PINMUX_CPLD_TDI; -#endif - scu_grp_pin_t PINMUX_CPLD_TCK; /* CPLD SGPIO interface */ scu_grp_pin_t PINMUX_SGPIO0; @@ -121,36 +119,45 @@ typedef struct { scu_grp_pin_t TRIGGER_EN; /* MAX283x GPIO (XCVR_CTL) PinMux */ - scu_grp_pin_t XCVR_RXHP; // RAD1O, PRALINE - scu_grp_pin_t XCVR_B6; // RAD1O - scu_grp_pin_t XCVR_B7; // RAD1O - scu_grp_pin_t XCVR_ENABLE; // PRALINE, HACKRF_ONE - scu_grp_pin_t XCVR_RXENABLE; // PRALINE, HACKRF_ONE - scu_grp_pin_t XCVR_CS; // PRALINE, HACKRF_ONE - scu_grp_pin_t XCVR_LD; // PRALINE - uint32_t XCVR_ENABLE_PINCFG; // PRALINE, HACKRF_ONE - uint32_t XCVR_RXENABLE_PINCFG; // PRALINE, HACKRF_ONE - uint32_t XCVR_CS_PINCFG; // PRALINE, HACKRF_ONE - uint32_t XCVR_RXHP_PINCFG; // PRALINE - uint32_t XCVR_LD_PINCFG; // PRALINE - scu_grp_pin_t XCVR_TXENABLE; // HACKRF_ONE - uint32_t XCVR_TXENABLE_PINCFG; // HACKRF_ONE + scu_grp_pin_t XCVR_ENABLE; + scu_grp_pin_t XCVR_RXENABLE; + scu_grp_pin_t XCVR_TXENABLE; + scu_grp_pin_t XCVR_CS; + uint32_t XCVR_ENABLE_PINCFG; + uint32_t XCVR_RXENABLE_PINCFG; + uint32_t XCVR_TXENABLE_PINCFG; + uint32_t XCVR_CS_PINCFG; +#if defined(PRALINE) || defined(UNIVERSAL) + scu_grp_pin_t XCVR_LD; + uint32_t XCVR_LD_PINCFG; + uint32_t XCVR_RXHP_PINCFG; +#endif +#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) + scu_grp_pin_t XCVR_RXHP; +#endif +#if defined(RAD1O) + scu_grp_pin_t XCVR_B6; + scu_grp_pin_t XCVR_B7; +#endif /* MAX5864 SPI chip select (AD_CS) GPIO PinMux */ scu_grp_pin_t AD_CS; scu_grp_pin_t AD_CS_PINCFG; /* RFFC5071 GPIO serial interface PinMux */ +#if !defined(RAD1O) scu_grp_pin_t MIXER_ENX; scu_grp_pin_t MIXER_SCLK; scu_grp_pin_t MIXER_SDATA; scu_grp_pin_t MIXER_RESETX; uint32_t MIXER_SCLK_PINCFG; uint32_t MIXER_SDATA_PINCFG; -#if defined(PRALINE) +#endif +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t MIXER_LD; uint32_t MIXER_LD_PINCFG; -#elif defined(RAD1O) +#endif +#if defined(RAD1O) scu_grp_pin_t VCO_CE; scu_grp_pin_t VCO_SCLK; scu_grp_pin_t VCO_SDATA; @@ -166,9 +173,10 @@ typedef struct { #endif /* RF supply (VAA) control */ -#if defined(PRALINE) || defined(HACKRF_ONE) +#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) scu_grp_pin_t NO_VAA_ENABLE; -#elif defined(RAD1O) +#endif +#if defined(RAD1O) scu_grp_pin_t VAA_ENABLE; #endif @@ -181,7 +189,7 @@ typedef struct { scu_grp_pin_t FLASH_WP; /* RF switch control */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t TX_EN; scu_grp_pin_t MIX_EN_N; scu_grp_pin_t MIX_EN_N_R1_0; @@ -189,7 +197,8 @@ typedef struct { scu_grp_pin_t RF_AMP_EN; scu_grp_pin_t ANT_BIAS_EN_N; scu_grp_pin_t ANT_BIAS_OC_N; -#elif defined(HACKRF_ONE) +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) scu_grp_pin_t HP; scu_grp_pin_t LP; scu_grp_pin_t TX_MIX_BP; @@ -203,7 +212,8 @@ typedef struct { scu_grp_pin_t AMP_BYPASS; scu_grp_pin_t RX_AMP; scu_grp_pin_t NO_RX_AMP_PWR; -#elif defined(RAD1O) +#endif +#if defined(RAD1O) scu_grp_pin_t BY_AMP; scu_grp_pin_t BY_AMP_N; scu_grp_pin_t TX_RX; @@ -217,7 +227,7 @@ typedef struct { #endif /* Praline */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) scu_grp_pin_t P2_CTRL0; scu_grp_pin_t P2_CTRL1; scu_grp_pin_t P1_CTRL0; @@ -242,7 +252,7 @@ typedef struct { #endif /* HackRF One r9 */ -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) scu_grp_pin_t H1R9_CLKIN_EN; scu_grp_pin_t H1R9_CLKOUT_EN; scu_grp_pin_t H1R9_MCU_CLK_EN; @@ -262,9 +272,7 @@ typedef struct { scu_grp_pin_t PINMUX_PP_D5; scu_grp_pin_t PINMUX_PP_D6; scu_grp_pin_t PINMUX_PP_D7; - /* TODO add other Pins */ - scu_grp_pin_t PINMUX_GPIO3_8; scu_grp_pin_t PINMUX_GPIO3_9; scu_grp_pin_t PINMUX_GPIO3_10; From d0ac3183f597974b13daf2c5e81dfb9520c5cf25 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 21:37:14 +0100 Subject: [PATCH 033/161] Universalize: cpld_jtag.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/cpld_jtag.c | 85 ++++++++++++++++++++++++++----------- firmware/common/cpld_jtag.h | 4 +- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 4ef9ce5d..21991db0 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -19,16 +19,17 @@ * Boston, MA 02110-1301, USA. */ -#if !defined(PRALINE) +#if !defined(PRALINE) || defined(UNIVERSAL) #include #endif #include "cpld_jtag.h" -#if !defined(PRALINE) +#include "platform_detect.h" +#if !defined(PRALINE) || defined(UNIVERSAL) #include "xapp058/micro.h" #endif -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) static refill_buffer_cb refill_buffer; static uint32_t xsvf_buffer_len, xsvf_pos; static unsigned char* xsvf_buffer; @@ -38,52 +39,86 @@ void cpld_jtag_take(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; + board_id_t board_id = detected_platform(); + /* Set initial GPIO state to the voltages of the internal or external pull-ups/downs, * to avoid any glitches. */ -#if (defined HACKRF_ONE || defined PRALINE) - gpio_set(gpio->gpio_pp_tms); + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + gpio_set(gpio->gpio_pp_tms); #endif + break; + default: + break; + } gpio_clear(gpio->gpio_tck); -#ifndef PRALINE - gpio_set(gpio->gpio_tms); - gpio_set(gpio->gpio_tdi); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio_set(gpio->gpio_tms); + gpio_set(gpio->gpio_tdi); #endif + } -#if (defined HACKRF_ONE || defined PRALINE) - /* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */ - gpio_input(gpio->gpio_pp_tms); - gpio_input(gpio->gpio_pp_tdo); + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */ + gpio_input(gpio->gpio_pp_tms); + gpio_input(gpio->gpio_pp_tdo); #endif + break; + default: + break; + } gpio_output(gpio->gpio_tck); -#ifndef PRALINE - gpio_output(gpio->gpio_tms); - gpio_output(gpio->gpio_tdi); - gpio_input(gpio->gpio_tdo); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio_output(gpio->gpio_tms); + gpio_output(gpio->gpio_tdi); + gpio_input(gpio->gpio_tdo); #endif + } } void cpld_jtag_release(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; + board_id_t board_id = detected_platform(); + /* Make all pins inputs when JTAG interface not active. * Let the pull-ups/downs do the work. */ -#if (defined HACKRF_ONE || defined PRALINE) - /* Do not drive PortaPack-specific pins, initially, just to be cautious. */ - gpio_input(gpio->gpio_pp_tms); - gpio_input(gpio->gpio_pp_tdo); + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Do not drive PortaPack-specific pins, initially, just to be cautious. */ + gpio_input(gpio->gpio_pp_tms); + gpio_input(gpio->gpio_pp_tdo); #endif + break; + default: + break; + } gpio_input(gpio->gpio_tck); -#ifndef PRALINE - gpio_input(gpio->gpio_tms); - gpio_input(gpio->gpio_tdi); - gpio_input(gpio->gpio_tdo); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio_input(gpio->gpio_tms); + gpio_input(gpio->gpio_tdi); + gpio_input(gpio->gpio_tdo); #endif + } } -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) /* return 0 if success else return error code see xsvfExecute() */ int cpld_jtag_program( jtag_t* const jtag, diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index 60036759..939e5d94 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -27,12 +27,12 @@ typedef struct { gpio_t gpio_tck; -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_tms; gpio_t gpio_tdi; gpio_t gpio_tdo; #endif -#if (defined HACKRF_ONE || defined PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_pp_tms; gpio_t gpio_pp_tdo; #endif From 8d4570d2cbc6e820da9381865402414713e08f53 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 21:39:02 +0100 Subject: [PATCH 034/161] Universalize: max283x.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/max283x.c | 270 ++++++++++++++++++++++---------------- firmware/common/max283x.h | 21 ++- firmware/common/rf_path.c | 8 +- 3 files changed, 181 insertions(+), 118 deletions(-) diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index c0e912e5..54b596bc 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -29,31 +29,36 @@ #include "fixed_point.h" #include "platform_gpio.h" #include "spi_bus.h" -#if defined(PRALINE) - #include "max2831.h" + +#if defined(PRALINE) || defined(UNIVERSAL) #include "max2831_target.h" -#else - #include "max2837.h" +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) #include "max2837_target.h" - #include "max2839.h" +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include "max2839_target.h" #endif extern spi_bus_t spi_bus_ssp1; -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) max2831_driver_t max2831 = { .bus = &spi_bus_ssp1, .target_init = max2831_target_init, .set_mode = max2831_target_set_mode, }; -#else +#endif + +#if !defined(PRALINE) || defined(UNIVERSAL) max2837_driver_t max2837 = { .bus = &spi_bus_ssp1, .target_init = max2837_target_init, .set_mode = max2837_target_set_mode, }; +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) max2839_driver_t max2839 = { .bus = &spi_bus_ssp1, .target_init = max2839_target_init, @@ -69,36 +74,43 @@ void max283x_setup(max283x_driver_t* const drv, max283x_variant_t type) const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) max2831.gpio_enable = gpio->max283x_enable; max2831.gpio_rxtx = gpio->max283x_rx_enable; max2831.gpio_rxhp = gpio->max2831_rxhp; max2831.gpio_ld = gpio->max2831_ld; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) max2837.gpio_enable = gpio->max283x_enable; max2837.gpio_rx_enable = gpio->max283x_rx_enable; max2837.gpio_tx_enable = gpio->max283x_tx_enable; +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) max2839.gpio_enable = gpio->max283x_enable; max2839.gpio_rxtx = gpio->max283x_rx_enable; #endif - switch (type) { -#ifdef PRALINE + switch (drv->type) { +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: memcpy(&drv->drv.max2831, &max2831, sizeof(max2831)); max2831_setup(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); max2837_setup(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); max2839_setup(&drv->drv.max2839); break; #endif + default: + break; } } @@ -106,44 +118,42 @@ void max283x_setup(max283x_driver_t* const drv, max283x_variant_t type) uint16_t max283x_num_regs(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return MAX2831_NUM_REGS; - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return MAX2837_NUM_REGS; - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return MAX2839_NUM_REGS; - break; #endif + default: + return 0; } - - return 0; } /* Returns the maximum data register value supported by the driver. */ uint16_t max283x_data_regs_max_value(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return MAX2831_DATA_REGS_MAX_VALUE; - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return MAX2837_DATA_REGS_MAX_VALUE; - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return MAX2839_DATA_REGS_MAX_VALUE; - break; #endif + default: + return 0; } - - return 0; } /* Read a register via SPI. Save a copy to memory and return @@ -151,22 +161,21 @@ uint16_t max283x_data_regs_max_value(max283x_driver_t* const drv) uint16_t max283x_reg_read(max283x_driver_t* const drv, uint8_t r) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_reg_read(&drv->drv.max2831, r); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_reg_read(&drv->drv.max2837, r); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_reg_read(&drv->drv.max2839, r); - break; #endif + default: + return 0; } - - return 0; } /* Write value to register via SPI and save a copy to memory. Mark @@ -174,19 +183,23 @@ uint16_t max283x_reg_read(max283x_driver_t* const drv, uint8_t r) void max283x_reg_write(max283x_driver_t* const drv, uint8_t r, uint16_t v) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_reg_write(&drv->drv.max2831, r, v); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_reg_write(&drv->drv.max2837, r, v); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_reg_write(&drv->drv.max2839, r, v); break; #endif + default: + break; } } @@ -196,97 +209,113 @@ void max283x_reg_write(max283x_driver_t* const drv, uint8_t r, uint16_t v) void max283x_regs_commit(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_regs_commit(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_regs_commit(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_regs_commit(&drv->drv.max2839); break; #endif + default: + break; } } void max283x_set_mode(max283x_driver_t* const drv, const max283x_mode_t new_mode) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_set_mode(&drv->drv.max2831, (max2831_mode_t) new_mode); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_set_mode(&drv->drv.max2837, (max2837_mode_t) new_mode); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_set_mode(&drv->drv.max2839, (max2839_mode_t) new_mode); break; #endif + default: + break; } } max283x_mode_t max283x_mode(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return (max283x_mode_t) max2831_mode(&drv->drv.max2831); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return (max283x_mode_t) max2837_mode(&drv->drv.max2837); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return (max283x_mode_t) max2839_mode(&drv->drv.max2839); - break; #endif + default: + return 0; } - return 0; } /* Turn on/off all chip functions. Does not control oscillator and CLKOUT */ void max283x_start(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_start(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_start(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_start(&drv->drv.max2839); break; #endif + default: + break; } } void max283x_stop(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_stop(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_stop(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_stop(&drv->drv.max2839); break; #endif + default: + break; } } @@ -297,19 +326,23 @@ fp_40_24_t max283x_set_frequency( bool program) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_frequency(&drv->drv.max2831, freq, program); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_frequency(&drv->drv.max2837, freq, program); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_frequency(&drv->drv.max2839, freq, program); break; #endif + default: + break; } return 0; } @@ -324,189 +357,206 @@ uint32_t max283x_set_lpf_bandwidth( #endif switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_lpf_bandwidth( &drv->drv.max2831, (max2831_mode_t) mode, bandwidth_hz); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_lpf_bandwidth(&drv->drv.max2837, bandwidth_hz); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_lpf_bandwidth(&drv->drv.max2839, bandwidth_hz); - break; #endif + default: + return 0; } - - return 0; } bool max283x_set_lna_gain(max283x_driver_t* const drv, const uint32_t gain_db) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_lna_gain(&drv->drv.max2831, gain_db); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_lna_gain(&drv->drv.max2837, gain_db); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_lna_gain(&drv->drv.max2839, gain_db); - break; #endif + default: + return false; } - - return false; } bool max283x_set_vga_gain(max283x_driver_t* const drv, const uint32_t gain_db) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_vga_gain(&drv->drv.max2831, gain_db); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_vga_gain(&drv->drv.max2837, gain_db); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_vga_gain(&drv->drv.max2839, gain_db); - break; #endif + default: + return false; } - - return false; } bool max283x_set_txvga_gain(max283x_driver_t* const drv, const uint32_t gain_db) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: return max2831_set_txvga_gain(&drv->drv.max2831, gain_db); - break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: return max2837_set_txvga_gain(&drv->drv.max2837, gain_db); - break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: return max2839_set_txvga_gain(&drv->drv.max2839, gain_db); - break; #endif + default: + return false; } - - return false; } void max283x_tx(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_tx(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_tx(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_tx(&drv->drv.max2839); break; #endif + default: + break; } } void max283x_rx(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_rx(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: max2837_rx(&drv->drv.max2837); break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: max2839_rx(&drv->drv.max2839); break; #endif + default: + break; } } +/* Set MAX2831 receiver high-pass filter corner frequency in Hz */ void max283x_set_rx_hpf_frequency( max283x_driver_t* const drv, const max283x_rx_hpf_freq_t freq) { -#ifndef PRALINE +#if !defined(PRALINE) (void) freq; #endif switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_set_rx_hpf_frequency( &drv->drv.max2831, (max2831_rx_hpf_freq_t) freq); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: // unsupported break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: // unsupported break; #endif + default: + break; } } void max283x_tx_calibration(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_tx_calibration(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: // unsupported - use max283x_set_mode instead break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: // unsupported - use max283x_set_mode instead break; #endif + default: + break; } } void max283x_rx_calibration(max283x_driver_t* const drv) { switch (drv->type) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case MAX2831_VARIANT: max2831_rx_calibration(&drv->drv.max2831); break; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) case MAX2837_VARIANT: // unsupported - use max283x_set_mode instead break; - +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) case MAX2839_VARIANT: // unsupported - use max283x_set_mode instead break; #endif + default: + break; } } diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index db5a93f8..87da59b4 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -27,10 +27,13 @@ #include #include "fixed_point.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "max2831.h" -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) #include "max2837.h" +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include "max2839.h" #endif @@ -52,10 +55,13 @@ typedef enum { } max283x_rx_hpf_freq_t; typedef enum { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) MAX2831_VARIANT, -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) MAX2837_VARIANT, +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) MAX2839_VARIANT, #endif } max283x_variant_t; @@ -64,10 +70,13 @@ typedef struct { max283x_variant_t type; union { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) max2831_driver_t max2831; -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) max2837_driver_t max2837; +#endif +#if defined(HACKRF_ONE) || defined(UNIVERSAL) max2839_driver_t max2839; #endif } drv; diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 4407960a..bce921ea 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -31,7 +31,9 @@ #include "max283x.h" #include "max5864.h" #include "mixer.h" -#include "platform_detect.h" +#if defined(HACKRF_ONE) || defined(PRALINE) + #include "platform_detect.h" +#endif #if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) #include "platform_scu.h" #endif @@ -441,12 +443,14 @@ void rf_path_init(rf_path_t* const rf_path) ssp1_set_mode_max283x(); #ifdef PRALINE max283x_setup(&max283x, MAX2831_VARIANT); -#else +#elif defined(HACKRF_ONE) if (detected_platform() == BOARD_ID_HACKRF1_R9) { max283x_setup(&max283x, MAX2839_VARIANT); } else { max283x_setup(&max283x, MAX2837_VARIANT); } +#else + max283x_setup(&max283x, MAX2837_VARIANT); #endif max283x_start(&max283x); From c913e81ed619703a62d06de302166c2ec9fbce2d Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 22:00:17 +0100 Subject: [PATCH 035/161] Universalize: rffc5071.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/rffc5071.c | 59 +++++++++++++++++++++++++++----------- firmware/common/rffc5071.h | 4 +-- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index ffe958c1..89befe98 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -35,16 +35,17 @@ #include #include -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include #endif #include "delay.h" #include "fixed_point.h" +#include "platform_detect.h" #include "rffc5071.h" #include "rffc5071_regs.def" // private register def macros #include "selftest.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "platform_scu.h" #endif @@ -109,15 +110,19 @@ void rffc5071_init(rffc5071_driver_t* const drv) */ void rffc5071_setup(rffc5071_driver_t* const drv) { + board_id_t board_id = detected_platform(); + gpio_set(drv->gpio_reset); gpio_output(drv->gpio_reset); -#ifdef PRALINE - /* Configure mixer PLL lock detect pin */ - const platform_scu_t* scu = platform_scu(); - scu_pinmux(scu->MIXER_LD, scu->MIXER_LD_PINCFG); - gpio_input(drv->gpio_ld); + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + /* Configure mixer PLL lock detect pin */ + const platform_scu_t* scu = platform_scu(); + scu_pinmux(scu->MIXER_LD, scu->MIXER_LD_PINCFG); + gpio_input(drv->gpio_ld); #endif + } rffc5071_init(drv); @@ -135,10 +140,16 @@ void rffc5071_setup(rffc5071_driver_t* const drv) set_RFFC5071_FULLD(drv, 0); set_RFFC5071_MODE(drv, 1); -#if defined(PRALINE) || defined(HACKRF_ONE) - /* Enable GPO Lock output signal */ - set_RFFC5071_LOCK(drv, 1); -#endif + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: + /* Enable GPO Lock output signal */ + set_RFFC5071_LOCK(drv, 1); + break; + default: + break; + } /* Enable reference oscillator standby */ set_RFFC5071_REFST(drv, 1); @@ -181,13 +192,17 @@ void rffc5071_lock_test(rffc5071_driver_t* const drv) bool rffc5071_check_lock(rffc5071_driver_t* const drv) { -#ifdef PRALINE - return gpio_read(drv->gpio_ld); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + return gpio_read(drv->gpio_ld); #else - set_RFFC5071_READSEL(drv, 0b0001); - rffc5071_regs_commit(drv); - return !!(rffc5071_reg_read(drv, RFFC5071_READBACK_REG) & 0x8000); + return false; #endif + } else { + set_RFFC5071_READSEL(drv, 0b0001); + rffc5071_regs_commit(drv); + return !!(rffc5071_reg_read(drv, RFFC5071_READBACK_REG) & 0x8000); + } } static uint16_t rffc5071_spi_read(rffc5071_driver_t* const drv, uint8_t r) @@ -360,9 +375,17 @@ void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t gpo) rffc5071_regs_commit(drv); } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) { + // This is only supported on Praline hardware. + // + // For all other boards we'll just return true to avoid a situation where a + // a caller is waiting for a lock signal that will never be detected. + if (detected_platform() != BOARD_ID_PRALINE) { + return true; + } + // The RFFC5072 can be configured to output PLL lock status on // GPO4. The lock detect signal is produced by a window detector // on the VCO tuning voltage. It goes high to show PLL lock when @@ -428,7 +451,9 @@ bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) uint8_t rsm_state = (rb >> 11) & 0b11111; // get gpo4 lock detect signal + #if defined(PRALINE) || defined(UNIVERSAL) bool gpo4_ld = gpio_read(drv->gpio_ld); + #endif // parse state switch (rsm_state) { diff --git a/firmware/common/rffc5071.h b/firmware/common/rffc5071.h index 41e0509d..6a0c4ce2 100644 --- a/firmware/common/rffc5071.h +++ b/firmware/common/rffc5071.h @@ -35,7 +35,7 @@ typedef struct { spi_bus_t* const bus; gpio_t gpio_reset; -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_ld; #endif uint16_t regs[RFFC5071_NUM_REGS]; @@ -70,7 +70,7 @@ extern void rffc5071_enable(rffc5071_driver_t* const drv); extern void rffc5071_disable(rffc5071_driver_t* const drv); extern void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) extern bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state); #endif extern bool rffc5071_check_lock(rffc5071_driver_t* const drv); From cfd729597c883a2ec5917852c33bf7b2df5d4e35 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 22:04:35 +0100 Subject: [PATCH 036/161] Universalize: si5351.c Co-authored-by: Antoine van Gelder --- firmware/common/si5351c.c | 119 ++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index ceb9f4a9..b50aad81 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -23,7 +23,7 @@ #include #include -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include #endif @@ -32,7 +32,7 @@ #include "platform_detect.h" #include "selftest.h" #include "si5351c.h" -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include "gpio.h" #include "platform_gpio.h" #include "platform_scu.h" @@ -235,6 +235,7 @@ void si5351c_configure_multisynth( void si5351c_configure_clock_control(si5351c_driver_t* const drv) { uint8_t clkout_ctrl; + board_id_t board_id = detected_platform(); if (clkout_enabled) { clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | @@ -269,7 +270,8 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ }; - if (detected_platform() == BOARD_ID_HACKRF1_R9) { + if (board_id == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); @@ -280,37 +282,41 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) data[4] = SI5351C_CLK_POWERDOWN; data[5] = SI5351C_CLK_POWERDOWN; data[6] = SI5351C_CLK_POWERDOWN; +#endif } -#ifdef PRALINE - /* CLK0: AFE_CLK */ - data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); - /* CLK1: SCT_CLK and FPGA_CLK */ - data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); - /* CLK3: CLKOUT */ - clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); - /* CLK4: XCVR_CLK */ - data[5] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA) | SI5351C_CLK_INV; - data[6] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - data[3] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + /* CLK0: AFE_CLK */ + data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); + /* CLK1: SCT_CLK and FPGA_CLK */ + data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); - } else { - /* CLK2: MCU_CLK */ - data[3] = SI5351C_CLK_POWERDOWN; - } + /* CLK3: CLKOUT */ + clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); + /* CLK4: XCVR_CLK */ + data[5] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA) | SI5351C_CLK_INV; + data[6] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); + if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { + /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ + data[3] = SI5351C_CLK_FRAC_MODE | + SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | + SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | + SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); + } else { + /* CLK2: MCU_CLK */ + data[3] = SI5351C_CLK_POWERDOWN; + } #endif + } si5351c_write(drv, data, sizeof(data)); } @@ -325,30 +331,34 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) /* 7: Clock to CPU is deactivated as it is not used and creates noise */ /* 3: External clock output is deactivated by default */ -#ifndef PRALINE - uint8_t value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | - SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); -#else - uint8_t value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | - SI5351C_CLK_DISABLE(7); - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - value |= SI5351C_CLK_ENABLE(2); - } else { - value |= SI5351C_CLK_DISABLE(2); - } -#endif + board_id_t board_id = detected_platform(); uint8_t clkout = 3; + uint8_t value; - /* HackRF One r9 has only three clock generator outputs. */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - clkout = 2; + if (board_id == BOARD_ID_PRALINE) { value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | - SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | + SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | + SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); + if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { + /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ + value |= SI5351C_CLK_ENABLE(2); + } else { + value |= SI5351C_CLK_DISABLE(2); + } + } else { + value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | + SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | + SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); + + /* HackRF One r9 has only three clock generator outputs. */ + if (board_id == BOARD_ID_HACKRF1_R9) { + clkout = 2; + value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | + SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | + SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | + SI5351C_CLK_DISABLE(7); + } } value |= (clkout_enabled) ? SI5351C_CLK_ENABLE(clkout) : @@ -357,16 +367,16 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) si5351c_write(drv, data, sizeof(data)); outputs_disabled = value; -#if defined(HACKRF_ONE) if (detected_platform() == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) const platform_gpio_t* gpio = platform_gpio(); if (clkout_enabled) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); } - } #endif + } } void si5351c_set_int_mode( @@ -466,8 +476,8 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } -#if defined(HACKRF_ONE) if (detected_platform() == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -485,9 +495,8 @@ void si5351c_init(si5351c_driver_t* const drv) scu_pinmux(scu->H1R9_MCU_CLK_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); gpio_clear(gpio->h1r9_mcu_clk_en); gpio_output(gpio->h1r9_mcu_clk_en); - } #endif - (void) drv; + } } /* From cdc17d5e6791b918e4f0afa0f0eaa44334e9bbf1 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 3 Apr 2026 22:05:13 +0100 Subject: [PATCH 037/161] Universalize: rf_path.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/rf_path.c | 354 ++++++++++++++++++++++---------------- firmware/common/rf_path.h | 77 +++++---- 2 files changed, 243 insertions(+), 188 deletions(-) diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index bce921ea..a361f442 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -23,7 +23,7 @@ #include "rf_path.h" -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) +#if !defined(JAWBREAKER) #include #endif @@ -31,10 +31,8 @@ #include "max283x.h" #include "max5864.h" #include "mixer.h" -#if defined(HACKRF_ONE) || defined(PRALINE) - #include "platform_detect.h" -#endif -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) +#include "platform_detect.h" +#if !defined(JAWBREAKER) #include "platform_scu.h" #endif @@ -94,17 +92,18 @@ #define SWITCHCTRL_ANT_PWR (1 << 6) /* turn on antenna port power */ -#ifdef HACKRF_ONE - +#if defined(HACKRF_ONE) || defined(UNIVERSAL) static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) { + board_id_t board_id = detected_platform(); + if (ctrl & SWITCHCTRL_TX) { - if (detected_platform() != BOARD_ID_HACKRF1_R9) { + if (board_id != BOARD_ID_HACKRF1_R9) { gpio_set(rf_path->gpio_tx); } gpio_clear(rf_path->gpio_rx); } else { - if (detected_platform() != BOARD_ID_HACKRF1_R9) { + if (board_id != BOARD_ID_HACKRF1_R9) { gpio_clear(rf_path->gpio_tx); } gpio_set(rf_path->gpio_rx); @@ -167,7 +166,7 @@ static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) gpio_set(rf_path->gpio_no_rx_amp_pwr); } - if (detected_platform() == BOARD_ID_HACKRF1_R9) { + if (board_id == BOARD_ID_HACKRF1_R9) { if (ctrl & SWITCHCTRL_ANT_PWR) { gpio_clear(rf_path->gpio_h1r9_no_ant_pwr); } else { @@ -187,7 +186,7 @@ static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) } #endif -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl) { if (ctrl & SWITCHCTRL_TX) { @@ -228,7 +227,7 @@ static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl) } #endif -#ifdef RAD1O +#if defined(RAD1O) static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) { if (ctrl & SWITCHCTRL_TX) { @@ -295,169 +294,220 @@ static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) { -#ifdef JAWBREAKER - (void) rf_path; /* silence unused param warning */ - mixer_set_gpo(&mixer, gpo); -#elif HACKRF_ONE - switchctrl_set_hackrf_one(rf_path, gpo); -#elif PRALINE - switchctrl_set_praline(rf_path, gpo); -#elif RAD1O - switchctrl_set_rad1o(rf_path, gpo); -#else - (void) gpo; +#if defined(JAWBREAKER) + (void) rf_path; #endif + + switch (detected_platform()) { + case BOARD_ID_JAWBREAKER: +#if defined(JAWBREAKER) + mixer_set_gpo(&mixer, gpo); +#endif + break; + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + switchctrl_set_hackrf_one(rf_path, gpo); +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + switchctrl_set_praline(rf_path, gpo); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + switchctrl_set_rad1o(rf_path, gpo); +#endif + break; + default: + break; + } } void rf_path_pin_setup(rf_path_t* const rf_path) { +#if defined(JAWBREAKER) + (void) rf_path; +#endif + + board_id_t board_id = detected_platform(); #if !defined(JAWBREAKER) const platform_scu_t* scu = platform_scu(); #endif -#ifdef HACKRF_ONE - /* Configure RF switch control signals */ - // clang-format off - scu_pinmux(scu->HP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->LP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->TX_MIX_BP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->NO_MIX_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->RX_MIX_BP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->TX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->MIX_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu_pinmux(scu->NO_TX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->AMP_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->RX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->NO_RX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - // clang-format on - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - scu_pinmux(scu->H1R9_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->H1R9_NO_ANT_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - gpio_clear(rf_path->gpio_h1r9_no_ant_pwr); - gpio_output(rf_path->gpio_h1r9_no_ant_pwr); - scu_pinmux(scu->H1R9_NO_VAA_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } else { - scu_pinmux(scu->TX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu_pinmux(scu->RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - gpio_output(rf_path->gpio_tx); - scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + /* Configure RF switch control signals */ + // clang-format off + scu_pinmux(scu->HP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->LP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->TX_MIX_BP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->NO_MIX_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RX_MIX_BP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->TX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->MIX_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu_pinmux(scu->NO_TX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->AMP_BYPASS, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->NO_RX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + // clang-format on + if (board_id == BOARD_ID_HACKRF1_R9) { + scu_pinmux(scu->H1R9_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux( + scu->H1R9_NO_ANT_PWR, + SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + gpio_clear(rf_path->gpio_h1r9_no_ant_pwr); + gpio_output(rf_path->gpio_h1r9_no_ant_pwr); + scu_pinmux( + scu->H1R9_NO_VAA_EN, + SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + } else { + scu_pinmux(scu->TX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu_pinmux(scu->RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + gpio_output(rf_path->gpio_tx); + scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + } - /* - * Safe (initial) switch settings turn off both amplifiers and antenna port - * power and enable both amp bypass and mixer bypass. - */ - switchctrl_set(rf_path, SWITCHCTRL_SAFE); + /* + * Safe (initial) switch settings turn off both amplifiers and antenna port + * power and enable both amp bypass and mixer bypass. + */ + switchctrl_set(rf_path, SWITCHCTRL_SAFE); - /* Configure RF switch control signals as outputs */ - gpio_output(rf_path->gpio_amp_bypass); - gpio_output(rf_path->gpio_no_mix_bypass); - gpio_output(rf_path->gpio_rx_amp); - gpio_output(rf_path->gpio_no_rx_amp_pwr); - gpio_output(rf_path->gpio_hp); - gpio_output(rf_path->gpio_lp); - gpio_output(rf_path->gpio_tx_mix_bp); - gpio_output(rf_path->gpio_rx_mix_bp); - gpio_output(rf_path->gpio_tx_amp); - gpio_output(rf_path->gpio_no_tx_amp_pwr); - gpio_output(rf_path->gpio_mix_bypass); - gpio_output(rf_path->gpio_rx); -#elif RAD1O - /* Configure RF switch control signals */ - // clang-format off - scu_pinmux(scu->BY_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->BY_AMP_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu_pinmux(scu->TX_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->TX_RX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->BY_MIX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->BY_MIX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->LOW_HIGH_FILT, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->LOW_HIGH_FILT_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->TX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->RX_LNA, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - scu_pinmux(scu->MIXER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - // clang-format on - - /* Configure RF power supply (VAA) switch */ - scu_pinmux(scu->VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - - /* - * Safe (initial) switch settings turn off both amplifiers and antenna port - * power and enable both amp bypass and mixer bypass. - */ - switchctrl_set(rf_path, SWITCHCTRL_SAFE); - - /* Configure RF switch control signals as outputs */ - gpio_output(rf_path->gpio_tx_rx_n); - gpio_output(rf_path->gpio_tx_rx); - gpio_output(rf_path->gpio_by_mix); - gpio_output(rf_path->gpio_by_mix_n); - gpio_output(rf_path->gpio_by_amp); - gpio_output(rf_path->gpio_by_amp_n); - gpio_output(rf_path->gpio_mixer_en); - gpio_output(rf_path->gpio_low_high_filt); - gpio_output(rf_path->gpio_low_high_filt_n); - gpio_output(rf_path->gpio_tx_amp); - gpio_output(rf_path->gpio_rx_lna); -#elif PRALINE - /* Configure RF switch control signals */ - scu_pinmux(scu->TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - board_rev_t rev = detected_revision(); - if ((rev == BOARD_REV_PRALINE_R1_0) || (rev == BOARD_REV_GSG_PRALINE_R1_0)) { - scu_pinmux(scu->MIX_EN_N_R1_0, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - } else { - scu_pinmux(scu->MIX_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } - scu_pinmux(scu->LPF_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->RF_AMP_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - - /* Configure antenna port power control signal */ - scu_pinmux(scu->ANT_BIAS_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - - /* Configure RF power supply (VAA) switch */ - scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - - /* - * Safe (initial) switch settings turn off both amplifiers and antenna port - * power and enable both amp bypass and mixer bypass. - */ - switchctrl_set(rf_path, SWITCHCTRL_SAFE); - - /* Configure RF switch control signals as outputs */ - gpio_output(rf_path->gpio_ant_bias_en_n); - gpio_output(rf_path->gpio_tx_en); - gpio_output(rf_path->gpio_mix_en_n); - gpio_output(rf_path->gpio_lpf_en); - gpio_output(rf_path->gpio_rf_amp_en); -#else - (void) rf_path; /* silence unused param warning */ + /* Configure RF switch control signals as outputs */ + gpio_output(rf_path->gpio_amp_bypass); + gpio_output(rf_path->gpio_no_mix_bypass); + gpio_output(rf_path->gpio_rx_amp); + gpio_output(rf_path->gpio_no_rx_amp_pwr); + gpio_output(rf_path->gpio_hp); + gpio_output(rf_path->gpio_lp); + gpio_output(rf_path->gpio_tx_mix_bp); + gpio_output(rf_path->gpio_rx_mix_bp); + gpio_output(rf_path->gpio_tx_amp); + gpio_output(rf_path->gpio_no_tx_amp_pwr); + gpio_output(rf_path->gpio_mix_bypass); + gpio_output(rf_path->gpio_rx); #endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + /* Configure RF switch control signals */ + // clang-format off + scu_pinmux(scu->BY_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->BY_AMP_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu_pinmux(scu->TX_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->TX_RX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->BY_MIX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->BY_MIX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->LOW_HIGH_FILT, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->LOW_HIGH_FILT_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->TX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RX_LNA, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu_pinmux(scu->MIXER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + // clang-format on + + /* Configure RF power supply (VAA) switch */ + scu_pinmux(scu->VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + + /* + * Safe (initial) switch settings turn off both amplifiers and antenna port + * power and enable both amp bypass and mixer bypass. + */ + switchctrl_set(rf_path, SWITCHCTRL_SAFE); + + /* Configure RF switch control signals as outputs */ + gpio_output(rf_path->gpio_tx_rx_n); + gpio_output(rf_path->gpio_tx_rx); + gpio_output(rf_path->gpio_by_mix); + gpio_output(rf_path->gpio_by_mix_n); + gpio_output(rf_path->gpio_by_amp); + gpio_output(rf_path->gpio_by_amp_n); + gpio_output(rf_path->gpio_mixer_en); + gpio_output(rf_path->gpio_low_high_filt); + gpio_output(rf_path->gpio_low_high_filt_n); + gpio_output(rf_path->gpio_tx_amp); + gpio_output(rf_path->gpio_rx_lna); +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + /* Configure RF switch control signals */ + scu_pinmux(scu->TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + board_rev_t rev = detected_revision(); + if ((rev == BOARD_REV_PRALINE_R1_0) || + (rev == BOARD_REV_GSG_PRALINE_R1_0)) { + scu_pinmux(scu->MIX_EN_N_R1_0, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + } else { + scu_pinmux(scu->MIX_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + } + scu_pinmux(scu->LPF_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RF_AMP_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + + /* Configure antenna port power control signal */ + scu_pinmux(scu->ANT_BIAS_EN_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + + /* Configure RF power supply (VAA) switch */ + scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + + /* + * Safe (initial) switch settings turn off both amplifiers and antenna port + * power and enable both amp bypass and mixer bypass. + */ + switchctrl_set(rf_path, SWITCHCTRL_SAFE); + + /* Configure RF switch control signals as outputs */ + gpio_output(rf_path->gpio_ant_bias_en_n); + gpio_output(rf_path->gpio_tx_en); + gpio_output(rf_path->gpio_mix_en_n); + gpio_output(rf_path->gpio_lpf_en); + gpio_output(rf_path->gpio_rf_amp_en); +#endif + break; + default: + break; + } } void rf_path_init(rf_path_t* const rf_path) { + board_id_t board_id = detected_platform(); + ssp1_set_mode_max5864(); max5864_setup(&max5864); max5864_shutdown(&max5864); ssp1_set_mode_max283x(); -#ifdef PRALINE - max283x_setup(&max283x, MAX2831_VARIANT); -#elif defined(HACKRF_ONE) - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - max283x_setup(&max283x, MAX2839_VARIANT); - } else { - max283x_setup(&max283x, MAX2837_VARIANT); - } -#else - max283x_setup(&max283x, MAX2837_VARIANT); + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + max283x_setup(&max283x, MAX2831_VARIANT); #endif + break; + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + max283x_setup(&max283x, MAX2839_VARIANT); +#endif + break; + default: +#if !defined(PRALINE) || defined(UNIVERSAL) + max283x_setup(&max283x, MAX2837_VARIANT); +#endif + break; + } max283x_start(&max283x); - // On HackRF One, the mixer is now set up earlier in boot. -#ifndef HACKRF_ONE - mixer_setup(&mixer); -#endif + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + // On HackRF One, the mixer is now set up earlier in boot. + break; + default: + mixer_setup(&mixer); + break; + } rf_path->switchctrl = SWITCHCTRL_SAFE; } @@ -501,7 +551,7 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d max283x_rx(&max283x); break; -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) case RF_PATH_DIRECTION_TX_CALIBRATION: case RF_PATH_DIRECTION_RX_CALIBRATION: rf_path->switchctrl &= ~SWITCHCTRL_TX; diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index 1ac0e243..64634b3d 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -25,7 +25,7 @@ #include -#if defined(HACKRF_ONE) || defined(RAD1O) || defined(PRALINE) +#if !defined(JAWBREAKER) #include "gpio.h" #endif @@ -33,7 +33,7 @@ typedef enum { RF_PATH_DIRECTION_OFF, RF_PATH_DIRECTION_RX, RF_PATH_DIRECTION_TX, -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) RF_PATH_DIRECTION_TX_CALIBRATION, RF_PATH_DIRECTION_RX_CALIBRATION, #endif @@ -47,43 +47,48 @@ typedef enum { typedef struct { uint8_t switchctrl; -#ifdef HACKRF_ONE - gpio_t gpio_hp; - gpio_t gpio_lp; - gpio_t gpio_tx_mix_bp; - gpio_t gpio_no_mix_bypass; - gpio_t gpio_rx_mix_bp; - gpio_t gpio_tx_amp; - gpio_t gpio_tx; - gpio_t gpio_mix_bypass; - gpio_t gpio_rx; - gpio_t gpio_no_tx_amp_pwr; - gpio_t gpio_amp_bypass; - gpio_t gpio_rx_amp; - gpio_t gpio_no_rx_amp_pwr; - // In HackRF One r9 this control signal has been moved to the microcontroller. - gpio_t gpio_h1r9_no_ant_pwr; + + struct { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio_t gpio_hp; + gpio_t gpio_lp; + gpio_t gpio_tx_mix_bp; + gpio_t gpio_no_mix_bypass; + gpio_t gpio_rx_mix_bp; + gpio_t gpio_tx_amp; + gpio_t gpio_tx; + gpio_t gpio_mix_bypass; + gpio_t gpio_rx; + gpio_t gpio_no_tx_amp_pwr; + gpio_t gpio_amp_bypass; + gpio_t gpio_rx_amp; + gpio_t gpio_no_rx_amp_pwr; + // In HackRF One r9 this control signal has been moved to the microcontroller. + gpio_t gpio_h1r9_no_ant_pwr; #endif -#ifdef RAD1O - gpio_t gpio_tx_rx_n; - gpio_t gpio_tx_rx; - gpio_t gpio_by_mix; - gpio_t gpio_by_mix_n; - gpio_t gpio_by_amp; - gpio_t gpio_by_amp_n; - gpio_t gpio_mixer_en; - gpio_t gpio_low_high_filt; - gpio_t gpio_low_high_filt_n; - gpio_t gpio_tx_amp; - gpio_t gpio_rx_lna; + +#if defined(RAD1O) + gpio_t gpio_tx_rx_n; + gpio_t gpio_tx_rx; + gpio_t gpio_by_mix; + gpio_t gpio_by_mix_n; + gpio_t gpio_by_amp; + gpio_t gpio_by_amp_n; + gpio_t gpio_mixer_en; + gpio_t gpio_low_high_filt; + gpio_t gpio_low_high_filt_n; + gpio_t gpio_tx_amp; + gpio_t gpio_rx_lna; #endif -#ifdef PRALINE - gpio_t gpio_tx_en; - gpio_t gpio_mix_en_n; - gpio_t gpio_lpf_en; - gpio_t gpio_rf_amp_en; - gpio_t gpio_ant_bias_en_n; + +#if defined(PRALINE) || defined(UNIVERSAL) + gpio_t gpio_tx_en; + gpio_t gpio_mix_en_n; + gpio_t gpio_lpf_en; + gpio_t gpio_rf_amp_en; + gpio_t gpio_ant_bias_en_n; #endif + }; } rf_path_t; void rf_path_pin_setup(rf_path_t* const rf_path); From 3aaa7185bd49d27a918fc7f810d5e5a1572245fe Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:32:50 +0100 Subject: [PATCH 038/161] Universalize: mixer.{c,h}, usb_api_register.c Co-authored-by: Antoine van Gelder --- firmware/common/mixer.c | 157 +++++++++++++++++-------- firmware/common/mixer.h | 27 ++++- firmware/common/rf_path.c | 9 +- firmware/hackrf_usb/hackrf_usb.c | 8 +- firmware/hackrf_usb/usb_api_register.c | 52 ++++++-- 5 files changed, 185 insertions(+), 68 deletions(-) diff --git a/firmware/common/mixer.c b/firmware/common/mixer.c index 8623ee6b..9e1afb32 100644 --- a/firmware/common/mixer.c +++ b/firmware/common/mixer.c @@ -25,101 +25,164 @@ #include "fixed_point.h" #include "platform_gpio.h" -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) +#if defined(RAD1O) + #include "max2871.h" +#else + #include "platform_detect.h" #include "rffc5071.h" #include "rffc5071_spi.h" #include "spi_bus.h" -#elif defined(RAD1O) - #include "max2871.h" #endif -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) +#if !defined(RAD1O) static rffc5071_spi_config_t rffc5071_spi_config; -spi_bus_t spi_bus_rffc5071 = { +static spi_bus_t spi_bus_rffc5071 = { .config = &rffc5071_spi_config, .start = rffc5071_spi_start, .stop = rffc5071_spi_stop, .transfer = rffc5071_spi_transfer, .transfer_gather = rffc5071_spi_transfer_gather, }; +#endif mixer_driver_t mixer = { - .bus = &spi_bus_rffc5071, -}; -#elif defined(RAD1O) -mixer_driver_t mixer = {}; +#if !defined(RAD1O) + .rffc5071.bus = &spi_bus_rffc5071, #endif +}; void mixer_bus_setup(mixer_driver_t* const mixer) { +#if defined(RAD1O) (void) mixer; - -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) +#else const platform_gpio_t* gpio = platform_gpio(); - rffc5071_spi_config = (rffc5071_spi_config_t){ - .gpio_select = gpio->rffc5072_select, - .gpio_clock = gpio->rffc5072_clock, - .gpio_data = gpio->rffc5072_data, - }; - spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config); #endif + + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_spi_config = (rffc5071_spi_config_t){ + .gpio_select = gpio->rffc5072_select, + .gpio_clock = gpio->rffc5072_clock, + .gpio_data = gpio->rffc5072_data, + }; + spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config); +#endif + break; + case MAX2871_VARIANT: + break; + } } -void mixer_setup(mixer_driver_t* const mixer) +void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) { + mixer->type = type; + const platform_gpio_t* gpio = platform_gpio(); -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - mixer->gpio_reset = gpio->rffc5072_reset; - #if defined(PRALINE) - mixer->gpio_ld = gpio->rffc5072_ld; + /* Mixer GPIO serial interface PinMux */ + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + mixer->rffc5071.gpio_reset = gpio->rffc5072_reset; + if (detected_platform() == BOARD_ID_PRALINE) { + #if defined(PRALINE) || defined(UNIVERSAL) + mixer->rffc5071.gpio_ld = gpio->rffc5072_ld; #endif - rffc5071_setup(mixer); -#elif defined(RAD1O) - mixer->gpio_vco_ce = gpio->vco_ce; - mixer->gpio_vco_sclk = gpio->vco_sclk; - mixer->gpio_vco_sdata = gpio->vco_sdata; - mixer->gpio_vco_le = gpio->vco_le; - mixer->gpio_synt_rfout_en = gpio->synt_rfout_en; - mixer->gpio_vco_mux = gpio->vco_mux; - max2871_setup(mixer); + } #endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + mixer->max2871.gpio_vco_ce = gpio->vco_ce; + mixer->max2871.gpio_vco_sclk = gpio->vco_sclk; + mixer->max2871.gpio_vco_sdata = gpio->vco_sdata; + mixer->max2871.gpio_vco_le = gpio->vco_le; + mixer->max2871.gpio_synt_rfout_en = gpio->synt_rfout_en; + mixer->max2871.gpio_vco_mux = gpio->vco_mux; +#endif + break; + } + + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_setup(&mixer->rffc5071); +#endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + max2871_setup(&mixer->max2871); +#endif + break; + } } fp_40_24_t mixer_set_frequency(mixer_driver_t* const mixer, fp_40_24_t lo, bool program) { -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - return rffc5071_set_frequency(mixer, lo, program); -#elif defined(RAD1O) - return max2871_set_frequency(mixer, lo, program); + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + return rffc5071_set_frequency(&mixer->rffc5071, lo, program); #endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + return max2871_set_frequency(&mixer->max2871, lo, program); +#endif + break; + } + + return 0; } void mixer_enable(mixer_driver_t* const mixer) { -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - rffc5071_enable(mixer); -#elif defined(RAD1O) - max2871_enable(mixer); + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_enable(&mixer->rffc5071); #endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + max2871_enable(&mixer->max2871); +#endif + break; + } } void mixer_disable(mixer_driver_t* const mixer) { -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - rffc5071_disable(mixer); -#elif defined(RAD1O) - max2871_disable(mixer); + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_disable(&mixer->rffc5071); #endif + break; + case MAX2871_VARIANT: +#if defined(RAD1O) + max2871_disable(&mixer->max2871); +#endif + break; + } } void mixer_set_gpo(mixer_driver_t* const mixer, uint8_t gpo) { -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - rffc5071_set_gpo(mixer, gpo); -#elif defined(RAD1O) - (void) mixer; +#if defined(RAD1O) (void) gpo; #endif + + switch (mixer->type) { + case RFFC5071_VARIANT: +#if !defined(RAD1O) + rffc5071_set_gpo(&mixer->rffc5071, gpo); +#endif + break; + case MAX2871_VARIANT: + break; + } } diff --git a/firmware/common/mixer.h b/firmware/common/mixer.h index 6ba47d75..0afacb6b 100644 --- a/firmware/common/mixer.h +++ b/firmware/common/mixer.h @@ -26,17 +26,32 @@ #include "fixed_point.h" -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) - #include "rffc5071.h" -typedef rffc5071_driver_t mixer_driver_t; -#elif defined(RAD1O) +#if defined(RAD1O) #include "max2871.h" -typedef max2871_driver_t mixer_driver_t; +#else + #include "rffc5071.h" #endif +typedef enum { + RFFC5071_VARIANT, + MAX2871_VARIANT, +} mixer_variant_t; + +typedef struct { + mixer_variant_t type; + + union { +#if defined(RAD1O) + max2871_driver_t max2871; +#else + rffc5071_driver_t rffc5071; +#endif + }; +} mixer_driver_t; + #include extern void mixer_bus_setup(mixer_driver_t* const mixer); -extern void mixer_setup(mixer_driver_t* const mixer); +extern void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type); /* Set frequency (Hz). */ extern fp_40_24_t mixer_set_frequency( diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index a361f442..5e892668 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -504,8 +504,15 @@ void rf_path_init(rf_path_t* const rf_path) case BOARD_ID_HACKRF1_R9: // On HackRF One, the mixer is now set up earlier in boot. break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + mixer_setup(&mixer, MAX2871_VARIANT); +#endif + break; default: - mixer_setup(&mixer); +#if !defined(RAD1O) + mixer_setup(&mixer, RFFC5071_VARIANT); +#endif break; } rf_path->switchctrl = SWITCHCTRL_SAFE; diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index c365e9bc..93308cda 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -47,7 +47,7 @@ #include #include #include -#if defined(HACKRF_ONE) +#if !defined(RAD1O) #include #endif #if defined(PRALINE) || defined(HACKRF_ONE) @@ -432,7 +432,9 @@ int main(void) // This will be cleared if any self-test check fails. selftest.report.pass = true; + // Detect hardware platform before we do anything else. detect_hardware_platform(); + pin_shutdown(); #ifndef RAD1O clock_gen_shutdown(); @@ -464,7 +466,7 @@ int main(void) #ifdef HACKRF_ONE // Set up mixer before enabling RF power, because its // GPO is used to control the antenna bias tee. - mixer_setup(&mixer); + mixer_setup(&mixer, RFFC5071_VARIANT); #endif #if (defined HACKRF_ONE || defined RAD1O) enable_rf_power(); @@ -526,7 +528,7 @@ int main(void) rf_path_init(&rf_path); #ifndef RAD1O - rffc5071_lock_test(&mixer); + rffc5071_lock_test(&mixer.rffc5071); #endif #ifdef PRALINE diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index 01d9fa8f..b74589ec 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -30,17 +30,19 @@ #include #include +#include #include #include #include #include #include -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(JAWBREAKER) - #include -#endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include #endif +#if !defined(RAD1O) + #include + #include +#endif usb_request_status_t usb_vendor_request_write_max283x( usb_endpoint_t* const endpoint, @@ -136,20 +138,29 @@ usb_request_status_t usb_vendor_request_read_si5351c( } } -#ifndef RAD1O usb_request_status_t usb_vendor_request_write_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { +#if defined(RAD1O) + (void) endpoint; + (void) stage; + if (detected_platform() == BOARD_ID_RAD1O) { + return USB_REQUEST_STATUS_STALL; + } +#endif + if (stage == USB_TRANSFER_STAGE_SETUP) { +#if !defined(RAD1O) if (endpoint->setup.index < RFFC5071_NUM_REGS) { rffc5071_reg_write( - &mixer, + &mixer.rffc5071, endpoint->setup.index, endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); return USB_REQUEST_STATUS_OK; } +#endif return USB_REQUEST_STATUS_STALL; } else { return USB_REQUEST_STATUS_OK; @@ -160,10 +171,19 @@ usb_request_status_t usb_vendor_request_read_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - uint16_t value; +#if defined(RAD1O) + (void) endpoint; + (void) stage; + if (detected_platform() == BOARD_ID_RAD1O) { + return USB_REQUEST_STATUS_STALL; + } +#endif + if (stage == USB_TRANSFER_STAGE_SETUP) { +#if !defined(RAD1O) + uint16_t value; if (endpoint->setup.index < RFFC5071_NUM_REGS) { - value = rffc5071_reg_read(&mixer, endpoint->setup.index); + value = rffc5071_reg_read(&mixer.rffc5071, endpoint->setup.index); endpoint->buffer[0] = value & 0xff; endpoint->buffer[1] = value >> 8; usb_transfer_schedule_block( @@ -175,12 +195,12 @@ usb_request_status_t usb_vendor_request_read_rffc5071( usb_transfer_schedule_ack(endpoint->out); return USB_REQUEST_STATUS_OK; } +#endif return USB_REQUEST_STATUS_STALL; } else { return USB_REQUEST_STATUS_OK; } } -#endif usb_request_status_t usb_vendor_request_set_clkout_enable( usb_endpoint_t* const endpoint, @@ -266,13 +286,18 @@ usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( return USB_REQUEST_STATUS_OK; } -#ifdef PRALINE usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { +#if defined(PRALINE) || defined(UNIVERSAL) fpga_reg_write(&fpga, endpoint->setup.index, endpoint->setup.value); +#endif usb_transfer_schedule_ack(endpoint->in); return USB_REQUEST_STATUS_OK; } @@ -283,7 +308,12 @@ usb_request_status_t usb_vendor_request_read_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { +#if defined(PRALINE) || defined(UNIVERSAL) const uint8_t value = fpga_reg_read(&fpga, endpoint->setup.index); endpoint->buffer[0] = value; usb_transfer_schedule_block( @@ -292,11 +322,11 @@ usb_request_status_t usb_vendor_request_read_fpga_reg( 1, NULL, NULL); +#endif usb_transfer_schedule_ack(endpoint->out); } return USB_REQUEST_STATUS_OK; } -#endif /* * Each register is transferred as a uint8_t register number followed by a From 275b75fc8558cb0ab7e72f2c47e714ab9e44d920 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:51:41 +0100 Subject: [PATCH 039/161] Universalize: tuning.{c,h}, tune_config.h Co-authored-by: Antoine van Gelder --- firmware/common/tune_config.h | 2 +- firmware/common/tuning.c | 37 ++++++++++++++++++-------------- firmware/common/tuning.h | 2 ++ firmware/hackrf_usb/hackrf_usb.c | 4 +++- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/firmware/common/tune_config.h b/firmware/common/tune_config.h index 7fc76af9..34c692e6 100644 --- a/firmware/common/tune_config.h +++ b/firmware/common/tune_config.h @@ -21,7 +21,7 @@ #pragma once -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) #include "fpga.h" typedef struct { diff --git a/firmware/common/tuning.c b/firmware/common/tuning.c index f613ab30..9c863beb 100644 --- a/firmware/common/tuning.c +++ b/firmware/common/tuning.c @@ -29,18 +29,23 @@ #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) -#ifdef PRALINE - #define MIN_BYPASS_FREQ FP_MHZ(2320) - #define MAX_BYPASS_FREQ FP_MHZ(2580) -#else - #define MIN_BYPASS_FREQ FP_MHZ(2170) - #define MAX_BYPASS_FREQ FP_MHZ(2740) -#endif - #define MID1_HP_FREQ FP_MHZ(3600) #define MID2_HP_FREQ FP_MHZ(5100) #define MAX_HP_FREQ FP_MHZ(7250) +static fp_40_24_t min_bypass_freq = FP_MHZ(2170); +static fp_40_24_t max_bypass_freq = FP_MHZ(2740); + +void tuning_setup(void) +{ +#if defined(PRALINE) || defined(UNIVERSAL) + if (detected_platform() == BOARD_ID_PRALINE) { + min_bypass_freq = FP_MHZ(2320); + max_bypass_freq = FP_MHZ(2580); + } +#endif +} + fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) { fp_40_24_t freq_if; @@ -57,8 +62,8 @@ fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) case RF_PATH_FILTER_HIGH_PASS: if (freq_rf < MID1_HP_FREQ) { /* IF is graduated from 2170 MHz to 2740 MHz */ - freq_if = MIN_BYPASS_FREQ + - (((freq_rf - (MAX_BYPASS_FREQ)) * 57) / 86); + freq_if = min_bypass_freq + + (((freq_rf - (max_bypass_freq)) * 57) / 86); } else if (freq_rf < MID2_HP_FREQ) { /* IF is graduated from 2350 MHz to 2650 MHz */ freq_if = FP_MHZ(2350) + ((freq_rf - (MID1_HP_FREQ)) / 5); @@ -75,9 +80,9 @@ fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) rf_path_filter_t select_img_reject(fp_40_24_t freq_rf) { - if (freq_rf > MAX_BYPASS_FREQ) { + if (freq_rf > max_bypass_freq) { return RF_PATH_FILTER_HIGH_PASS; - } else if (freq_rf >= MIN_BYPASS_FREQ) { + } else if (freq_rf >= min_bypass_freq) { return RF_PATH_FILTER_BYPASS; } else { return RF_PATH_FILTER_LOW_PASS; @@ -91,14 +96,14 @@ fp_40_24_t restrict_rf(fp_40_24_t freq_rf, rf_path_filter_t img_reject) switch (img_reject) { case RF_PATH_FILTER_LOW_PASS: - max_rf = MIN_BYPASS_FREQ; + max_rf = min_bypass_freq; break; case RF_PATH_FILTER_HIGH_PASS: - min_rf = MAX_BYPASS_FREQ; + min_rf = max_bypass_freq; break; default: - min_rf = MIN_BYPASS_FREQ; - max_rf = MAX_BYPASS_FREQ; + min_rf = min_bypass_freq; + max_rf = max_bypass_freq; } freq_rf = MIN(freq_rf, max_rf); freq_rf = MAX(freq_rf, min_rf); diff --git a/firmware/common/tuning.h b/firmware/common/tuning.h index a338cd28..db123ee3 100644 --- a/firmware/common/tuning.h +++ b/firmware/common/tuning.h @@ -26,6 +26,8 @@ #include "fixed_point.h" #include "rf_path.h" +void tuning_setup(void); + /** * Select fixed or graduated IF for a given RF and image reject filter. */ diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 93308cda..3238ca0e 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -40,8 +40,9 @@ #include #include #include -#include #include +#include +#include #include #include #include @@ -463,6 +464,7 @@ int main(void) clock_gen_init(); #endif #endif + tuning_setup(); #ifdef HACKRF_ONE // Set up mixer before enabling RF power, because its // GPO is used to control the antenna bias tee. From 233951c9c9fae3dd36af8e62edd44dc35997764c Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:53:20 +0100 Subject: [PATCH 040/161] Universalize: hackrf_ui.c Co-authored-by: Antoine van Gelder --- firmware/common/hackrf_ui.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index 6e230943..ac16a444 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -23,8 +23,9 @@ #include #include "hackrf_ui.h" +#include "platform_detect.h" #include "transceiver_mode.h" -#if defined(PRALINE) || defined(HACKRF_ONE) +#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) #include "ui_portapack.h" #endif #if defined(RAD1O) @@ -82,16 +83,26 @@ const hackrf_ui_t* hackrf_ui(void) { /* Detect on first use. If no UI hardware is detected, use a stub function table. */ if (ui == NULL && ui_enabled) { -#if (defined HACKRF_ONE || defined PRALINE) - if (portapack_hackrf_ui_init) { - ui = portapack_hackrf_ui_init(); - } + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + if (portapack_hackrf_ui_init) { + ui = portapack_hackrf_ui_init(); + } #endif -#ifdef RAD1O - if (rad1o_ui_setup) { - ui = rad1o_ui_setup(); - } + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + if (rad1o_ui_setup) { + ui = rad1o_ui_setup(); + } #endif + break; + default: + break; + } } if (ui == NULL) { From 9285573f0bbdec44e586daf14be92c68f7ac72cd Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:54:12 +0100 Subject: [PATCH 041/161] Universalize: operacake_sctimer.c Co-authored-by: Antoine van Gelder --- firmware/common/operacake_sctimer.c | 40 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 9ae37567..21ce3d7e 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -30,6 +30,8 @@ #include #endif +#include + #include "delay.h" #include "sct.h" @@ -95,25 +97,31 @@ void operacake_sctimer_init(void) P7_0, SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); -#ifndef PRALINE - // Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12 - SGPIO_OUT_MUX_CFG12 = SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode - SGPIO_OUT_MUX_CFG_P_OE_CFG(0); // gpio_oe - SGPIO_GPIO_OENREG |= BIT12; + uint8_t sct_clock_input; + if (detected_platform() != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + // Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12 + SGPIO_OUT_MUX_CFG12 = + SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode + SGPIO_OUT_MUX_CFG_P_OE_CFG(0); // gpio_oe + SGPIO_GPIO_OENREG |= BIT12; - // Use the GIMA to connect the SGPIO clock to the SCTimer - GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1 + // Use the GIMA to connect the SGPIO clock to the SCTimer + GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1 - uint8_t sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1; -#else - // Configure pin P6_4 as SCT_IN_6 - scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); - - // Use the GIMA to connect MS0/CLK1 (SCT_CLK) on pin P6_4 to the SCTimer - GIMA_CTIN_6_IN = 0x0 << 4; - - uint8_t sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6; + sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1; #endif + } else { +#if defined(PRALINE) || defined(UNIVERSAL) + // Configure pin P6_4 as SCT_IN_6 + scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); + + // Use the GIMA to connect MS0/CLK1 (SCT_CLK) on pin P6_4 to the SCTimer + GIMA_CTIN_6_IN = 0x0 << 4; + + sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6; +#endif + } // We configure this register first, because the user manual says to SCT_CONFIG |= SCT_CONFIG_UNIFY_32_BIT | SCT_CONFIG_CLKMODE_PRESCALED_BUS_CLOCK | From ed1a4d0e9e0938cddb83636f5a079c9a4ca30b9b Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:55:40 +0100 Subject: [PATCH 042/161] Universalize: w25q80bv.c Co-authored-by: Antoine van Gelder --- firmware/common/w25q80bv.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/firmware/common/w25q80bv.c b/firmware/common/w25q80bv.c index 9e7e1acd..f569bedf 100644 --- a/firmware/common/w25q80bv.c +++ b/firmware/common/w25q80bv.c @@ -27,10 +27,11 @@ * programming the flash. */ -#include "w25q80bv.h" - -#include #include +#include + +#include "platform_detect.h" +#include "w25q80bv.h" #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -59,13 +60,13 @@ void w25q80bv_setup(w25q80bv_driver_t* const drv) uint8_t device_id; drv->page_len = 256U; -#ifdef PRALINE - drv->num_pages = 16384U; - drv->num_bytes = 4194304U; -#else - drv->num_pages = 4096U; - drv->num_bytes = 1048576U; -#endif + if (detected_platform() == BOARD_ID_PRALINE) { + drv->num_pages = 16384U; + drv->num_bytes = 4194304U; + } else { + drv->num_pages = 4096U; + drv->num_bytes = 1048576U; + } drv->target_init(drv); From 54dfdd9174f2bf90ad2179b3b74e611986373961 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:56:31 +0100 Subject: [PATCH 043/161] Universalize: sgpio.{c,h} Co-authored-by: Antoine van Gelder --- firmware/common/sgpio.c | 14 ++++++++------ firmware/common/sgpio.h | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index f364d7b0..ac755d51 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -58,7 +58,7 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config) scu_pinmux(scu->PINMUX_SGPIO15, scu->PINMUX_SGPIO15_PINCFG); /* GPIO5[14] */ if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) scu_pinmux( scu->H1R9_TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[5] */ @@ -72,10 +72,12 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config) sgpio_cpld_set_mixer_invert(config, 0); gpio_output(config->gpio_q_invert); -#ifndef PRALINE - trigger_enable(false); - gpio_output(config->gpio_trigger_enable); + if (detected_platform() != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + trigger_enable(false); + gpio_output(config->gpio_trigger_enable); #endif + } } void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice) @@ -157,11 +159,11 @@ void sgpio_configure(sgpio_config_t* const config, const sgpio_direction_t direc SGPIO_OUT_MUX_CFG_P_OE_CFG(0) // gpio_oe (state set by GPIO_OEREG) | SGPIO_OUT_MUX_CFG_P_OUT_CFG(0) // dout_doutm1 (1-bit mode) ; - SGPIO_OUT_MUX_CFG(10) = // GPIO10: Output: disable + SGPIO_OUT_MUX_CFG(10) = // GPIO10: Output: disable SGPIO_OUT_MUX_CFG_P_OE_CFG(0) // gpio_oe (state set by GPIO_OEREG) | SGPIO_OUT_MUX_CFG_P_OUT_CFG(4) // gpio_out (level set by GPIO_OUTREG) ; - SGPIO_OUT_MUX_CFG(11) = // GPIO11: Output: direction + SGPIO_OUT_MUX_CFG(11) = // GPIO11: Output: direction SGPIO_OUT_MUX_CFG_P_OE_CFG(0) // gpio_oe (state set by GPIO_OEREG) | SGPIO_OUT_MUX_CFG_P_OUT_CFG(4) // gpio_out (level set by GPIO_OUTREG) ; diff --git a/firmware/common/sgpio.h b/firmware/common/sgpio.h index a3b6b2eb..f6fdf213 100644 --- a/firmware/common/sgpio.h +++ b/firmware/common/sgpio.h @@ -34,7 +34,7 @@ typedef enum { typedef struct { gpio_t gpio_q_invert; -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) gpio_t gpio_trigger_enable; #endif bool slice_mode_multislice; From 6c4023e1791883d51d83a4e91ca8897556e90b3d Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 00:57:58 +0100 Subject: [PATCH 044/161] Universalize: radio.c Co-authored-by: Antoine van Gelder --- firmware/common/radio.c | 184 +++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 88 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index ac150ca1..ff97a814 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -33,7 +33,7 @@ #include "rf_path.h" #include "transceiver_mode.h" #include "tuning.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "fpga.h" #include "tune_config.h" #endif @@ -195,18 +195,22 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) case TRANSCEIVER_MODE_SS: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) { -#ifdef PRALINE - fpga_set_tx_interpolation_ratio(&fpga, n); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_tx_interpolation_ratio(&fpga, n); #endif + } radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX] = n; } break; default: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) { -#ifdef PRALINE - fpga_set_rx_decimation_ratio(&fpga, n); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_rx_decimation_ratio(&fpga, n); #endif + } radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX] = n; } } @@ -290,7 +294,7 @@ static fp_40_24_t digital_from_analog_rf( return drf; } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) /* * Convert center frequency of digital baseband (as seen by MCU) to center * frequency of analog baseband (as seen by ADC/DAC). @@ -418,7 +422,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) /* Look up settings appropriate for requested RF. */ if (detected_platform() == BOARD_ID_PRALINE) { -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) const tune_config_t* tune_config = select_tune_config(opmode, freq_rf); if (requested_rotation == RADIO_UNSET) { @@ -498,9 +502,11 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) changed |= (1 << RADIO_IMAGE_REJECT); } if ((rotation != applied_rotation) && (rotation != RADIO_UNSET)) { -#ifdef PRALINE - fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); #endif + } radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION] = rotation; changed |= (1 << RADIO_ROTATION); } @@ -565,70 +571,74 @@ static uint32_t radio_update_bandwidth(radio_t* const radio, uint64_t* bank) opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE]; } -#ifdef PRALINE - /* Praline legacy mode always sets baseband bandwidth automatically. */ - (void) bank; - uint32_t lpf_bandwidth = auto_bandwidth(radio, opmode); + if (detected_platform() == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + /* Praline legacy mode always sets baseband bandwidth automatically. */ + (void) bank; + uint32_t lpf_bandwidth = auto_bandwidth(radio, opmode); - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; - changed |= (1 << RADIO_XCVR_TX_LPF); - } - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_RX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; - changed |= (1 << RADIO_XCVR_RX_LPF); - } - bool narrow_lpf_enable = false; - bool applied_narrow_lpf_enable = - radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF]; - if (lpf_bandwidth <= 1750000) { - narrow_lpf_enable = true; - } - if (applied_narrow_lpf_enable != narrow_lpf_enable) { - narrowband_filter_set(narrow_lpf_enable); - radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF] = - narrow_lpf_enable; - changed |= (1 << RADIO_RX_NARROW_LPF); - } - /* Always set HPF bandwidth to 30 kHz for now. */ - const max283x_rx_hpf_freq_t hpf_bandwidth = MAX283x_RX_HPF_30_KHZ; - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] != hpf_bandwidth) { - max283x_set_rx_hpf_frequency(&max283x, hpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] = hpf_bandwidth; - changed |= (1 << RADIO_XCVR_RX_HPF); - } -#else - uint64_t lpf_bandwidth; - lpf_bandwidth = bank[RADIO_XCVR_TX_LPF]; - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = bank[RADIO_XCVR_RX_LPF]; - } - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_TX]; - } - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_RX]; - } - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF]; - } - if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = auto_bandwidth(radio, opmode); - } - - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_RX] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_TX] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; - changed |= - ((1 << RADIO_BB_BANDWIDTH_RX) | (1 << RADIO_BB_BANDWIDTH_TX) | - (1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF)); - } + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { + max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; + changed |= (1 << RADIO_XCVR_TX_LPF); + } + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] != lpf_bandwidth) { + max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_RX, lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; + changed |= (1 << RADIO_XCVR_RX_LPF); + } + bool narrow_lpf_enable = false; + bool applied_narrow_lpf_enable = + radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF]; + if (lpf_bandwidth <= 1750000) { + narrow_lpf_enable = true; + } + if (applied_narrow_lpf_enable != narrow_lpf_enable) { + narrowband_filter_set(narrow_lpf_enable); + radio->config[RADIO_BANK_APPLIED][RADIO_RX_NARROW_LPF] = + narrow_lpf_enable; + changed |= (1 << RADIO_RX_NARROW_LPF); + } + /* Always set HPF bandwidth to 30 kHz for now. */ + const max283x_rx_hpf_freq_t hpf_bandwidth = MAX283x_RX_HPF_30_KHZ; + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] != hpf_bandwidth) { + max283x_set_rx_hpf_frequency(&max283x, hpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] = hpf_bandwidth; + changed |= (1 << RADIO_XCVR_RX_HPF); + } #endif + } else { +#if !defined(PRALINE) || defined(UNIVERSAL) + uint64_t lpf_bandwidth; + lpf_bandwidth = bank[RADIO_XCVR_TX_LPF]; + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = bank[RADIO_XCVR_RX_LPF]; + } + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_TX]; + } + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_RX]; + } + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF]; + } + if (lpf_bandwidth == RADIO_UNSET) { + lpf_bandwidth = auto_bandwidth(radio, opmode); + } + + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { + max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_RX] = lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_TX] = lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; + changed |= + ((1 << RADIO_BB_BANDWIDTH_RX) | (1 << RADIO_BB_BANDWIDTH_TX) | + (1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF)); + } +#endif + } return changed; } @@ -764,26 +774,24 @@ static uint32_t radio_update_trigger(radio_t* const radio, uint64_t* bank) static uint32_t radio_update_dc_block(radio_t* const radio, uint64_t* bank) { -#ifndef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) + if (detected_platform() == BOARD_ID_PRALINE) { + const uint64_t requested = bank[RADIO_DC_BLOCK]; + bool enable = requested; + + if (radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] == (uint64_t) enable) { + return 0; + } + + fpga_set_rx_dc_block_enable(&fpga, enable); + radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; + return (1 << RADIO_DC_BLOCK); + } +#else (void) radio; (void) bank; - return 0; -#else - const uint64_t requested = bank[RADIO_DC_BLOCK]; - bool enable = requested; - - if (requested == RADIO_UNSET) { - enable = true; - } - - if (radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] == (uint64_t) enable) { - return 0; - } - - fpga_set_rx_dc_block_enable(&fpga, enable); - radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; - return (1 << RADIO_DC_BLOCK); #endif + return 0; } bool radio_update(radio_t* const radio) From 938c020f3b5b146e322c88402325c831e7c5cb8a Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 01:09:30 +0100 Subject: [PATCH 045/161] Universalize: hackrf_core.{c,h}, selftest.h Co-authored-by: Antoine van Gelder --- firmware/common/hackrf_core.c | 781 +++++++++++++++++++++------------- firmware/common/hackrf_core.h | 18 +- firmware/common/selftest.h | 9 +- 3 files changed, 504 insertions(+), 304 deletions(-) diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 25e3b870..c8b66443 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -25,7 +25,7 @@ #include #include #include -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(PRALINE) +#if !defined(RAD1O) #include #endif @@ -41,10 +41,10 @@ #include "platform_scu.h" #include "spi_bus.h" #include "w25q80bv_target.h" -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) #include "portapack.h" #endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "ice40_spi.h" #endif @@ -124,7 +124,7 @@ ssp_config_t ssp_config_w25q80bv = { .clock_prescale_rate = 2, }; -spi_bus_t spi_bus_ssp0 = { +static spi_bus_t spi_bus_ssp0 = { .obj = (void*) SSP0_BASE, .config = &ssp_config_w25q80bv, .start = spi_ssp_start, @@ -142,8 +142,8 @@ sgpio_config_t sgpio_config = { .slice_mode_multislice = true, }; -#ifdef PRALINE -ssp_config_t ssp_config_ice40_fpga = { +#if defined(PRALINE) || defined(UNIVERSAL) +static ssp_config_t ssp_config_ice40_fpga = { .data_bits = SSP_DATA_8BITS, .spi_mode = SSP_CPOL_1_CPHA_1, .serial_clock_rate = 21, @@ -163,11 +163,9 @@ radio_t radio = { .sample_rate_cb = sample_rate_set, }; -rf_path_t rf_path = { - .switchctrl = 0, -}; +rf_path_t rf_path; -jtag_gpio_t jtag_gpio_cpld = {}; +jtag_gpio_t jtag_gpio_cpld; jtag_t jtag_cpld = { .gpio = &jtag_gpio_cpld, @@ -343,61 +341,77 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) sgpio_cpld_stream_disable(&sgpio_config); } -#ifndef PRALINE - /* Integer mode can be enabled if p1 is even and p2 is zero. */ - if (p1 & 0x1 || p2) { - si5351c_set_int_mode(&clock_gen, 0, 0); - } else { - si5351c_set_int_mode(&clock_gen, 0, 1); - } + if (detected_platform() != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + /* Integer mode can be enabled if p1 is even and p2 is zero. */ + if (p1 & 0x1 || p2) { + si5351c_set_int_mode(&clock_gen, 0, 0); + } else { + si5351c_set_int_mode(&clock_gen, 0, 1); + } - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - /* - * On HackRF One r9 all sample clocks are externally derived - * from MS1/CLK1 operating at twice the sample rate. - */ - si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); + if (detected_platform() == BOARD_ID_HACKRF1_R9) { + /* + * On HackRF One r9 all sample clocks are externally derived + * from MS1/CLK1 operating at twice the sample rate. + */ + si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); + } else { + /* + * On other platforms the clock generator produces three + * different sample clocks, all derived from multisynth 0. + */ + /* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */ + si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); + + /* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */ + si5351c_configure_multisynth( + &clock_gen, + 1, + 0, + 0, + 0, + 0); //p1 doesn't matter + + /* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */ + si5351c_configure_multisynth( + &clock_gen, + 2, + 0, + 0, + 0, + 0); //p1 doesn't matter + } +#endif } else { - /* - * On other platforms the clock generator produces three - * different sample clocks, all derived from multisynth 0. - */ - /* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */ +#if defined(PRALINE) || defined(UNIVERSAL) + /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); - /* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */ - si5351c_configure_multisynth(&clock_gen, 1, 0, 0, 0, 0); //p1 doesn't matter + /* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */ + si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 1); - /* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */ - si5351c_configure_multisynth(&clock_gen, 2, 0, 0, 0, 0); //p1 doesn't matter - } -#else - /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ - si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); + /* Delay FPGA_CLK relative to AFE_CLK. */ + uint8_t phase_offset = 0; + if (p1 < 2100) { + phase_offset = (p1 >> 4) - 6; + } + si5351c_set_phase(&clock_gen, 1, phase_offset); - /* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */ - si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 1); + if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { + /* + * On older boards FPGA_CLK is on CLK2 while SCT_CLK is on + * CLK1. We configure both so that behavior is consistent with + * newer boards that use CLK1 for both FPGA_CLK and SCT_CLK. + */ + si5351c_configure_multisynth(&clock_gen, 2, p1, p2, p3, 1); + si5351c_set_phase(&clock_gen, 2, phase_offset); + } - /* Delay FPGA_CLK relative to AFE_CLK. */ - uint8_t phase_offset = 0; - if (p1 < 2100) { - phase_offset = (p1 >> 4) - 6; - } - si5351c_set_phase(&clock_gen, 1, phase_offset); - - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* - * On older boards FPGA_CLK is on CLK2 while SCT_CLK is on - * CLK1. We configure both so that behavior is consistent with - * newer boards that use CLK1 for both FPGA_CLK and SCT_CLK. - */ - si5351c_configure_multisynth(&clock_gen, 2, p1, p2, p3, 1); - si5351c_set_phase(&clock_gen, 2, phase_offset); - } - - /* Reset PLL to synchronize output clock phase. */ - si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); + /* Reset PLL to synchronize output clock phase. */ + si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); #endif + } if (streaming) { sgpio_cpld_stream_enable(&sgpio_config); @@ -549,7 +563,7 @@ void cpu_clock_init(void) CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); -#if (defined JAWBREAKER || defined HACKRF_ONE || defined PRALINE) +#if !defined(RAD1O) /* Disable unused clocks */ /* Start with PLLs */ CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); @@ -707,12 +721,20 @@ void clock_gen_shutdown(void) clock_source_t activate_best_clock_source(void) { -#if (defined HACKRF_ONE || defined PRALINE) - /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ - if (portapack_reference_oscillator && portapack()) { - portapack_reference_oscillator(false); - } + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ + if (portapack_reference_oscillator && portapack()) { + portapack_reference_oscillator(false); + } #endif + break; + default: + break; + } clock_source_t source = CLOCK_SOURCE_HACKRF; @@ -720,18 +742,26 @@ clock_source_t activate_best_clock_source(void) if (si5351c_clkin_signal_valid(&clock_gen)) { source = CLOCK_SOURCE_EXTERNAL; } else { -#if (defined HACKRF_ONE || defined PRALINE) - /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ - if (portapack_reference_oscillator && portapack()) { - portapack_reference_oscillator(true); - delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */ - if (si5351c_clkin_signal_valid(&clock_gen)) { - source = CLOCK_SOURCE_PORTAPACK; - } else { - portapack_reference_oscillator(false); + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ + if (portapack_reference_oscillator && portapack()) { + portapack_reference_oscillator(true); + delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */ + if (si5351c_clkin_signal_valid(&clock_gen)) { + source = CLOCK_SOURCE_PORTAPACK; + } else { + portapack_reference_oscillator(false); + } } - } #endif + break; + default: + break; + } /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ } @@ -753,7 +783,7 @@ void ssp1_set_mode_max5864(void) spi_bus_start(max5864.bus, &ssp_config_max5864); } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void ssp1_set_mode_ice40(void) { spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); @@ -762,12 +792,14 @@ void ssp1_set_mode_ice40(void) void pin_shutdown(void) { - const platform_gpio_t* gpio = platform_gpio(); - const platform_scu_t* scu = platform_scu(); - /* Configure all GPIO as Input (safe state) */ gpio_init(); + /* Detect Platform */ + board_id_t board_id = detected_platform(); + const platform_gpio_t* gpio = platform_gpio(); + const platform_scu_t* scu = platform_scu(); + /* TDI and TMS pull-ups are required in all JTAG-compliant devices. * * The HackRF CPLD is always present, so let the CPLD pull up its TDI and TMS. @@ -782,119 +814,159 @@ void pin_shutdown(void) * * LPC43xx pull-up and pull-down resistors are approximately 53K. */ -#if (defined HACKRF_ONE || defined PRALINE) - scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); #endif + break; + default: + break; + } scu_pinmux(scu->PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); -#ifndef PRALINE - scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); #endif + } /* Configure SCU Pin Mux as GPIO */ scu_pinmux(scu->PINMUX_LED1, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED2, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED3, SCU_GPIO_NOPULL); -#ifdef RAD1O - scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4); + switch (board_id) { + case BOARD_ID_RAD1O: +#if defined(RAD1O) + scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4); #endif -#ifdef PRALINE - scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); #endif + break; + default: + break; + } /* Configure USB indicators */ -#ifdef JAWBREAKER - scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3); - scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3); + if (board_id == BOARD_ID_JAWBREAKER) { +#if defined(JAWBREAKER) + scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3); + scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3); #endif - -#ifdef PRALINE - disable_1v2_power(); - disable_3v3aux_power(); - gpio_output(gpio->gpio_1v2_enable); - gpio_output(gpio->gpio_3v3aux_enable_n); - scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#else - disable_1v8_power(); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - #ifdef HACKRF_ONE - gpio_output(gpio->h1r9_1v8_enable); - scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - #endif - } else { - gpio_output(gpio->gpio_1v8_enable); - scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); } + + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + disable_1v2_power(); + disable_3v3aux_power(); + gpio_output(gpio->gpio_1v2_enable); + gpio_output(gpio->gpio_3v3aux_enable_n); + scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); #endif - -#if (defined HACKRF_ONE || defined PRALINE) - /* Safe state: start with VAA turned off: */ - disable_rf_power(); - - /* Configure RF power supply (VAA) switch control signal as output */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - #ifdef HACKRF_ONE - gpio_output(gpio->h1r9_vaa_disable); - #endif - } else { - gpio_output(gpio->vaa_disable); + break; + default: + disable_1v8_power(); + if (detected_platform() == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio_output(gpio->h1r9_1v8_enable); + scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); +#endif + } else { + gpio_output(gpio->gpio_1v8_enable); + scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); + } + break; } + + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + /* Safe state: start with VAA turned off: */ + disable_rf_power(); + + /* Configure RF power supply (VAA) switch control signal as output */ + if (board_id == BOARD_ID_HACKRF1_R9) { + #if defined(HACKRF_ONE) || defined(UNIVERSAL) + gpio_output(gpio->h1r9_vaa_disable); + #endif + } else { + gpio_output(gpio->vaa_disable); + } + break; #endif + case BOARD_ID_RAD1O: +#if defined(RAD1O) + /* Safe state: start with VAA turned off: */ + disable_rf_power(); -#ifdef RAD1O - /* Safe state: start with VAA turned off: */ - disable_rf_power(); + /* Configure RF power supply (VAA) switch control signal as output */ + gpio_output(gpio->vaa_enable); - /* Configure RF power supply (VAA) switch control signal as output */ - gpio_output(gpio->vaa_enable); + /* Disable unused clock outputs. They generate noise. */ + scu_pinmux(CLK0, SCU_CLK_IN | SCU_CONF_FUNCTION7); + scu_pinmux(CLK2, SCU_CLK_IN | SCU_CONF_FUNCTION7); - /* Disable unused clock outputs. They generate noise. */ - scu_pinmux(CLK0, SCU_CLK_IN | SCU_CONF_FUNCTION7); - scu_pinmux(CLK2, SCU_CLK_IN | SCU_CONF_FUNCTION7); - - scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); #endif + break; + default: + break; + } -#ifdef PRALINE - scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG); - scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG); - scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG); - scu_pinmux(scu->P1_CTRL1, scu->P1_CTRL1_PINCFG); - scu_pinmux(scu->P1_CTRL2, scu->P1_CTRL2_PINCFG); - scu_pinmux(scu->CLKIN_CTRL, scu->CLKIN_CTRL_PINCFG); - scu_pinmux(scu->AA_EN, scu->AA_EN_PINCFG); - scu_pinmux(scu->TRIGGER_IN, scu->TRIGGER_IN_PINCFG); - scu_pinmux(scu->TRIGGER_OUT, scu->TRIGGER_OUT_PINCFG); - scu_pinmux(scu->PPS_OUT, scu->PPS_OUT_PINCFG); - scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4); - scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + switch (board_id) { + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG); + scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG); + scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG); + scu_pinmux(scu->P1_CTRL1, scu->P1_CTRL1_PINCFG); + scu_pinmux(scu->P1_CTRL2, scu->P1_CTRL2_PINCFG); + scu_pinmux(scu->CLKIN_CTRL, scu->CLKIN_CTRL_PINCFG); + scu_pinmux(scu->AA_EN, scu->AA_EN_PINCFG); + scu_pinmux(scu->TRIGGER_IN, scu->TRIGGER_IN_PINCFG); + scu_pinmux(scu->TRIGGER_OUT, scu->TRIGGER_OUT_PINCFG); + scu_pinmux(scu->PPS_OUT, scu->PPS_OUT_PINCFG); + scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4); + scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - p2_ctrl_set(P2_SIGNAL_CLK3); - p1_ctrl_set(P1_SIGNAL_CLKIN); - narrowband_filter_set(0); - clkin_ctrl_set(CLKIN_SIGNAL_P22); + p2_ctrl_set(P2_SIGNAL_CLK3); + p1_ctrl_set(P1_SIGNAL_CLKIN); + narrowband_filter_set(0); + clkin_ctrl_set(CLKIN_SIGNAL_P22); - gpio_output(gpio->p2_ctrl0); - gpio_output(gpio->p2_ctrl1); - gpio_output(gpio->p1_ctrl0); - gpio_output(gpio->p1_ctrl1); - gpio_output(gpio->p1_ctrl2); - gpio_output(gpio->clkin_ctrl); - gpio_output(gpio->pps_out); - gpio_output(gpio->aa_en); - gpio_input(gpio->trigger_in); - gpio_input(gpio->trigger_out); - gpio_clear(gpio->fpga_cfg_spi_cs); - gpio_output(gpio->fpga_cfg_spi_cs); - gpio_clear(gpio->fpga_cfg_creset); - gpio_output(gpio->fpga_cfg_creset); - gpio_input(gpio->fpga_cfg_cdone); + gpio_output(gpio->p2_ctrl0); + gpio_output(gpio->p2_ctrl1); + gpio_output(gpio->p1_ctrl0); + gpio_output(gpio->p1_ctrl1); + gpio_output(gpio->p1_ctrl2); + gpio_output(gpio->clkin_ctrl); + gpio_output(gpio->pps_out); + gpio_output(gpio->aa_en); + gpio_input(gpio->trigger_in); + gpio_input(gpio->trigger_out); + gpio_clear(gpio->fpga_cfg_spi_cs); + gpio_output(gpio->fpga_cfg_spi_cs); + gpio_clear(gpio->fpga_cfg_creset); + gpio_output(gpio->fpga_cfg_creset); + gpio_input(gpio->fpga_cfg_cdone); #endif + break; + default: + break; + } /* enable input on SCL and SDA pins */ SCU_SFSI2C0 = SCU_I2C0_NOMINAL; @@ -903,30 +975,43 @@ void pin_shutdown(void) /* Run after pin_shutdown() and prior to enabling power supplies. */ void pin_setup(void) { + /* Detect Platform */ + board_id_t board_id = detected_platform(); const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); + /* Configure LEDs */ led_off(0); led_off(1); led_off(2); -#ifdef RAD1O - led_off(3); -#endif + switch (board_id) { + case BOARD_ID_RAD1O: + led_off(3); + default: + break; + } gpio_output(gpio->led[0]); gpio_output(gpio->led[1]); gpio_output(gpio->led[2]); -#if (defined RAD1O || defined PRALINE) - gpio_output(gpio->led[3]); + switch (board_id) { + case BOARD_ID_RAD1O: + case BOARD_ID_PRALINE: +#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) + gpio_output(gpio->led[3]); #endif + break; + default: + break; + } /* Configure drivers and driver pins */ ssp_config_max283x.gpio_select = gpio->max283x_select; -#if defined(PRALINE) - ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words -#else - ssp_config_max283x.data_bits = SSP_DATA_16BITS; -#endif + if (board_id == BOARD_ID_PRALINE) { + ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words + } else { + ssp_config_max283x.data_bits = SSP_DATA_16BITS; + } ssp_config_max5864.gpio_select = gpio->max5864_select; @@ -935,11 +1020,13 @@ void pin_setup(void) spi_flash.gpio_wp = gpio->w25q80bv_wp; sgpio_config.gpio_q_invert = gpio->q_invert; -#if !defined(PRALINE) - sgpio_config.gpio_trigger_enable = gpio->trigger_enable; + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + sgpio_config.gpio_trigger_enable = gpio->trigger_enable; #endif + } -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; ice40.gpio_select = gpio->fpga_cfg_spi_cs; ice40.gpio_creset = gpio->fpga_cfg_creset; @@ -952,73 +1039,102 @@ void pin_setup(void) jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; #endif -#if defined(HACKRF_ONE) || defined(PRALINE) - jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; - jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; + jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; + jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; #endif + } + switch (board_id) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; + jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; +#endif + break; + default: + break; + } ssp1_set_mode_max283x(); mixer_bus_setup(&mixer); -#if defined(HACKRF_ONE) if (detected_platform() == BOARD_ID_HACKRF1_R9) { - rf_path.gpio_rx = gpio->h1r9_rx; +#if defined(HACKRF_ONE) || defined(UNIVERSAL) sgpio_config.gpio_trigger_enable = gpio->h1r9_trigger_enable; - } #endif + } - /* Configure RF path */ -#if defined(HACKRF_ONE) - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_hp = gpio->hp, - .gpio_lp = gpio->lp, - .gpio_tx_mix_bp = gpio->tx_mix_bp, - .gpio_no_mix_bypass = gpio->no_mix_bypass, - .gpio_rx_mix_bp = gpio->rx_mix_bp, - .gpio_tx_amp = gpio->tx_amp, - .gpio_tx = gpio->tx, - .gpio_mix_bypass = gpio->mix_bypass, - .gpio_rx = gpio->rx, - .gpio_no_tx_amp_pwr = gpio->no_tx_amp_pwr, - .gpio_amp_bypass = gpio->amp_bypass, - .gpio_rx_amp = gpio->rx_amp, - .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, - }; - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - rf_path.gpio_rx = gpio->h1r9_rx; - rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; - } -#elif defined(RAD1O) - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_tx_rx_n = gpio->tx_rx_n, - .gpio_tx_rx = gpio->tx_rx, - .gpio_by_mix = gpio->by_mix, - .gpio_by_mix_n = gpio->by_mix_n, - .gpio_by_amp = gpio->by_amp, - .gpio_by_amp_n = gpio->by_amp_n, - .gpio_mixer_en = gpio->mixer_en, - .gpio_low_high_filt = gpio->low_high_filt, - .gpio_low_high_filt_n = gpio->low_high_filt_n, - .gpio_tx_amp = gpio->tx_amp, - .gpio_rx_lna = gpio->rx_lna, - }; -#elif defined(PRALINE) - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_tx_en = gpio->tx_en, - .gpio_mix_en_n = gpio->mix_en_n, - .gpio_lpf_en = gpio->lpf_en, - .gpio_rf_amp_en = gpio->rf_amp_en, - .gpio_ant_bias_en_n = gpio->ant_bias_en_n, - }; - board_rev_t rev = detected_revision(); - if ((rev == BOARD_REV_PRALINE_R1_0) || (rev == BOARD_REV_GSG_PRALINE_R1_0)) { - rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; - } + // initialize rf_path struct and assign gpio's + switch (board_id) { + case BOARD_ID_JAWBREAKER: + break; + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_hp = gpio->hp, + .gpio_lp = gpio->lp, + .gpio_tx_mix_bp = gpio->tx_mix_bp, + .gpio_no_mix_bypass = gpio->no_mix_bypass, + .gpio_rx_mix_bp = gpio->rx_mix_bp, + .gpio_tx_amp = gpio->tx_amp, + .gpio_tx = gpio->tx, + .gpio_mix_bypass = gpio->mix_bypass, + .gpio_rx = gpio->rx, + .gpio_no_tx_amp_pwr = gpio->no_tx_amp_pwr, + .gpio_amp_bypass = gpio->amp_bypass, + .gpio_rx_amp = gpio->rx_amp, + .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, + }; + if (board_id == BOARD_ID_HACKRF1_R9) { + rf_path.gpio_rx = gpio->h1r9_rx; + rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; + } #endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_tx_rx_n = gpio->tx_rx_n, + .gpio_tx_rx = gpio->tx_rx, + .gpio_by_mix = gpio->by_mix, + .gpio_by_mix_n = gpio->by_mix_n, + .gpio_by_amp = gpio->by_amp, + .gpio_by_amp_n = gpio->by_amp_n, + .gpio_mixer_en = gpio->mixer_en, + .gpio_low_high_filt = gpio->low_high_filt, + .gpio_low_high_filt_n = gpio->low_high_filt_n, + .gpio_tx_amp = gpio->tx_amp, + .gpio_rx_lna = gpio->rx_lna, + }; +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_tx_en = gpio->tx_en, + .gpio_mix_en_n = gpio->mix_en_n, + .gpio_lpf_en = gpio->lpf_en, + .gpio_rf_amp_en = gpio->rf_amp_en, + .gpio_ant_bias_en_n = gpio->ant_bias_en_n, + }; + if ((detected_revision() == BOARD_REV_PRALINE_R1_0) || + (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { + rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; + } +#endif + break; + default: + break; + } rf_path_pin_setup(&rf_path); /* Configure external clock in */ @@ -1027,52 +1143,86 @@ void pin_setup(void) sgpio_configure_pin_functions(&sgpio_config); } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void enable_1v2_power(void) { - gpio_set(platform_gpio()->gpio_1v2_enable); + switch (detected_platform()) { + case BOARD_ID_PRALINE: + gpio_set(platform_gpio()->gpio_1v2_enable); + break; + default: + break; + } } void disable_1v2_power(void) { - gpio_clear(platform_gpio()->gpio_1v2_enable); + switch (detected_platform()) { + case BOARD_ID_PRALINE: + gpio_clear(platform_gpio()->gpio_1v2_enable); + break; + default: + break; + } } void enable_3v3aux_power(void) { - gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); + switch (detected_platform()) { + case BOARD_ID_PRALINE: + gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); + break; + default: + break; + } } void disable_3v3aux_power(void) { - gpio_set(platform_gpio()->gpio_3v3aux_enable_n); + switch (detected_platform()) { + case BOARD_ID_PRALINE: + gpio_set(platform_gpio()->gpio_3v3aux_enable_n); + break; + default: + break; + } } -#else +#endif + void enable_1v8_power(void) { - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - #ifdef HACKRF_ONE + switch (detected_platform()) { + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) gpio_set(platform_gpio()->h1r9_1v8_enable); - #endif - } else { +#endif + break; + case BOARD_ID_PRALINE: + break; + default: gpio_set(platform_gpio()->gpio_1v8_enable); + break; } } void disable_1v8_power(void) { - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - #ifdef HACKRF_ONE + switch (detected_platform()) { + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) gpio_clear(platform_gpio()->h1r9_1v8_enable); - #endif - } else { +#endif + break; + case BOARD_ID_PRALINE: + break; + default: gpio_clear(platform_gpio()->gpio_1v8_enable); + break; } } -#endif -#ifdef HACKRF_ONE -void enable_rf_power(void) +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +static inline void enable_rf_power_hackrf_one(void) { const platform_gpio_t* gpio = platform_gpio(); uint32_t i; @@ -1089,7 +1239,7 @@ void enable_rf_power(void) } } -void disable_rf_power(void) +static inline void disable_rf_power_hackrf_one(void) { if (detected_platform() == BOARD_ID_HACKRF1_R9) { gpio_set(platform_gpio()->h1r9_vaa_disable); @@ -1099,8 +1249,8 @@ void disable_rf_power(void) } #endif -#ifdef PRALINE -void enable_rf_power(void) +#if defined(PRALINE) || defined(UNIVERSAL) +static inline void enable_rf_power_praline(void) { gpio_clear(platform_gpio()->vaa_disable); @@ -1108,14 +1258,14 @@ void enable_rf_power(void) delay(1000000); } -void disable_rf_power(void) +static inline void disable_rf_power_praline(void) { gpio_set(platform_gpio()->vaa_disable); } #endif -#ifdef RAD1O -void enable_rf_power(void) +#if defined(RAD1O) +static inline void enable_rf_power_rad1o(void) { gpio_set(platform_gpio()->vaa_enable); @@ -1123,33 +1273,77 @@ void enable_rf_power(void) delay(1000000); } -void disable_rf_power(void) +static inline void disable_rf_power_rad1o(void) { gpio_clear(platform_gpio()->vaa_enable); } #endif -#ifdef PRALINE -void led_on(const led_t led) +void enable_rf_power(void) { - gpio_clear(platform_gpio()->led[led]); -} - -void led_off(const led_t led) -{ - gpio_set(platform_gpio()->led[led]); -} -#else -void led_on(const led_t led) -{ - gpio_set(platform_gpio()->led[led]); -} - -void led_off(const led_t led) -{ - gpio_clear(platform_gpio()->led[led]); -} + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + enable_rf_power_hackrf_one(); #endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + enable_rf_power_praline(); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + enable_rf_power_rad1o(); +#endif + break; + default: + break; + } +} + +void disable_rf_power(void) +{ + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + disable_rf_power_hackrf_one(); +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + disable_rf_power_praline(); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + disable_rf_power_rad1o(); +#endif + break; + default: + break; + } +} + +void led_on(const led_t led) +{ + if (detected_platform() == BOARD_ID_PRALINE) { + gpio_clear(platform_gpio()->led[led]); + } else { + gpio_set(platform_gpio()->led[led]); + } +} + +void led_off(const led_t led) +{ + if (detected_platform() == BOARD_ID_PRALINE) { + gpio_set(platform_gpio()->led[led]); + } else { + gpio_clear(platform_gpio()->led[led]); + } +} void led_toggle(const led_t led) { @@ -1158,26 +1352,33 @@ void led_toggle(const led_t led) void set_leds(const uint8_t state) { + board_id_t board_id = detected_platform(); + int num_leds = 3; -#if (defined RAD1O || defined PRALINE) - num_leds = 4; -#endif + if (board_id == BOARD_ID_RAD1O || board_id == BOARD_ID_PRALINE) { + num_leds = 4; + } + for (int i = 0; i < num_leds; i++) { -#ifdef PRALINE - gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); -#else - gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); -#endif + if (board_id == BOARD_ID_PRALINE) { + gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); + } else { + gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); + } } } void trigger_enable(const bool enable) { -#ifndef PRALINE - gpio_write(sgpio_config.gpio_trigger_enable, enable); -#else - fpga_set_trigger_enable(&fpga, enable); + if (detected_platform() != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + gpio_write(sgpio_config.gpio_trigger_enable, enable); #endif + } else { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_set_trigger_enable(&fpga, enable); +#endif + } } void halt_and_flash(const uint32_t duration) @@ -1195,7 +1396,7 @@ void halt_and_flash(const uint32_t duration) } } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void p1_ctrl_set(const p1_ctrl_signal_t signal) { const platform_gpio_t* gpio = platform_gpio(); diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 539ce770..93bc44c9 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Great Scott Gadgets + * Copyright 2012-2026 Great Scott Gadgets * Copyright 2012 Benjamin Vernoux * Copyright 2012 Jared Boone * @@ -42,7 +42,7 @@ extern "C" { #include "si5351c.h" #include "spi_ssp.h" #include "w25q80bv.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "fpga.h" #include "ice40_spi.h" #endif @@ -51,7 +51,8 @@ extern "C" { extern si5351c_driver_t clock_gen; extern ssp_config_t ssp_config_w25q80bv; -#ifdef PRALINE +extern max283x_driver_t max283x; +#if defined(PRALINE) || defined(UNIVERSAL) extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif @@ -70,28 +71,27 @@ void clock_gen_init(void); void clock_gen_shutdown(void); void ssp1_set_mode_max283x(void); void ssp1_set_mode_max5864(void); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void ssp1_set_mode_ice40(void); #endif void pin_shutdown(void); void pin_setup(void); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) void enable_1v2_power(void); void disable_1v2_power(void); void enable_3v3aux_power(void); void disable_3v3aux_power(void); -#else +#endif void enable_1v8_power(void); void disable_1v8_power(void); -#endif fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program); clock_source_t activate_best_clock_source(void); -#if (defined HACKRF_ONE || defined RAD1O || defined PRALINE) +#if defined(RAD1O) || defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) void enable_rf_power(void); void disable_rf_power(void); #endif @@ -112,7 +112,7 @@ void trigger_enable(const bool enable); void halt_and_flash(const uint32_t duration); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) typedef enum { P1_SIGNAL_TRIGGER_IN = 0, P1_SIGNAL_AUX_CLK1 = 1, diff --git a/firmware/common/selftest.h b/firmware/common/selftest.h index 28e02415..8223a582 100644 --- a/firmware/common/selftest.h +++ b/firmware/common/selftest.h @@ -37,23 +37,22 @@ typedef uint8_t test_result_t; typedef struct { uint16_t mixer_id; -#ifndef RAD1O +#if !defined(RAD1O) bool mixer_locks[NUM_LOCK_ATTEMPTS]; #endif -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) uint16_t max2831_mux_rssi_1; uint16_t max2831_mux_temp; uint16_t max2831_mux_rssi_2; bool max2831_mux_test_ok; -#else +#endif uint16_t max283x_readback_bad_value; uint16_t max283x_readback_expected_value; uint8_t max283x_readback_register_count; uint8_t max283x_readback_total_registers; -#endif uint8_t si5351_rev_id; bool si5351_readback_ok; -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) test_result_t fpga_image_load; test_result_t fpga_spi; test_result_t sgpio_rx; From 85dfacf6efa420955c949aa426282ad7a1681d23 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 01:12:34 +0100 Subject: [PATCH 046/161] Universalize: firmware/hackrf_usb Co-authored-by: Antoine van Gelder --- firmware/hackrf_usb/hackrf_usb.c | 192 ++++++++++++++-------- firmware/hackrf_usb/usb_api_board_info.c | 15 +- firmware/hackrf_usb/usb_api_cpld.c | 10 ++ firmware/hackrf_usb/usb_api_praline.c | 21 +++ firmware/hackrf_usb/usb_api_register.h | 2 +- firmware/hackrf_usb/usb_api_selftest.c | 170 ++++++++++--------- firmware/hackrf_usb/usb_api_sweep.c | 19 ++- firmware/hackrf_usb/usb_api_transceiver.c | 11 ++ firmware/hackrf_usb/usb_descriptor.c | 132 +++++++++++---- firmware/hackrf_usb/usb_descriptor.h | 28 +++- firmware/hackrf_usb/usb_device.c | 43 ++++- firmware/hackrf_usb/usb_device.h | 13 ++ 12 files changed, 453 insertions(+), 203 deletions(-) diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 3238ca0e..4952f85d 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -48,23 +48,22 @@ #include #include #include -#if !defined(RAD1O) - #include -#endif -#if defined(PRALINE) || defined(HACKRF_ONE) +#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) #include #endif -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(JAWBREAKER) +#if !defined(RAD1O) + #include #include #endif -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include #if !(defined(DFU_MODE) || defined(RAM_MODE)) #include #include #include #endif -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) #include #include #endif @@ -82,9 +81,10 @@ #include "usb_descriptor.h" #include "usb_device.h" #include "usb_endpoint.h" -#if defined(PRALINE) +#if defined(PRALINE) || defined(UNIVERSAL) #include "usb_api_praline.h" -#else +#endif +#if !defined(PRALINE) || defined(UNIVERSAL) #include "usb_api_cpld.h" #endif @@ -102,12 +102,12 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_si5351c, usb_vendor_request_set_sample_rate_frac, usb_vendor_request_set_baseband_filter_bandwidth, -#ifdef RAD1O - NULL, // write_rffc5071 not used - NULL, // read_rffc5071 not used -#else +#if !defined(RAD1O) usb_vendor_request_write_rffc5071, usb_vendor_request_read_rffc5071, +#else + NULL, // write_rffc5071 not used + NULL, // read_rffc5071 not used #endif usb_vendor_request_erase_spiflash, usb_vendor_request_write_spiflash, @@ -122,7 +122,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_set_vga_gain, usb_vendor_request_set_txvga_gain, NULL, // was set_if_freq -#if (defined HACKRF_ONE || defined PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) usb_vendor_request_set_antenna_enable, #else NULL, @@ -139,7 +139,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_spiflash_status, usb_vendor_request_spiflash_clear_status, usb_vendor_request_operacake_gpio_test, -#ifdef HACKRF_ONE +#if defined(HACKRF_ONE) || defined(UNIVERSAL) usb_vendor_request_cpld_checksum, #else NULL, @@ -156,7 +156,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_supported_platform, usb_vendor_request_set_leds, usb_vendor_request_user_config_set_bias_t_opts, -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) usb_vendor_request_write_fpga_reg, usb_vendor_request_read_fpga_reg, usb_vendor_request_p2_ctrl, @@ -252,7 +252,7 @@ void usb_set_descriptor_by_serial_number(void) } } -#ifndef PRALINE +#if !defined(PRALINE) || defined(UNIVERSAL) static bool cpld_jtag_sram_load(jtag_t* const jtag) { cpld_jtag_take(jtag); @@ -280,7 +280,7 @@ static void m0_rom_to_ram(void) memcpy(dest, (uint32_t*) (base + src), len); } -#if defined(PRALINE) && !(defined(DFU_MODE) || defined(RAM_MODE)) +#if (defined(PRALINE) || defined(UNIVERSAL)) && !(defined(DFU_MODE) || defined(RAM_MODE)) extern uint32_t _binary_fpga_bin_start; void fpga_loader_setup(void) @@ -347,7 +347,7 @@ void radio_changed(const uint32_t changed) if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { hackrf_ui()->set_frequency(freq / FP_ONE_HZ); } -#if defined(HACKRF_ONE) || defined(PRALINE) +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) operacake_set_range(freq / FP_ONE_MHZ); #endif } @@ -435,74 +435,91 @@ int main(void) // Detect hardware platform before we do anything else. detect_hardware_platform(); + board_id_t board_id = detected_platform(); pin_shutdown(); -#ifndef RAD1O - clock_gen_shutdown(); -#endif + if (board_id != BOARD_ID_RAD1O) { + clock_gen_shutdown(); + } delay_us_at_mhz(10000, 96); pin_setup(); -#ifndef PRALINE - enable_1v8_power(); - #ifndef RAD1O - /* - * On rad1o, the clock generator power supply comes from the RF supply - * which is enabled later. On H1 and Jawbreaker, the clock generator is - * on the main 3V3 supply. - */ - clock_gen_init(); - #endif -#else - enable_3v3aux_power(); + if (board_id != BOARD_ID_PRALINE) { + enable_1v8_power(); + if (board_id != BOARD_ID_RAD1O) { + /* + * On rad1o, the clock generator power supply comes from the RF supply + * which is enabled later. On H1 and Jawbreaker, the clock generator is + * on the main 3V3 supply. + */ + clock_gen_init(); + } + } else { +#if defined(PRALINE) || defined(UNIVERSAL) + enable_3v3aux_power(); #if !defined(DFU_MODE) && !defined(RAM_MODE) - enable_1v2_power(); - enable_rf_power(); - /* - * On Praline, the clock generator power supply comes from 3V3FPGA - * which is enabled when 1V2FPGA is turned on. - */ - clock_gen_init(); + enable_1v2_power(); + enable_rf_power(); + /* + * On Praline, the clock generator power supply comes from 3V3FPGA + * which is enabled when 1V2FPGA is turned on. + */ + clock_gen_init(); #endif #endif + } tuning_setup(); -#ifdef HACKRF_ONE - // Set up mixer before enabling RF power, because its - // GPO is used to control the antenna bias tee. - mixer_setup(&mixer, RFFC5071_VARIANT); + if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + // Set up mixer before enabling RF power, because its + // GPO is used to control the antenna bias tee. + mixer_setup(&mixer, RFFC5071_VARIANT); #endif -#if (defined HACKRF_ONE || defined RAD1O) - enable_rf_power(); -#endif -#ifdef RAD1O - clock_gen_init(); + } + if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9 || + board_id == BOARD_ID_RAD1O) { +#if defined(RAD1O) || defined(HACKRF_ONE) || defined(UNIVERSAL) + enable_rf_power(); #endif + } + if (board_id == BOARD_ID_RAD1O) { + clock_gen_init(); + } cpu_clock_init(); /* Wake the M0 */ ipc_halt_m0(); ipc_start_m0((uint32_t) &__ram_m0_start__); -#ifndef PRALINE - if (!cpld_jtag_sram_load(&jtag_cpld)) { - halt_and_flash(6000000); +#if !defined(PRALINE) || defined(UNIVERSAL) + if (board_id != BOARD_ID_PRALINE) { + if (!cpld_jtag_sram_load(&jtag_cpld)) { + halt_and_flash(6000000); + } } -#else - #if defined(DFU_MODE) || defined(RAM_MODE) - selftest.fpga_image_load = SKIPPED; - selftest.report.pass = false; - #else - fpga_image_load(&fpga_loader, 0); - #endif - delay_us_at_mhz(100, 204); - fpga_spi_selftest(); - fpga_sgpio_selftest(); #endif +#if defined(PRALINE) || defined(UNIVERSAL) + if (board_id == BOARD_ID_PRALINE) { + #if defined(DFU_MODE) || defined(RAM_MODE) + selftest.fpga_image_load = SKIPPED; + selftest.report.pass = false; + #else + fpga_image_load(&fpga_loader, 0); + #endif + delay_us_at_mhz(100, 204); + fpga_spi_selftest(); + fpga_sgpio_selftest(); + } +#endif + radio.update_cb = radio_changed; radio_init(&radio); -#if (defined HACKRF_ONE || defined PRALINE) - portapack_init(); + if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9 || + board_id == BOARD_ID_PRALINE) { +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + portapack_init(); #endif + } #ifndef DFU_MODE usb_set_descriptor_by_serial_number(); @@ -511,6 +528,35 @@ int main(void) usb_set_configuration_changed_cb(usb_configuration_changed); usb_peripheral_reset(); + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + memcpy(&usb_device, + &usb_device_hackrf_one, + sizeof(usb_device_hackrf_one)); +#endif + break; + case BOARD_ID_JAWBREAKER: +#if defined(JAWBREAKER) + memcpy(&usb_device, + &usb_device_jawbreaker, + sizeof(usb_device_jawbreaker)); +#endif + break; + case BOARD_ID_RAD1O: +#if defined(RAD1O) + memcpy(&usb_device, &usb_device_rad1o, sizeof(usb_device_rad1o)); +#endif + break; + case BOARD_ID_PRALINE: +#if defined(PRALINE) || defined(UNIVERSAL) + memcpy(&usb_device, &usb_device_praline, sizeof(usb_device_praline)); +#endif + break; + default: + break; + }; usb_device_init(0, &usb_device); usb_queue_init(&usb_endpoint_control_out_queue); @@ -529,13 +575,15 @@ int main(void) rf_path_init(&rf_path); -#ifndef RAD1O +#if !defined(RAD1O) rffc5071_lock_test(&mixer.rffc5071); #endif -#ifdef PRALINE - fpga_if_xcvr_selftest(); + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + fpga_if_xcvr_selftest(); #endif + } if (da7219_detect()) { operacake_skip_i2c_address(DA7219_ADDRESS); @@ -581,11 +629,13 @@ int main(void) case TRANSCEIVER_MODE_RX_SWEEP: sweep_mode(request.seq); break; -#ifndef PRALINE case TRANSCEIVER_MODE_CPLD_UPDATE: - cpld_update(); - break; + if (board_id != BOARD_ID_PRALINE) { +#if !defined(PRALINE) || defined(UNIVERSAL) + cpld_update(); #endif + } + break; default: break; } diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index a1ad3bce..8e64ad7f 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -34,7 +34,7 @@ #include #include #include -#ifdef HACKRF_ONE +#if defined(HACKRF_ONE) || defined(UNIVERSAL) #include #include #endif @@ -131,18 +131,19 @@ usb_request_status_t usb_vendor_request_reset( if (stage == USB_TRANSFER_STAGE_SETUP) { pin_shutdown(); clock_gen_shutdown(); -#ifdef HACKRF_ONE - /* - * Set boot pins as inputs so that the bootloader reads them - * correctly after the reset. - */ if (detected_platform() == BOARD_ID_HACKRF1_R9) { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) + /* + * Set boot pins as inputs so that the bootloader reads them + * correctly after the reset. + */ const platform_gpio_t* gpio = platform_gpio(); gpio_input(gpio->h1r9_mcu_clk_en); gpio_input(gpio->h1r9_clkout_en); gpio_input(gpio->h1r9_rx); - } #endif + } + wwdt_reset(100000); usb_transfer_schedule_ack(endpoint->in); diff --git a/firmware/hackrf_usb/usb_api_cpld.c b/firmware/hackrf_usb/usb_api_cpld.c index 7d8200c3..05636a0b 100644 --- a/firmware/hackrf_usb/usb_api_cpld.c +++ b/firmware/hackrf_usb/usb_api_cpld.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,15 @@ usb_request_status_t usb_vendor_request_cpld_checksum( static uint32_t cpld_crc; uint8_t length; + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + // supported + break; + default: + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { cpld_jtag_take(&jtag_cpld); const bool checksum_success = cpld_xc2c64a_jtag_checksum( diff --git a/firmware/hackrf_usb/usb_api_praline.c b/firmware/hackrf_usb/usb_api_praline.c index 6a44da60..2a41b839 100644 --- a/firmware/hackrf_usb/usb_api_praline.c +++ b/firmware/hackrf_usb/usb_api_praline.c @@ -24,6 +24,7 @@ #include "usb_api_praline.h" #include +#include #include #include #include @@ -35,6 +36,10 @@ usb_request_status_t usb_vendor_request_p1_ctrl( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { p1_ctrl_set(endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -46,6 +51,10 @@ usb_request_status_t usb_vendor_request_p2_ctrl( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { p2_ctrl_set(endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -57,6 +66,10 @@ usb_request_status_t usb_vendor_request_clkin_ctrl( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { clkin_ctrl_set(endpoint->setup.value & 1); usb_transfer_schedule_ack(endpoint->in); @@ -68,6 +81,10 @@ usb_request_status_t usb_vendor_request_set_narrowband_filter( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { narrowband_filter_set(endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -86,6 +103,10 @@ usb_request_status_t usb_vendor_request_set_fpga_bitstream( #else extern struct fpga_loader_t fpga_loader; + if (detected_platform() != BOARD_ID_PRALINE) { + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { if (!fpga_image_load(&fpga_loader, endpoint->setup.value)) { return USB_REQUEST_STATUS_STALL; diff --git a/firmware/hackrf_usb/usb_api_register.h b/firmware/hackrf_usb/usb_api_register.h index 24825144..58a84aff 100644 --- a/firmware/hackrf_usb/usb_api_register.h +++ b/firmware/hackrf_usb/usb_api_register.h @@ -56,7 +56,7 @@ usb_request_status_t usb_vendor_request_set_leds( usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index 4fe2e628..fa5267b5 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -21,20 +21,18 @@ #include #include -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(JAWBREAKER) +#if !defined(RAD1O) #include #endif #include #include +#include #include #include #include #include -#if !defined(PRALINE) - #include -#endif #include "usb_api_selftest.h" @@ -63,7 +61,7 @@ void append(char** dest, size_t* capacity, const char* str) } } -#ifdef PRALINE +#if defined(PRALINE) || defined(UNIVERSAL) static const char* test_result_to_str(test_result_t result) { switch (result) { @@ -82,91 +80,105 @@ static const char* test_result_to_str(test_result_t result) void generate_selftest_report(void) { + board_id_t board_id = detected_platform(); + char* s = &selftest.report.msg[0]; size_t c = sizeof(selftest.report.msg); -#ifdef RAD1O - append(&s, &c, "Mixer: MAX2871, ID: "); - append(&s, &c, itoa(selftest.mixer_id, 10)); - append(&s, &c, "\n"); -#else - append(&s, &c, "Mixer: RFFC5072, ID: "); - append(&s, &c, itoa(selftest.mixer_id >> 3, 10)); - append(&s, &c, ", Rev: "); - append(&s, &c, itoa(selftest.mixer_id & 0x7, 10)); - append(&s, &c, ", Locks: "); - bool lock; - for (int i = 0; i < NUM_LOCK_ATTEMPTS; i++) { - lock = selftest.mixer_locks[i]; - append(&s, &c, itoa(lock, 2)); - } - if (lock) { - append(&s, &c, " (PASS)"); - } else { - append(&s, &c, " (FAIL)"); - } - append(&s, &c, "\n"); + if (board_id == BOARD_ID_RAD1O) { +#if defined(RAD1O) + append(&s, &c, "Mixer: MAX2871, ID: "); + append(&s, &c, itoa(selftest.mixer_id, 10)); + append(&s, &c, "\n"); #endif + } else { +#if !defined(RAD1O) + append(&s, &c, "Mixer: RFFC5072, ID: "); + append(&s, &c, itoa(selftest.mixer_id >> 3, 10)); + append(&s, &c, ", Rev: "); + append(&s, &c, itoa(selftest.mixer_id & 0x7, 10)); + append(&s, &c, ", Locks: "); + bool lock; + for (int i = 0; i < NUM_LOCK_ATTEMPTS; i++) { + lock = selftest.mixer_locks[i]; + append(&s, &c, itoa(lock, 2)); + } + if (lock) { + append(&s, &c, " (PASS)"); + } else { + append(&s, &c, " (FAIL)"); + } + append(&s, &c, "\n"); +#endif + } + append(&s, &c, "Clock: Si5351"); append(&s, &c, ", Rev: "); append(&s, &c, itoa(selftest.si5351_rev_id, 10)); append(&s, &c, ", readback: "); append(&s, &c, selftest.si5351_readback_ok ? "OK" : "FAIL"); append(&s, &c, "\n"); -#ifdef PRALINE - append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); - append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); - append(&s, &c, "\n"); -#else - append(&s, &c, "Transceiver: "); - append(&s, - &c, - (detected_platform() == BOARD_ID_HACKRF1_R9 ? "MAX2839" : "MAX2837")); - append(&s, &c, ", readback success: "); - append(&s, &c, itoa(selftest.max283x_readback_register_count, 10)); - append(&s, &c, "/"); - append(&s, &c, itoa(selftest.max283x_readback_total_registers, 10)); - if (selftest.max283x_readback_register_count < - selftest.max283x_readback_total_registers) { - append(&s, &c, ", bad value: 0x"); - append(&s, &c, itoa(selftest.max283x_readback_bad_value, 10)); - append(&s, &c, ", expected: 0x"); - append(&s, &c, itoa(selftest.max283x_readback_expected_value, 10)); - } - append(&s, &c, "\n"); -#endif -#ifdef PRALINE - append(&s, &c, "FPGA configuration: "); - append(&s, &c, test_result_to_str(selftest.fpga_image_load)); - append(&s, &c, "\n"); - append(&s, &c, "FPGA SPI: "); - append(&s, &c, test_result_to_str(selftest.fpga_spi)); - append(&s, &c, "\n"); - append(&s, &c, "SGPIO RX test: "); - append(&s, &c, test_result_to_str(selftest.sgpio_rx)); - append(&s, &c, "\n"); - append(&s, &c, "Loopback test: "); - append(&s, &c, test_result_to_str(selftest.xcvr_loopback)); - append(&s, &c, "\n"); - // Dump transceiver loopback measurements. - for (int i = 0; i < 4; ++i) { - struct xcvr_measurements* m = &selftest.xcvr_measurements[i]; - append(&s, &c, " "); - append(&s, &c, itoa(i, 10)); - append(&s, &c, ":"); - append(&s, &c, itoa(m->zcs_i, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->zcs_q, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->max_mag_i, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->max_mag_q, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->avg_mag_sq_i, 10)); - append(&s, &c, ","); - append(&s, &c, itoa(m->avg_mag_sq_q, 10)); + + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); + append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); append(&s, &c, "\n"); - } #endif + } else { +#if !defined(PRALINE) || defined(UNIVERSAL) + append(&s, &c, "Transceiver: "); + append(&s, &c, (board_id == BOARD_ID_HACKRF1_R9 ? "MAX2839" : "MAX2837")); + append(&s, &c, ", readback success: "); + append(&s, &c, itoa(selftest.max283x_readback_register_count, 10)); + append(&s, &c, "/"); + append(&s, &c, itoa(selftest.max283x_readback_total_registers, 10)); + if (selftest.max283x_readback_register_count < + selftest.max283x_readback_total_registers) { + append(&s, &c, ", bad value: 0x"); + append(&s, &c, itoa(selftest.max283x_readback_bad_value, 10)); + append(&s, &c, ", expected: 0x"); + append(&s, + &c, + itoa(selftest.max283x_readback_expected_value, 10)); + } + append(&s, &c, "\n"); +#endif + } + if (board_id == BOARD_ID_PRALINE) { +#if defined(PRALINE) || defined(UNIVERSAL) + append(&s, &c, "FPGA configuration: "); + append(&s, &c, test_result_to_str(selftest.fpga_image_load)); + append(&s, &c, "\n"); + append(&s, &c, "FPGA SPI: "); + append(&s, &c, test_result_to_str(selftest.fpga_spi)); + append(&s, &c, "\n"); + append(&s, &c, "SGPIO RX test: "); + append(&s, &c, test_result_to_str(selftest.sgpio_rx)); + append(&s, &c, "\n"); + append(&s, &c, "Loopback test: "); + append(&s, &c, test_result_to_str(selftest.xcvr_loopback)); + append(&s, &c, "\n"); + // Dump transceiver loopback measurements. + for (int i = 0; i < 4; ++i) { + struct xcvr_measurements* m = &selftest.xcvr_measurements[i]; + append(&s, &c, " "); + append(&s, &c, itoa(i, 10)); + append(&s, &c, ":"); + append(&s, &c, itoa(m->zcs_i, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->zcs_q, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->max_mag_i, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->max_mag_q, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->avg_mag_sq_i, 10)); + append(&s, &c, ","); + append(&s, &c, itoa(m->avg_mag_sq_q, 10)); + append(&s, &c, "\n"); + } +#endif + } } usb_request_status_t usb_vendor_request_read_selftest( diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index a9bfc695..4f7d8e57 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -46,11 +47,6 @@ #define MAX(x, y) ((x) > (y) ? (x) : (y)) #define FREQ_GRANULARITY 1000000 #define MAX_RANGES 10 -#ifndef PRALINE - #define THROWAWAY_BUFFERS 2 -#else - #define THROWAWAY_BUFFERS 1 -#endif static uint64_t sweep_freq; static uint16_t frequencies[MAX_RANGES * 2]; @@ -59,6 +55,7 @@ static uint16_t num_ranges = 0; static uint32_t dwell_blocks = 0; static uint32_t step_width = 0; static uint32_t offset = 0; +static uint32_t throwaway_buffers = 0; static enum sweep_style style = LINEAR; static bool freq_ui_dirty = false; static bool img_reject_ui_dirty = false; @@ -91,6 +88,12 @@ usb_request_status_t usb_vendor_request_init_sweep( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + if (detected_platform() == BOARD_ID_PRALINE) { + throwaway_buffers = 1; + } else { + throwaway_buffers = 2; + } + uint32_t num_bytes; int i; if (stage == USB_TRANSFER_STAGE_SETUP) { @@ -144,7 +147,7 @@ void sweep_bulk_transfer_complete(void* user_data, unsigned int bytes_transferre // For each buffer transferred, we need to bump the count to allow // for the buffer(s) that are to be discarded. - m0_state.m4_count += (THROWAWAY_BUFFERS + 1) * 0x4000; + m0_state.m4_count += (throwaway_buffers + 1) * 0x4000; } void sweep_mode(uint32_t seq) @@ -200,7 +203,7 @@ void sweep_mode(uint32_t seq) // Set M0 to switch back to RX after we have received our // discard buffers. - m0_state.threshold += (0x4000 * THROWAWAY_BUFFERS); + m0_state.threshold += (0x4000 * throwaway_buffers); m0_state.next_mode = M0_MODE_RX; // Write metadata to buffer. @@ -225,7 +228,7 @@ void sweep_mode(uint32_t seq) NULL); // Use other buffer next time. - phase = (phase + 1) % THROWAWAY_BUFFERS; + phase = (phase + 1) % throwaway_buffers; if (++blocks_queued == dwell_blocks) { // Calculate next sweep frequency. diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 12c516c9..be16b1be 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -282,6 +283,16 @@ usb_request_status_t usb_vendor_request_set_antenna_enable( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { + switch (detected_platform()) { + case BOARD_ID_HACKRF1_OG: + case BOARD_ID_HACKRF1_R9: + case BOARD_ID_PRALINE: + // supported + break; + default: + return USB_REQUEST_STATUS_STALL; + } + if (stage == USB_TRANSFER_STAGE_SETUP) { radio_reg_write( &radio, diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index 7c98f0ca..f213f484 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -51,7 +51,8 @@ #define USB_STRING_LANGID (0x0409) -uint8_t usb_descriptor_device[] = { +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +uint8_t usb_descriptor_device_hackrf[] = { 18, // bLength USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType USB_WORD(0x0200), // bcdUSB @@ -60,13 +61,50 @@ uint8_t usb_descriptor_device[] = { 0x00, // bDeviceProtocol USB_MAX_PACKET0, // bMaxPacketSize0 USB_WORD(USB_VENDOR_ID), // idVendor - USB_WORD(USB_PRODUCT_ID), // idProduct + USB_WORD(0x6089), // idProduct USB_WORD(USB_API_VERSION), // bcdDevice 0x01, // iManufacturer 0x02, // iProduct 0x04, // iSerialNumber 0x01 // bNumConfigurations }; +#endif +#if defined(JAWBREAKER) +uint8_t usb_descriptor_device_jawbreaker[] = { + 18, // bLength + USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType + USB_WORD(0x0200), // bcdUSB + 0x00, // bDeviceClass + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + USB_MAX_PACKET0, // bMaxPacketSize0 + USB_WORD(USB_VENDOR_ID), // idVendor + USB_WORD(0x604B), // idProduct + USB_WORD(USB_API_VERSION), // bcdDevice + 0x01, // iManufacturer + 0x02, // iProduct + 0x04, // iSerialNumber + 0x01 // bNumConfigurations +}; +#endif +#if defined(RAD1O) +uint8_t usb_descriptor_device_rad1o[] = { + 18, // bLength + USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType + USB_WORD(0x0200), // bcdUSB + 0x00, // bDeviceClass + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + USB_MAX_PACKET0, // bMaxPacketSize0 + USB_WORD(USB_VENDOR_ID), // idVendor + USB_WORD(0xCC15), // idProduct + USB_WORD(USB_API_VERSION), // bcdDevice + 0x01, // iManufacturer + 0x02, // iProduct + 0x04, // iSerialNumber + 0x01 // bNumConfigurations +}; +#endif uint8_t usb_descriptor_device_qualifier[] = { 10, // bLength @@ -185,8 +223,8 @@ uint8_t usb_descriptor_string_manufacturer[] = { 's', 0x00, }; -uint8_t usb_descriptor_string_product[] = { -#ifdef HACKRF_ONE +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +uint8_t usb_descriptor_string_product_hackrf_one[] = { 22, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType 'H', 0x00, @@ -199,7 +237,26 @@ uint8_t usb_descriptor_string_product[] = { 'O', 0x00, 'n', 0x00, 'e', 0x00, -#elif JAWBREAKER +}; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +uint8_t usb_descriptor_string_product_praline[] = { + 22, // bLength + USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType + 'H', 0x00, + 'a', 0x00, + 'c', 0x00, + 'k', 0x00, + 'R', 0x00, + 'F', 0x00, + ' ', 0x00, + 'P', 0x00, + 'r', 0x00, + 'o', 0x00, +}; +#endif +#if defined(JAWBREAKER) +uint8_t usb_descriptor_string_product_jawbreaker[] = { 36, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType 'H', 0x00, @@ -219,7 +276,10 @@ uint8_t usb_descriptor_string_product[] = { 'k', 0x00, 'e', 0x00, 'r', 0x00, -#elif RAD1O +}; +#endif +#if defined(RAD1O) +uint8_t usb_descriptor_string_product_rad1o[] = { 12, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType 'r', 0x00, @@ -227,30 +287,8 @@ uint8_t usb_descriptor_string_product[] = { 'd', 0x00, '1', 0x00, 'o', 0x00, -#elif PRALINE - 22, // bLength - USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType - 'H', 0x00, - 'a', 0x00, - 'c', 0x00, - 'k', 0x00, - 'R', 0x00, - 'F', 0x00, - ' ', 0x00, - 'P', 0x00, - 'r', 0x00, - 'o', 0x00, -#else - 14, // bLength - USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType - 'H', 0x00, - 'a', 0x00, - 'c', 0x00, - 'k', 0x00, - 'R', 0x00, - 'F', 0x00, -#endif }; +#endif uint8_t usb_descriptor_string_config_description[] = { 24, // bLength @@ -291,14 +329,46 @@ uint8_t usb_descriptor_string_serial_number[] = { uint8_t usb_descriptor_string_serial_number[USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN]; #endif -uint8_t* usb_descriptor_strings[] = { +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +uint8_t* usb_descriptor_strings_hackrf_one[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, - usb_descriptor_string_product, + usb_descriptor_string_product_hackrf_one, usb_descriptor_string_config_description, usb_descriptor_string_serial_number, 0, // TERMINATOR }; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +uint8_t* usb_descriptor_strings_praline[] = { + usb_descriptor_string_languages, + usb_descriptor_string_manufacturer, + usb_descriptor_string_product_praline, + usb_descriptor_string_config_description, + usb_descriptor_string_serial_number, + 0, // TERMINATOR +}; +#endif +#if defined(JAWBREAKER) +uint8_t* usb_descriptor_strings_jawbreaker[] = { + usb_descriptor_string_languages, + usb_descriptor_string_manufacturer, + usb_descriptor_string_product_jawbreaker, + usb_descriptor_string_config_description, + usb_descriptor_string_serial_number, + 0, // TERMINATOR +}; +#endif +#if defined(RAD1O) +uint8_t* usb_descriptor_strings_rad1o[] = { + usb_descriptor_string_languages, + usb_descriptor_string_manufacturer, + usb_descriptor_string_product_rad1o, + usb_descriptor_string_config_description, + usb_descriptor_string_serial_number, + 0, // TERMINATOR +}; +#endif uint8_t wcid_string_descriptor[] = { 18, // bLength diff --git a/firmware/hackrf_usb/usb_descriptor.h b/firmware/hackrf_usb/usb_descriptor.h index 097ccce0..93f3eb78 100644 --- a/firmware/hackrf_usb/usb_descriptor.h +++ b/firmware/hackrf_usb/usb_descriptor.h @@ -24,20 +24,42 @@ #include -extern uint8_t usb_descriptor_device[]; +#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +extern uint8_t usb_descriptor_device_hackrf[]; +#endif +#if defined(JAWBREAKER) +extern uint8_t usb_descriptor_device_jawbreaker[]; +#endif +#if defined(RAD1O) +extern uint8_t usb_descriptor_device_rad1o[]; +#endif extern uint8_t usb_descriptor_device_qualifier[]; extern uint8_t usb_descriptor_configuration_full_speed[]; extern uint8_t usb_descriptor_configuration_high_speed[]; extern uint8_t usb_descriptor_string_languages[]; extern uint8_t usb_descriptor_string_manufacturer[]; -extern uint8_t usb_descriptor_string_product[]; +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +extern uint8_t usb_descriptor_string_product_hackrf_one[]; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +extern uint8_t usb_descriptor_string_product_praline[]; +#endif +#if defined(JAWBREAKER) +extern uint8_t usb_descriptor_string_product_jawbreaker[]; +#endif +#if defined(RAD1O) +extern uint8_t usb_descriptor_string_product_rad1o[]; +#endif #define USB_DESCRIPTOR_STRING_SERIAL_LEN 32 #define USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN \ (USB_DESCRIPTOR_STRING_SERIAL_LEN * 2 + 2) /* UTF-16LE */ extern uint8_t usb_descriptor_string_serial_number[]; -extern uint8_t* usb_descriptor_strings[]; +extern uint8_t* usb_descriptor_strings_hackrf_one[]; +extern uint8_t* usb_descriptor_strings_jawbreaker[]; +extern uint8_t* usb_descriptor_strings_rad1o[]; +extern uint8_t* usb_descriptor_strings_praline[]; #define USB_WCID_VENDOR_REQ 0x19 extern uint8_t wcid_string_descriptor[]; diff --git a/firmware/hackrf_usb/usb_device.c b/firmware/hackrf_usb/usb_device.c index b689ce9f..4ad46424 100644 --- a/firmware/hackrf_usb/usb_device.c +++ b/firmware/hackrf_usb/usb_device.c @@ -45,12 +45,49 @@ usb_configuration_t* usb_configurations[] = { 0, }; -usb_device_t usb_device = { - .descriptor = usb_descriptor_device, - .descriptor_strings = usb_descriptor_strings, +usb_device_t usb_device; + +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +const usb_device_t usb_device_hackrf_one = { + .descriptor = usb_descriptor_device_hackrf, + .descriptor_strings = usb_descriptor_strings_hackrf_one, .qualifier_descriptor = usb_descriptor_device_qualifier, .configurations = &usb_configurations, .configuration = 0, .wcid_string_descriptor = wcid_string_descriptor, .wcid_feature_descriptor = wcid_feature_descriptor, }; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +const usb_device_t usb_device_praline = { + .descriptor = usb_descriptor_device_hackrf, + .descriptor_strings = usb_descriptor_strings_praline, + .qualifier_descriptor = usb_descriptor_device_qualifier, + .configurations = &usb_configurations, + .configuration = 0, + .wcid_string_descriptor = wcid_string_descriptor, + .wcid_feature_descriptor = wcid_feature_descriptor, +}; +#endif +#if defined(JAWBREAKER) +const usb_device_t usb_device_jawbreaker = { + .descriptor = usb_descriptor_device_jawbreaker, + .descriptor_strings = usb_descriptor_strings_jawbreaker, + .qualifier_descriptor = usb_descriptor_device_qualifier, + .configurations = &usb_configurations, + .configuration = 0, + .wcid_string_descriptor = wcid_string_descriptor, + .wcid_feature_descriptor = wcid_feature_descriptor, +}; +#endif +#if defined(RAD1O) +const usb_device_t usb_device_rad1o = { + .descriptor = usb_descriptor_device_rad1o, + .descriptor_strings = usb_descriptor_strings_rad1o, + .qualifier_descriptor = usb_descriptor_device_qualifier, + .configurations = &usb_configurations, + .configuration = 0, + .wcid_string_descriptor = wcid_string_descriptor, + .wcid_feature_descriptor = wcid_feature_descriptor, +}; +#endif diff --git a/firmware/hackrf_usb/usb_device.h b/firmware/hackrf_usb/usb_device.h index 79df298d..528d084d 100644 --- a/firmware/hackrf_usb/usb_device.h +++ b/firmware/hackrf_usb/usb_device.h @@ -26,3 +26,16 @@ #include extern usb_device_t usb_device; + +#if defined(HACKRF_ONE) || defined(UNIVERSAL) +extern const usb_device_t usb_device_hackrf_one; +#endif +#if defined(PRALINE) || defined(UNIVERSAL) +extern const usb_device_t usb_device_praline; +#endif +#if defined(JAWBREAKER) +extern const usb_device_t usb_device_jawbreaker; +#endif +#if defined(RAD1O) +extern const usb_device_t usb_device_rad1o; +#endif From 3faa91ef85336b4f989b8fa1a8fb519bcf34c99a Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 4 Apr 2026 01:13:09 +0100 Subject: [PATCH 047/161] Add UNIVERSAL board target. --- firmware/common/firmware_info.c | 3 +++ firmware/hackrf-common.cmake | 21 +++++++++++++++++---- firmware/hackrf_usb/CMakeLists.txt | 24 ++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/firmware/common/firmware_info.c b/firmware/common/firmware_info.c index 1ac02074..527dd19b 100644 --- a/firmware/common/firmware_info.c +++ b/firmware/common/firmware_info.c @@ -30,6 +30,9 @@ #define SUPPORTED_PLATFORM PLATFORM_RAD1O #elif PRALINE #define SUPPORTED_PLATFORM PLATFORM_PRALINE +#elif UNIVERSAL + #define SUPPORTED_PLATFORM \ + (PLATFORM_PRALINE | PLATFORM_HACKRF1_OG | PLATFORM_HACKRF1_R9) #else #define SUPPORTED_PLATFORM 0 #endif diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 26a24d6e..9a349b1e 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -74,7 +74,7 @@ if(NOT DEFINED BOARD) set(BOARD HACKRF_ONE) endif() -if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE") +if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE" OR BOARD STREQUAL "UNIVERSAL") set(MCU_PARTNO LPC4320) else() set(MCU_PARTNO LPC4330) @@ -195,7 +195,8 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/m0_state.c ${PATH_HACKRF_FIRMWARE_COMMON}/adc.c ${PATH_HACKRF_FIRMWARE_COMMON}/da7219.c - ) + ${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c + ) if(BOARD STREQUAL "RAD1O") SET(SRC_M4 @@ -216,14 +217,26 @@ macro(DeclareTargets) ${SRC_M4} ${PATH_HACKRF_FIRMWARE_COMMON}/fpga.c ${PATH_HACKRF_FIRMWARE_COMMON}/ice40_spi.c - ${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2831.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2831_target.c ) else() SET(SRC_M4 ${SRC_M4} - ${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2837.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2837_target.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2839.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2839_target.c + ) + endif() + + if(BOARD STREQUAL "UNIVERSAL") + SET(SRC_M4 + ${SRC_M4} + ${PATH_HACKRF_FIRMWARE_COMMON}/fpga.c + ${PATH_HACKRF_FIRMWARE_COMMON}/ice40_spi.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2831.c + ${PATH_HACKRF_FIRMWARE_COMMON}/max2831_target.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2837.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2837_target.c ${PATH_HACKRF_FIRMWARE_COMMON}/max2839.c diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index 3ab1beed..3fd3ea87 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -35,7 +35,7 @@ add_custom_command( OUTPUT ${PATH_PRALINE_FPGA_OBJ} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy ${PATH_PRALINE_FPGA_BIN} "fpga.bin" - COMMAND ${CMAKE_OBJCOPY} + COMMAND ${CMAKE_OBJCOPY} -I binary -O elf32-littlearm -B arm @@ -75,6 +75,26 @@ set(SRC_M4 set(SRC_M0 sgpio_m0.s) +if(BOARD STREQUAL "UNIVERSAL") + SET(SRC_M4 + ${SRC_M4} + usb_api_cpld.c + "${PATH_HACKRF_FIRMWARE_COMMON}/cpld_xc2c.c" + "${PATH_HACKRF_CPLD_DATA_C}" + "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/lenval.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/micro.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/ports.c" + + "${PATH_HACKRF_FIRMWARE_COMMON}/lz4_blk.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/fpga_image.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/fpga_selftest.c" + usb_api_praline.c + ) + SET(OBJ_M4 + ${PATH_PRALINE_FPGA_OBJ} + ) +endif() + if(BOARD STREQUAL "PRALINE") SET(SRC_M4 ${SRC_M4} @@ -98,7 +118,7 @@ else() ) endif() -if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE") +if(BOARD STREQUAL "PRALINE" OR BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "UNIVERSAL") SET(SRC_M4 ${SRC_M4} "${PATH_HACKRF_FIRMWARE_COMMON}/portapack.c" From 6387a0b1006a2492a4cb90b95b84756bb736a23e Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 2 Apr 2026 04:48:00 +0100 Subject: [PATCH 048/161] Add UNIVERSAL firmware build to GitHub Actions. --- .github/workflows/build.yml | 2 +- .github/workflows/includes-check.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07b419e5..a93a27c1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -159,7 +159,7 @@ jobs: strategy: matrix: os: ['macos', 'ubuntu', 'windows'] - board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE'] + board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE', 'UNIVERSAL'] cmake: ['3.10.0', 'latest'] exclude: - os: 'windows' diff --git a/.github/workflows/includes-check.yml b/.github/workflows/includes-check.yml index dfb631bd..11640069 100644 --- a/.github/workflows/includes-check.yml +++ b/.github/workflows/includes-check.yml @@ -13,7 +13,7 @@ jobs: options: --user 0 strategy: matrix: - board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE'] + board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE', 'UNIVERSAL'] cmake: ['3.10.0', 'latest'] # Don't cancel all builds when one fails From 421af497447a9e2632f4ca7dad88229af1da6300 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 2 Apr 2026 07:45:08 +0100 Subject: [PATCH 049/161] Jenkinsfile: add tests with BOARD=UNIVERSAL. --- Jenkinsfile | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index bf79d51e..b41d6714 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -67,6 +67,46 @@ pipeline { } } } + stage('Test HackRF One with BOARD=UNIVERSAL') { + agent { + docker { + image 'hackrf' + reuseNode true + args docker_args + } + } + options { + timeout(time: 20, unit: 'MINUTES') + } + steps { + sh './ci-scripts/install_host.sh' + sh './ci-scripts/build_firmware.sh UNIVERSAL' + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' + retry(3) { + sh './ci-scripts/test_host.sh' + } + script { + reset('h1_tester h1_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) + } + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' + retry(3) { + sh 'python3 ci-scripts/test_sgpio_debug.py' + } + } + } stage('Test HackRF Pro with BOARD=PRALINE') { agent { docker { @@ -99,6 +139,38 @@ pipeline { } } } + stage('Test HackRF Pro with BOARD=UNIVERSAL') { + agent { + docker { + image 'hackrf' + reuseNode true + args "$docker_args" + } + } + options { + timeout(time: 20, unit: 'MINUTES') + } + steps { + sh './ci-scripts/install_host.sh' + sh './ci-scripts/build_firmware.sh UNIVERSAL' + script { + allOff() + reset('hpro_eut') + } + sh 'sleep 1s' + retry(3) { + sh './ci-scripts/test_host.sh' + } + script { + reset('hpro_tester hpro_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) + } + } + } } post { always { From 9b5f342be024fce5ab0c421116af720a9ccf44c4 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 6 Apr 2026 21:21:01 +0100 Subject: [PATCH 050/161] max283x: Move platform switch inside max283x_setup(). --- firmware/common/max283x.c | 51 +++++++++++++++++---------------------- firmware/common/max283x.h | 2 +- firmware/common/rf_path.c | 18 +------------- 3 files changed, 24 insertions(+), 47 deletions(-) diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 54b596bc..9c64d668 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -27,6 +27,7 @@ #include #include "fixed_point.h" +#include "platform_detect.h" #include "platform_gpio.h" #include "spi_bus.h" @@ -67,49 +68,41 @@ max2839_driver_t max2839 = { #endif /* Initialize chip. */ -void max283x_setup(max283x_driver_t* const drv, max283x_variant_t type) +void max283x_setup(max283x_driver_t* const drv) { - drv->type = type; - const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ + switch (detected_platform()) { + case BOARD_ID_PRALINE: #if defined(PRALINE) || defined(UNIVERSAL) - max2831.gpio_enable = gpio->max283x_enable; - max2831.gpio_rxtx = gpio->max283x_rx_enable; - max2831.gpio_rxhp = gpio->max2831_rxhp; - max2831.gpio_ld = gpio->max2831_ld; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - max2837.gpio_enable = gpio->max283x_enable; - max2837.gpio_rx_enable = gpio->max283x_rx_enable; - max2837.gpio_tx_enable = gpio->max283x_tx_enable; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - max2839.gpio_enable = gpio->max283x_enable; - max2839.gpio_rxtx = gpio->max283x_rx_enable; -#endif - - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: + drv->type = MAX2831_VARIANT; + max2831.gpio_enable = gpio->max283x_enable; + max2831.gpio_rxtx = gpio->max283x_rx_enable; + max2831.gpio_rxhp = gpio->max2831_rxhp; + max2831.gpio_ld = gpio->max2831_ld; memcpy(&drv->drv.max2831, &max2831, sizeof(max2831)); max2831_setup(&drv->drv.max2831); - break; #endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); - max2837_setup(&drv->drv.max2837); break; -#endif + case BOARD_ID_HACKRF1_R9: #if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: + drv->type = MAX2839_VARIANT; + max2839.gpio_enable = gpio->max283x_enable; + max2839.gpio_rxtx = gpio->max283x_rx_enable; memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); max2839_setup(&drv->drv.max2839); - break; #endif + break; default: +#if !defined(PRALINE) || defined(UNIVERSAL) + drv->type = MAX2837_VARIANT; + max2837.gpio_enable = gpio->max283x_enable; + max2837.gpio_rx_enable = gpio->max283x_rx_enable; + max2837.gpio_tx_enable = gpio->max283x_tx_enable; + memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); + max2837_setup(&drv->drv.max2837); +#endif break; } } diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index 87da59b4..bcafff06 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -83,7 +83,7 @@ typedef struct { } max283x_driver_t; /* Initialize chip. */ -void max283x_setup(max283x_driver_t* const drv, max283x_variant_t type); +void max283x_setup(max283x_driver_t* const drv); /* Returns the number of registers supported by the driver. */ uint16_t max283x_num_regs(max283x_driver_t* const drv); diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 5e892668..f2234413 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -480,23 +480,7 @@ void rf_path_init(rf_path_t* const rf_path) max5864_shutdown(&max5864); ssp1_set_mode_max283x(); - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) - max283x_setup(&max283x, MAX2831_VARIANT); -#endif - break; - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - max283x_setup(&max283x, MAX2839_VARIANT); -#endif - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) - max283x_setup(&max283x, MAX2837_VARIANT); -#endif - break; - } + max283x_setup(&max283x); max283x_start(&max283x); switch (board_id) { From d4b2091b128d7b413e3e334fabcea89e5a39d08e Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 6 Apr 2026 22:58:34 +0100 Subject: [PATCH 051/161] max283x: Add macros to simplify variant-specific dispatch. --- firmware/common/max283x.c | 442 +++++++++----------------------------- 1 file changed, 100 insertions(+), 342 deletions(-) diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 9c64d668..6a6d5f26 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -107,93 +107,95 @@ void max283x_setup(max283x_driver_t* const drv) } } +/* Macros to simplify dispatch of variant-specific operations. */ + +/* clang-format off */ + +#if defined(UNIVERSAL) + // UNIVERSAL: all variants + #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ + if (_drv->type == MAX2831_VARIANT) { \ + _max2831; \ + } else if (_drv->type == MAX2837_VARIANT) { \ + _max2837; \ + } else { \ + _max2839; \ + } +#elif defined(HACKRF_ONE) + // HACKRF_ONE: MAX2837 and MAX2839 only + #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ + if (_drv->type == MAX2837_VARIANT) { \ + _max2837; \ + } else { \ + _max2839; \ + } +#elif defined(PRALINE) + // PRALINE: MAX2831 only + #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ + (void) drv; \ + _max2831 +#else + // JAWBREAKER, RAD1O: MAX2837 only + #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ + (void) drv; \ + _max2837 +#endif + +#define CALL(_drv, _func, ...) \ + DISPATCH(drv, \ + max2831_ ## _func(&_drv->drv.max2831, ##__VA_ARGS__), \ + max2837_ ## _func(&_drv->drv.max2837, ##__VA_ARGS__), \ + max2839_ ## _func(&_drv->drv.max2839, ##__VA_ARGS__) \ + ); \ + +#define RESULT(_drv, _type, _func, ...) ({ \ + _type _result; \ + DISPATCH(drv, \ + _result = max2831_ ## _func(&_drv->drv.max2831, ##__VA_ARGS__), \ + _result = max2837_ ## _func(&_drv->drv.max2837, ##__VA_ARGS__), \ + _result = max2839_ ## _func(&_drv->drv.max2839, ##__VA_ARGS__) \ + ); \ + _result; \ +}) + +#define CONSTANT(_drv, _type, _name) ({\ + _type value; \ + DISPATCH(drv, \ + value = MAX2831_ ## _name, \ + value = MAX2837_ ## _name, \ + value = MAX2839_ ## _name \ + ); \ + value; \ +}) + +#define PRALINE_ONLY(_drv, _max2831) DISPATCH(_drv, _max2831, , ) + +/* clang-format on */ + /* Returns the number of registers supported by the driver. */ uint16_t max283x_num_regs(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return MAX2831_NUM_REGS; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return MAX2837_NUM_REGS; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return MAX2839_NUM_REGS; -#endif - default: - return 0; - } + return CONSTANT(drv, uint16_t, NUM_REGS); } /* Returns the maximum data register value supported by the driver. */ uint16_t max283x_data_regs_max_value(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return MAX2831_DATA_REGS_MAX_VALUE; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return MAX2837_DATA_REGS_MAX_VALUE; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return MAX2839_DATA_REGS_MAX_VALUE; -#endif - default: - return 0; - } + return CONSTANT(drv, uint16_t, DATA_REGS_MAX_VALUE); } /* Read a register via SPI. Save a copy to memory and return * value. Mark clean. */ uint16_t max283x_reg_read(max283x_driver_t* const drv, uint8_t r) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_reg_read(&drv->drv.max2831, r); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_reg_read(&drv->drv.max2837, r); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_reg_read(&drv->drv.max2839, r); -#endif - default: - return 0; - } + return RESULT(drv, uint16_t, reg_read, r); } /* Write value to register via SPI and save a copy to memory. Mark * clean. */ void max283x_reg_write(max283x_driver_t* const drv, uint8_t r, uint16_t v) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_reg_write(&drv->drv.max2831, r, v); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_reg_write(&drv->drv.max2837, r, v); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_reg_write(&drv->drv.max2839, r, v); - break; -#endif - default: - break; - } + CALL(drv, reg_write, r, v); } /* Write all dirty registers via SPI from memory. Mark all clean. Some @@ -201,115 +203,36 @@ void max283x_reg_write(max283x_driver_t* const drv, uint8_t r, uint16_t v) * provided routines for those operations. */ void max283x_regs_commit(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_regs_commit(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_regs_commit(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_regs_commit(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, regs_commit); } void max283x_set_mode(max283x_driver_t* const drv, const max283x_mode_t new_mode) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_set_mode(&drv->drv.max2831, (max2831_mode_t) new_mode); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_set_mode(&drv->drv.max2837, (max2837_mode_t) new_mode); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_set_mode(&drv->drv.max2839, (max2839_mode_t) new_mode); - break; -#endif - default: - break; - } + DISPATCH( + drv, + max2831_set_mode(&drv->drv.max2831, (max2831_mode_t) new_mode), + max2837_set_mode(&drv->drv.max2837, (max2837_mode_t) new_mode), + max2839_set_mode(&drv->drv.max2839, (max2839_mode_t) new_mode)); } max283x_mode_t max283x_mode(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return (max283x_mode_t) max2831_mode(&drv->drv.max2831); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return (max283x_mode_t) max2837_mode(&drv->drv.max2837); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return (max283x_mode_t) max2839_mode(&drv->drv.max2839); -#endif - default: - return 0; - } + DISPATCH( + drv, + return (max283x_mode_t) max2831_mode(&drv->drv.max2831), + return (max283x_mode_t) max2837_mode(&drv->drv.max2837), + return (max283x_mode_t) max2839_mode(&drv->drv.max2839)); } /* Turn on/off all chip functions. Does not control oscillator and CLKOUT */ void max283x_start(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_start(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_start(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_start(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, start); } void max283x_stop(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_stop(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_stop(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_stop(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, stop); } /* Set frequency in 1/(2**24) Hz. */ @@ -318,26 +241,7 @@ fp_40_24_t max283x_set_frequency( fp_40_24_t freq, bool program) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_set_frequency(&drv->drv.max2831, freq, program); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_frequency(&drv->drv.max2837, freq, program); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_frequency(&drv->drv.max2839, freq, program); - break; -#endif - default: - break; - } - return 0; + return RESULT(drv, fp_40_24_t, set_frequency, freq, program); } uint32_t max283x_set_lpf_bandwidth( @@ -345,135 +249,42 @@ uint32_t max283x_set_lpf_bandwidth( const max283x_mode_t mode, const uint32_t bandwidth_hz) { -#ifndef PRALINE +#if !defined(PRALINE) (void) mode; #endif - - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: + DISPATCH( + drv, return max2831_set_lpf_bandwidth( &drv->drv.max2831, (max2831_mode_t) mode, - bandwidth_hz); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_lpf_bandwidth(&drv->drv.max2837, bandwidth_hz); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_lpf_bandwidth(&drv->drv.max2839, bandwidth_hz); -#endif - default: - return 0; - } + bandwidth_hz), + return max2837_set_lpf_bandwidth(&drv->drv.max2837, bandwidth_hz), + return max2839_set_lpf_bandwidth(&drv->drv.max2839, bandwidth_hz)); } bool max283x_set_lna_gain(max283x_driver_t* const drv, const uint32_t gain_db) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_set_lna_gain(&drv->drv.max2831, gain_db); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_lna_gain(&drv->drv.max2837, gain_db); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_lna_gain(&drv->drv.max2839, gain_db); -#endif - default: - return false; - } + return RESULT(drv, bool, set_lna_gain, gain_db); } bool max283x_set_vga_gain(max283x_driver_t* const drv, const uint32_t gain_db) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_set_vga_gain(&drv->drv.max2831, gain_db); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_vga_gain(&drv->drv.max2837, gain_db); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_vga_gain(&drv->drv.max2839, gain_db); -#endif - default: - return false; - } + return RESULT(drv, bool, set_vga_gain, gain_db); } bool max283x_set_txvga_gain(max283x_driver_t* const drv, const uint32_t gain_db) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - return max2831_set_txvga_gain(&drv->drv.max2831, gain_db); -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - return max2837_set_txvga_gain(&drv->drv.max2837, gain_db); -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - return max2839_set_txvga_gain(&drv->drv.max2839, gain_db); -#endif - default: - return false; - } + return RESULT(drv, bool, set_txvga_gain, gain_db); } void max283x_tx(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_tx(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_tx(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_tx(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, tx); } void max283x_rx(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_rx(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - max2837_rx(&drv->drv.max2837); - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - max2839_rx(&drv->drv.max2839); - break; -#endif - default: - break; - } + CALL(drv, rx); } /* Set MAX2831 receiver high-pass filter corner frequency in Hz */ @@ -481,75 +292,22 @@ void max283x_set_rx_hpf_frequency( max283x_driver_t* const drv, const max283x_rx_hpf_freq_t freq) { -#if !defined(PRALINE) +#if !defined(PRALINE) && !defined(UNIVERSAL) (void) freq; #endif - - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: + PRALINE_ONLY( + drv, max2831_set_rx_hpf_frequency( &drv->drv.max2831, - (max2831_rx_hpf_freq_t) freq); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - // unsupported - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - // unsupported - break; -#endif - default: - break; - } + (max2831_rx_hpf_freq_t) freq)); } void max283x_tx_calibration(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_tx_calibration(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - // unsupported - use max283x_set_mode instead - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - // unsupported - use max283x_set_mode instead - break; -#endif - default: - break; - } + PRALINE_ONLY(drv, max2831_tx_calibration(&drv->drv.max2831)); } void max283x_rx_calibration(max283x_driver_t* const drv) { - switch (drv->type) { -#if defined(PRALINE) || defined(UNIVERSAL) - case MAX2831_VARIANT: - max2831_rx_calibration(&drv->drv.max2831); - break; -#endif -#if !defined(PRALINE) || defined(UNIVERSAL) - case MAX2837_VARIANT: - // unsupported - use max283x_set_mode instead - break; -#endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - case MAX2839_VARIANT: - // unsupported - use max283x_set_mode instead - break; -#endif - default: - break; - } + PRALINE_ONLY(drv, max2831_rx_calibration(&drv->drv.max2831)); } From 439f6fd3554dda31782fb3db66dd6f061cd08ece Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 15 Apr 2026 02:05:24 +0100 Subject: [PATCH 052/161] firmware: Use macros to simplify platform-specific code. --- firmware/common/cpld_jtag.c | 64 +-- firmware/common/hackrf_core.c | 549 +++++++++-------------- firmware/common/hackrf_ui.c | 18 +- firmware/common/max283x.c | 44 +- firmware/common/mixer.c | 121 ++--- firmware/common/operacake_sctimer.c | 11 +- firmware/common/platform_detect.h | 111 +++++ firmware/common/platform_gpio.c | 235 +++------- firmware/common/platform_scu.c | 300 ++++--------- firmware/common/radio.c | 116 ++--- firmware/common/rf_path.c | 90 ++-- firmware/common/rffc5071.c | 37 +- firmware/common/sgpio.c | 15 +- firmware/common/si5351c.c | 48 +- firmware/common/tuning.c | 13 +- firmware/hackrf_usb/hackrf_usb.c | 118 ++--- firmware/hackrf_usb/usb_api_board_info.c | 6 +- firmware/hackrf_usb/usb_api_register.c | 138 +++--- firmware/hackrf_usb/usb_api_selftest.c | 37 +- 19 files changed, 822 insertions(+), 1249 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 21991db0..58359051 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -39,83 +39,55 @@ void cpld_jtag_take(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; - board_id_t board_id = detected_platform(); - /* Set initial GPIO state to the voltages of the internal or external pull-ups/downs, * to avoid any glitches. */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( gpio_set(gpio->gpio_pp_tms); -#endif - break; - default: - break; - } + ) + gpio_clear(gpio->gpio_tck); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + + IF_NOT_PRALINE ( gpio_set(gpio->gpio_tms); gpio_set(gpio->gpio_tdi); -#endif - } + ) - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( /* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */ gpio_input(gpio->gpio_pp_tms); gpio_input(gpio->gpio_pp_tdo); -#endif - break; - default: - break; - } + ) + gpio_output(gpio->gpio_tck); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + + IF_NOT_PRALINE ( gpio_output(gpio->gpio_tms); gpio_output(gpio->gpio_tdi); gpio_input(gpio->gpio_tdo); -#endif - } + ) } void cpld_jtag_release(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; - board_id_t board_id = detected_platform(); - /* Make all pins inputs when JTAG interface not active. * Let the pull-ups/downs do the work. */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( /* Do not drive PortaPack-specific pins, initially, just to be cautious. */ gpio_input(gpio->gpio_pp_tms); gpio_input(gpio->gpio_pp_tdo); -#endif - break; - default: - break; - } + ) + gpio_input(gpio->gpio_tck); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + + IF_NOT_PRALINE ( gpio_input(gpio->gpio_tms); gpio_input(gpio->gpio_tdi); gpio_input(gpio->gpio_tdo); -#endif - } + ) } #if !defined(PRALINE) || defined(UNIVERSAL) diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index c8b66443..689a2a30 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -341,8 +341,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) sgpio_cpld_stream_disable(&sgpio_config); } - if (detected_platform() != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( /* Integer mode can be enabled if p1 is even and p2 is zero. */ if (p1 & 0x1 || p2) { si5351c_set_int_mode(&clock_gen, 0, 0); @@ -350,13 +349,14 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) si5351c_set_int_mode(&clock_gen, 0, 1); } - if (detected_platform() == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( /* * On HackRF One r9 all sample clocks are externally derived * from MS1/CLK1 operating at twice the sample rate. */ si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); - } else { + ) + IF_NOT_H1_R9 ( /* * On other platforms the clock generator produces three * different sample clocks, all derived from multisynth 0. @@ -381,10 +381,9 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) 0, 0, 0); //p1 doesn't matter - } -#endif - } else { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + ) + IF_PRALINE ( /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); @@ -410,8 +409,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) /* Reset PLL to synchronize output clock phase. */ si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); -#endif - } + ) if (streaming) { sgpio_cpld_stream_enable(&sgpio_config); @@ -563,73 +561,75 @@ void cpu_clock_init(void) CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); -#if !defined(RAD1O) - /* Disable unused clocks */ - /* Start with PLLs */ - CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); + IF_NOT_RAD1O ( + /* Disable unused clocks */ + /* Start with PLLs */ + CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); - /* Dividers */ - CGU_IDIVA_CTRL = CGU_IDIVA_CTRL_PD(1); - CGU_IDIVB_CTRL = CGU_IDIVB_CTRL_PD(1); - CGU_IDIVC_CTRL = CGU_IDIVC_CTRL_PD(1); - CGU_IDIVD_CTRL = CGU_IDIVD_CTRL_PD(1); - CGU_IDIVE_CTRL = CGU_IDIVE_CTRL_PD(1); + /* Dividers */ + CGU_IDIVA_CTRL = CGU_IDIVA_CTRL_PD(1); + CGU_IDIVB_CTRL = CGU_IDIVB_CTRL_PD(1); + CGU_IDIVC_CTRL = CGU_IDIVC_CTRL_PD(1); + CGU_IDIVD_CTRL = CGU_IDIVD_CTRL_PD(1); + CGU_IDIVE_CTRL = CGU_IDIVE_CTRL_PD(1); - /* Base clocks */ - CGU_BASE_SPIFI_CLK = CGU_BASE_SPIFI_CLK_PD(1); /* SPIFI is only used at boot */ - CGU_BASE_USB1_CLK = CGU_BASE_USB1_CLK_PD(1); /* USB1 is not exposed on HackRF */ - CGU_BASE_PHY_RX_CLK = CGU_BASE_PHY_RX_CLK_PD(1); - CGU_BASE_PHY_TX_CLK = CGU_BASE_PHY_TX_CLK_PD(1); - CGU_BASE_LCD_CLK = CGU_BASE_LCD_CLK_PD(1); - CGU_BASE_VADC_CLK = CGU_BASE_VADC_CLK_PD(1); - CGU_BASE_SDIO_CLK = CGU_BASE_SDIO_CLK_PD(1); - CGU_BASE_UART0_CLK = CGU_BASE_UART0_CLK_PD(1); - CGU_BASE_UART1_CLK = CGU_BASE_UART1_CLK_PD(1); - CGU_BASE_UART2_CLK = CGU_BASE_UART2_CLK_PD(1); - CGU_BASE_UART3_CLK = CGU_BASE_UART3_CLK_PD(1); - CGU_BASE_OUT_CLK = CGU_BASE_OUT_CLK_PD(1); - CGU_BASE_AUDIO_CLK = CGU_BASE_AUDIO_CLK_PD(1); - CGU_BASE_CGU_OUT0_CLK = CGU_BASE_CGU_OUT0_CLK_PD(1); - CGU_BASE_CGU_OUT1_CLK = CGU_BASE_CGU_OUT1_CLK_PD(1); + /* Base clocks */ + CGU_BASE_SPIFI_CLK = + CGU_BASE_SPIFI_CLK_PD(1); /* SPIFI is only used at boot */ + CGU_BASE_USB1_CLK = + CGU_BASE_USB1_CLK_PD(1); /* USB1 is not exposed on HackRF */ + CGU_BASE_PHY_RX_CLK = CGU_BASE_PHY_RX_CLK_PD(1); + CGU_BASE_PHY_TX_CLK = CGU_BASE_PHY_TX_CLK_PD(1); + CGU_BASE_LCD_CLK = CGU_BASE_LCD_CLK_PD(1); + CGU_BASE_VADC_CLK = CGU_BASE_VADC_CLK_PD(1); + CGU_BASE_SDIO_CLK = CGU_BASE_SDIO_CLK_PD(1); + CGU_BASE_UART0_CLK = CGU_BASE_UART0_CLK_PD(1); + CGU_BASE_UART1_CLK = CGU_BASE_UART1_CLK_PD(1); + CGU_BASE_UART2_CLK = CGU_BASE_UART2_CLK_PD(1); + CGU_BASE_UART3_CLK = CGU_BASE_UART3_CLK_PD(1); + CGU_BASE_OUT_CLK = CGU_BASE_OUT_CLK_PD(1); + CGU_BASE_AUDIO_CLK = CGU_BASE_AUDIO_CLK_PD(1); + CGU_BASE_CGU_OUT0_CLK = CGU_BASE_CGU_OUT0_CLK_PD(1); + CGU_BASE_CGU_OUT1_CLK = CGU_BASE_CGU_OUT1_CLK_PD(1); - /* Disable unused peripheral clocks */ - CCU1_CLK_APB1_CAN1_CFG = 0; - CCU1_CLK_APB1_I2S_CFG = 0; - CCU1_CLK_APB1_MOTOCONPWM_CFG = 0; - //CCU1_CLK_APB3_ADC0_CFG = 0; - CCU1_CLK_APB3_ADC1_CFG = 0; - CCU1_CLK_APB3_CAN0_CFG = 0; - CCU1_CLK_APB3_DAC_CFG = 0; - //CCU1_CLK_M4_DMA_CFG = 0; - CCU1_CLK_M4_EMC_CFG = 0; - CCU1_CLK_M4_EMCDIV_CFG = 0; - CCU1_CLK_M4_ETHERNET_CFG = 0; - CCU1_CLK_M4_LCD_CFG = 0; - CCU1_CLK_M4_QEI_CFG = 0; - CCU1_CLK_M4_RITIMER_CFG = 0; - // CCU1_CLK_M4_SCT_CFG = 0; - CCU1_CLK_M4_SDIO_CFG = 0; - CCU1_CLK_M4_SPIFI_CFG = 0; - CCU1_CLK_M4_TIMER0_CFG = 0; - //CCU1_CLK_M4_TIMER1_CFG = 0; - //CCU1_CLK_M4_TIMER2_CFG = 0; - CCU1_CLK_M4_TIMER3_CFG = 0; - CCU1_CLK_M4_UART1_CFG = 0; - CCU1_CLK_M4_USART0_CFG = 0; - CCU1_CLK_M4_USART2_CFG = 0; - CCU1_CLK_M4_USART3_CFG = 0; - CCU1_CLK_M4_USB1_CFG = 0; - CCU1_CLK_M4_VADC_CFG = 0; - // CCU1_CLK_SPIFI_CFG = 0; - // CCU1_CLK_USB1_CFG = 0; - // CCU1_CLK_VADC_CFG = 0; - // CCU2_CLK_APB0_UART1_CFG = 0; - // CCU2_CLK_APB0_USART0_CFG = 0; - // CCU2_CLK_APB2_USART2_CFG = 0; - // CCU2_CLK_APB2_USART3_CFG = 0; - // CCU2_CLK_APLL_CFG = 0; - // CCU2_CLK_SDIO_CFG = 0; -#endif + /* Disable unused peripheral clocks */ + CCU1_CLK_APB1_CAN1_CFG = 0; + CCU1_CLK_APB1_I2S_CFG = 0; + CCU1_CLK_APB1_MOTOCONPWM_CFG = 0; + //CCU1_CLK_APB3_ADC0_CFG = 0; + CCU1_CLK_APB3_ADC1_CFG = 0; + CCU1_CLK_APB3_CAN0_CFG = 0; + CCU1_CLK_APB3_DAC_CFG = 0; + //CCU1_CLK_M4_DMA_CFG = 0; + CCU1_CLK_M4_EMC_CFG = 0; + CCU1_CLK_M4_EMCDIV_CFG = 0; + CCU1_CLK_M4_ETHERNET_CFG = 0; + CCU1_CLK_M4_LCD_CFG = 0; + CCU1_CLK_M4_QEI_CFG = 0; + CCU1_CLK_M4_RITIMER_CFG = 0; + // CCU1_CLK_M4_SCT_CFG = 0; + CCU1_CLK_M4_SDIO_CFG = 0; + CCU1_CLK_M4_SPIFI_CFG = 0; + CCU1_CLK_M4_TIMER0_CFG = 0; + //CCU1_CLK_M4_TIMER1_CFG = 0; + //CCU1_CLK_M4_TIMER2_CFG = 0; + CCU1_CLK_M4_TIMER3_CFG = 0; + CCU1_CLK_M4_UART1_CFG = 0; + CCU1_CLK_M4_USART0_CFG = 0; + CCU1_CLK_M4_USART2_CFG = 0; + CCU1_CLK_M4_USART3_CFG = 0; + CCU1_CLK_M4_USB1_CFG = 0; + CCU1_CLK_M4_VADC_CFG = 0; + // CCU1_CLK_SPIFI_CFG = 0; + // CCU1_CLK_USB1_CFG = 0; + // CCU1_CLK_VADC_CFG = 0; + // CCU2_CLK_APB0_UART1_CFG = 0; + // CCU2_CLK_APB0_USART0_CFG = 0; + // CCU2_CLK_APB2_USART2_CFG = 0; + // CCU2_CLK_APB2_USART3_CFG = 0; + // CCU2_CLK_APLL_CFG = 0; + // CCU2_CLK_SDIO_CFG = 0; + ) } void clock_gen_init(void) @@ -670,7 +670,7 @@ void clock_gen_init(void) * CLK7 -> AUX_CLK2 */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( /* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */ si5351c_configure_multisynth( &clock_gen, @@ -679,7 +679,8 @@ void clock_gen_init(void) 0, 1, 0); /* 800/20 = 40MHz */ - } else { + ) + IF_NOT_H1_R9 ( /* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */ si5351c_configure_multisynth( &clock_gen, @@ -696,7 +697,7 @@ void clock_gen_init(void) 0, 1, 0); /* 800/20 = 40MHz */ - } + ) /* MS6/CLK6 is unused. */ /* MS7/CLK7 is unused. */ @@ -721,20 +722,12 @@ void clock_gen_shutdown(void) clock_source_t activate_best_clock_source(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(false); } -#endif - break; - default: - break; - } + ) clock_source_t source = CLOCK_SOURCE_HACKRF; @@ -742,11 +735,7 @@ clock_source_t activate_best_clock_source(void) if (si5351c_clkin_signal_valid(&clock_gen)) { source = CLOCK_SOURCE_EXTERNAL; } else { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(true); @@ -757,11 +746,7 @@ clock_source_t activate_best_clock_source(void) portapack_reference_oscillator(false); } } -#endif - break; - default: - break; - } + ) /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ } @@ -796,7 +781,6 @@ void pin_shutdown(void) gpio_init(); /* Detect Platform */ - board_id_t board_id = detected_platform(); const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -814,99 +798,69 @@ void pin_shutdown(void) * * LPC43xx pull-up and pull-down resistors are approximately 53K. */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); -#endif - break; - default: - break; - } + ) scu_pinmux(scu->PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); -#endif - } + ) /* Configure SCU Pin Mux as GPIO */ scu_pinmux(scu->PINMUX_LED1, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED2, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED3, SCU_GPIO_NOPULL); - switch (board_id) { - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); -#endif - break; - default: - break; - } + ) /* Configure USB indicators */ - if (board_id == BOARD_ID_JAWBREAKER) { -#if defined(JAWBREAKER) + IF_JAWBREAKER ( scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3); scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3); -#endif - } + ) - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( disable_1v2_power(); disable_3v3aux_power(); gpio_output(gpio->gpio_1v2_enable); gpio_output(gpio->gpio_3v3aux_enable_n); scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#endif - break; - default: + ) + + IF_NOT_PRALINE ( disable_1v8_power(); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( gpio_output(gpio->h1r9_1v8_enable); scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#endif - } else { + ) + IF_NOT_H1_R9 ( gpio_output(gpio->gpio_1v8_enable); scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } - break; - } + ) + ) - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_H1_OR_PRALINE ( /* Safe state: start with VAA turned off: */ disable_rf_power(); /* Configure RF power supply (VAA) switch control signal as output */ - if (board_id == BOARD_ID_HACKRF1_R9) { - #if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( gpio_output(gpio->h1r9_vaa_disable); - #endif - } else { + ) + IF_NOT_H1_R9 ( gpio_output(gpio->vaa_disable); - } - break; -#endif - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + ) + + IF_RAD1O ( /* Safe state: start with VAA turned off: */ disable_rf_power(); @@ -919,15 +873,9 @@ void pin_shutdown(void) scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); -#endif - break; - default: - break; - } + ) - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG); scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG); scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG); @@ -962,11 +910,7 @@ void pin_shutdown(void) gpio_clear(gpio->fpga_cfg_creset); gpio_output(gpio->fpga_cfg_creset); gpio_input(gpio->fpga_cfg_cdone); -#endif - break; - default: - break; - } + ) /* enable input on SCL and SDA pins */ SCU_SFSI2C0 = SCU_I2C0_NOMINAL; @@ -976,7 +920,6 @@ void pin_shutdown(void) void pin_setup(void) { /* Detect Platform */ - board_id_t board_id = detected_platform(); const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -984,34 +927,26 @@ void pin_setup(void) led_off(0); led_off(1); led_off(2); - switch (board_id) { - case BOARD_ID_RAD1O: + IF_FOUR_LEDS ( led_off(3); - default: - break; - } + ) gpio_output(gpio->led[0]); gpio_output(gpio->led[1]); gpio_output(gpio->led[2]); - switch (board_id) { - case BOARD_ID_RAD1O: - case BOARD_ID_PRALINE: -#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) + + IF_FOUR_LEDS ( gpio_output(gpio->led[3]); -#endif - break; - default: - break; - } + ) /* Configure drivers and driver pins */ ssp_config_max283x.gpio_select = gpio->max283x_select; - if (board_id == BOARD_ID_PRALINE) { - ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words - } else { + IF_NOT_PRALINE ( ssp_config_max283x.data_bits = SSP_DATA_16BITS; - } + ) + IF_PRALINE ( + ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words + ) ssp_config_max5864.gpio_select = gpio->max5864_select; @@ -1020,62 +955,41 @@ void pin_setup(void) spi_flash.gpio_wp = gpio->w25q80bv_wp; sgpio_config.gpio_q_invert = gpio->q_invert; - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) - sgpio_config.gpio_trigger_enable = gpio->trigger_enable; -#endif - } -#if defined(PRALINE) || defined(UNIVERSAL) - ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; - ice40.gpio_select = gpio->fpga_cfg_spi_cs; - ice40.gpio_creset = gpio->fpga_cfg_creset; - ice40.gpio_cdone = gpio->fpga_cfg_cdone; -#endif + IF_NOT_PRALINE ( + sgpio_config.gpio_trigger_enable = gpio->trigger_enable; + ) + + IF_PRALINE ( + ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; + ice40.gpio_select = gpio->fpga_cfg_spi_cs; + ice40.gpio_creset = gpio->fpga_cfg_creset; + ice40.gpio_cdone = gpio->fpga_cfg_cdone; + ) jtag_gpio_cpld.gpio_tck = gpio->cpld_tck; -#if !defined(PRALINE) || defined(UNIVERSAL) - jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; - jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; - jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; -#endif - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + + IF_NOT_PRALINE ( jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; -#endif - } - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + ) + + IF_EXPANSION_COMPATIBLE ( jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; -#endif - break; - default: - break; - } + ) ssp1_set_mode_max283x(); mixer_bus_setup(&mixer); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( sgpio_config.gpio_trigger_enable = gpio->h1r9_trigger_enable; -#endif - } + ) // initialize rf_path struct and assign gpio's - switch (board_id) { - case BOARD_ID_JAWBREAKER: - break; - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( rf_path = (rf_path_t){ .switchctrl = 0, .gpio_hp = gpio->hp, @@ -1092,14 +1006,13 @@ void pin_setup(void) .gpio_rx_amp = gpio->rx_amp, .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, }; - if (board_id == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( rf_path.gpio_rx = gpio->h1r9_rx; rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; - } -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + ) + + IF_RAD1O ( rf_path = (rf_path_t){ .switchctrl = 0, .gpio_tx_rx_n = gpio->tx_rx_n, @@ -1114,10 +1027,9 @@ void pin_setup(void) .gpio_tx_amp = gpio->tx_amp, .gpio_rx_lna = gpio->rx_lna, }; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + + IF_PRALINE ( rf_path = (rf_path_t){ .switchctrl = 0, .gpio_tx_en = gpio->tx_en, @@ -1130,11 +1042,8 @@ void pin_setup(void) (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; } -#endif - break; - default: - break; - } + ) + rf_path_pin_setup(&rf_path); /* Configure external clock in */ @@ -1146,79 +1055,55 @@ void pin_setup(void) #if defined(PRALINE) || defined(UNIVERSAL) void enable_1v2_power(void) { - switch (detected_platform()) { - case BOARD_ID_PRALINE: + IF_PRALINE ( gpio_set(platform_gpio()->gpio_1v2_enable); - break; - default: - break; - } + ) } void disable_1v2_power(void) { - switch (detected_platform()) { - case BOARD_ID_PRALINE: + IF_PRALINE ( gpio_clear(platform_gpio()->gpio_1v2_enable); - break; - default: - break; - } + ) } void enable_3v3aux_power(void) { - switch (detected_platform()) { - case BOARD_ID_PRALINE: + IF_PRALINE ( gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); - break; - default: - break; - } + ) } void disable_3v3aux_power(void) { - switch (detected_platform()) { - case BOARD_ID_PRALINE: + IF_PRALINE ( gpio_set(platform_gpio()->gpio_3v3aux_enable_n); - break; - default: - break; - } + ) } #endif void enable_1v8_power(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - gpio_set(platform_gpio()->h1r9_1v8_enable); -#endif - break; - case BOARD_ID_PRALINE: - break; - default: - gpio_set(platform_gpio()->gpio_1v8_enable); - break; - } + IF_NOT_PRALINE ( + IF_H1_R9 ( + gpio_set(platform_gpio()->h1r9_1v8_enable); + ) + IF_NOT_H1_R9 ( + gpio_set(platform_gpio()->gpio_1v8_enable); + ) + ) } void disable_1v8_power(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - gpio_clear(platform_gpio()->h1r9_1v8_enable); -#endif - break; - case BOARD_ID_PRALINE: - break; - default: - gpio_clear(platform_gpio()->gpio_1v8_enable); - break; - } + IF_NOT_PRALINE ( + IF_H1_R9 ( + gpio_clear(platform_gpio()->h1r9_1v8_enable); + ) + IF_NOT_H1_R9 ( + gpio_clear(platform_gpio()->gpio_1v8_enable); + ) + ) } #if defined(HACKRF_ONE) || defined(UNIVERSAL) @@ -1281,68 +1166,48 @@ static inline void disable_rf_power_rad1o(void) void enable_rf_power(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( enable_rf_power_hackrf_one(); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( enable_rf_power_praline(); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( enable_rf_power_rad1o(); -#endif - break; - default: - break; - } + ) } void disable_rf_power(void) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( disable_rf_power_hackrf_one(); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( disable_rf_power_praline(); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( disable_rf_power_rad1o(); -#endif - break; - default: - break; - } + ) } void led_on(const led_t led) { - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( gpio_clear(platform_gpio()->led[led]); - } else { + ) + IF_NOT_PRALINE ( gpio_set(platform_gpio()->led[led]); - } + ) } void led_off(const led_t led) { - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( gpio_set(platform_gpio()->led[led]); - } else { + ) + IF_NOT_PRALINE ( gpio_clear(platform_gpio()->led[led]); - } + ) } void led_toggle(const led_t led) @@ -1352,33 +1217,29 @@ void led_toggle(const led_t led) void set_leds(const uint8_t state) { - board_id_t board_id = detected_platform(); - int num_leds = 3; - if (board_id == BOARD_ID_RAD1O || board_id == BOARD_ID_PRALINE) { + IF_FOUR_LEDS ( num_leds = 4; - } + ) for (int i = 0; i < num_leds; i++) { - if (board_id == BOARD_ID_PRALINE) { + IF_PRALINE ( gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); - } else { + ) + IF_NOT_PRALINE ( gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); - } + ) } } void trigger_enable(const bool enable) { - if (detected_platform() != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( gpio_write(sgpio_config.gpio_trigger_enable, enable); -#endif - } else { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( fpga_set_trigger_enable(&fpga, enable); -#endif - } + ) } void halt_and_flash(const uint32_t duration) diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index ac16a444..092ec12b 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -83,26 +83,16 @@ const hackrf_ui_t* hackrf_ui(void) { /* Detect on first use. If no UI hardware is detected, use a stub function table. */ if (ui == NULL && ui_enabled) { - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( if (portapack_hackrf_ui_init) { ui = portapack_hackrf_ui_init(); } -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( if (rad1o_ui_setup) { ui = rad1o_ui_setup(); } -#endif - break; - default: - break; - } + ) } if (ui == NULL) { diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 6a6d5f26..feb7837d 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -73,9 +73,7 @@ void max283x_setup(max283x_driver_t* const drv) const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ - switch (detected_platform()) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( drv->type = MAX2831_VARIANT; max2831.gpio_enable = gpio->max283x_enable; max2831.gpio_rxtx = gpio->max283x_rx_enable; @@ -83,28 +81,24 @@ void max283x_setup(max283x_driver_t* const drv) max2831.gpio_ld = gpio->max2831_ld; memcpy(&drv->drv.max2831, &max2831, sizeof(max2831)); max2831_setup(&drv->drv.max2831); -#endif - break; - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - drv->type = MAX2839_VARIANT; - max2839.gpio_enable = gpio->max283x_enable; - max2839.gpio_rxtx = gpio->max283x_rx_enable; - memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); - max2839_setup(&drv->drv.max2839); -#endif - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) - drv->type = MAX2837_VARIANT; - max2837.gpio_enable = gpio->max283x_enable; - max2837.gpio_rx_enable = gpio->max283x_rx_enable; - max2837.gpio_tx_enable = gpio->max283x_tx_enable; - memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); - max2837_setup(&drv->drv.max2837); -#endif - break; - } + ) + IF_NOT_PRALINE ( + IF_H1_R9 ( + drv->type = MAX2839_VARIANT; + max2839.gpio_enable = gpio->max283x_enable; + max2839.gpio_rxtx = gpio->max283x_rx_enable; + memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); + max2839_setup(&drv->drv.max2839); + ) + IF_NOT_H1_R9 ( + drv->type = MAX2837_VARIANT; + max2837.gpio_enable = gpio->max283x_enable; + max2837.gpio_rx_enable = gpio->max283x_rx_enable; + max2837.gpio_tx_enable = gpio->max283x_tx_enable; + memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); + max2837_setup(&drv->drv.max2837); + ) + ) } /* Macros to simplify dispatch of variant-specific operations. */ diff --git a/firmware/common/mixer.c b/firmware/common/mixer.c index 9e1afb32..0706170b 100644 --- a/firmware/common/mixer.c +++ b/firmware/common/mixer.c @@ -24,11 +24,11 @@ #include #include "fixed_point.h" +#include "platform_detect.h" #include "platform_gpio.h" #if defined(RAD1O) #include "max2871.h" #else - #include "platform_detect.h" #include "rffc5071.h" #include "rffc5071_spi.h" #include "spi_bus.h" @@ -54,26 +54,18 @@ mixer_driver_t mixer = { void mixer_bus_setup(mixer_driver_t* const mixer) { -#if defined(RAD1O) (void) mixer; -#else - const platform_gpio_t* gpio = platform_gpio(); -#endif - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( + const platform_gpio_t* gpio = platform_gpio(); + rffc5071_spi_config = (rffc5071_spi_config_t){ .gpio_select = gpio->rffc5072_select, .gpio_clock = gpio->rffc5072_clock, .gpio_data = gpio->rffc5072_data, }; spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config); -#endif - break; - case MAX2871_VARIANT: - break; - } + ) } void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) @@ -83,106 +75,67 @@ void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) const platform_gpio_t* gpio = platform_gpio(); /* Mixer GPIO serial interface PinMux */ - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_PRALINE ( + mixer->rffc5071.gpio_ld = gpio->rffc5072_ld + ); + + IF_NOT_RAD1O ( mixer->rffc5071.gpio_reset = gpio->rffc5072_reset; - if (detected_platform() == BOARD_ID_PRALINE) { - #if defined(PRALINE) || defined(UNIVERSAL) - mixer->rffc5071.gpio_ld = gpio->rffc5072_ld; - #endif - } -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) + rffc5071_setup(&mixer->rffc5071); + ) + + IF_RAD1O ( mixer->max2871.gpio_vco_ce = gpio->vco_ce; mixer->max2871.gpio_vco_sclk = gpio->vco_sclk; mixer->max2871.gpio_vco_sdata = gpio->vco_sdata; mixer->max2871.gpio_vco_le = gpio->vco_le; mixer->max2871.gpio_synt_rfout_en = gpio->synt_rfout_en; mixer->max2871.gpio_vco_mux = gpio->vco_mux; -#endif - break; - } - - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) - rffc5071_setup(&mixer->rffc5071); -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) max2871_setup(&mixer->max2871); -#endif - break; - } + ) } fp_40_24_t mixer_set_frequency(mixer_driver_t* const mixer, fp_40_24_t lo, bool program) { - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( return rffc5071_set_frequency(&mixer->rffc5071, lo, program); -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) - return max2871_set_frequency(&mixer->max2871, lo, program); -#endif - break; - } + ) - return 0; + IF_RAD1O ( + return max2871_set_frequency(&mixer->max2871, lo, program); + ) } void mixer_enable(mixer_driver_t* const mixer) { - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( rffc5071_enable(&mixer->rffc5071); -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) + ) + + IF_RAD1O ( max2871_enable(&mixer->max2871); -#endif - break; - } + ) } void mixer_disable(mixer_driver_t* const mixer) { - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( rffc5071_disable(&mixer->rffc5071); -#endif - break; - case MAX2871_VARIANT: -#if defined(RAD1O) + ) + + IF_RAD1O ( max2871_disable(&mixer->max2871); -#endif - break; - } + ) } void mixer_set_gpo(mixer_driver_t* const mixer, uint8_t gpo) { -#if defined(RAD1O) - (void) gpo; -#endif - - switch (mixer->type) { - case RFFC5071_VARIANT: -#if !defined(RAD1O) + IF_NOT_RAD1O ( rffc5071_set_gpo(&mixer->rffc5071, gpo); -#endif - break; - case MAX2871_VARIANT: - break; - } + ) + + IF_RAD1O ( + (void) mixer; + (void) gpo; + ) } diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 21ce3d7e..82bb6400 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -98,8 +98,7 @@ void operacake_sctimer_init(void) SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); uint8_t sct_clock_input; - if (detected_platform() != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( // Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12 SGPIO_OUT_MUX_CFG12 = SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode @@ -110,9 +109,8 @@ void operacake_sctimer_init(void) GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1 sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1; -#endif - } else { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( // Configure pin P6_4 as SCT_IN_6 scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); @@ -120,8 +118,7 @@ void operacake_sctimer_init(void) GIMA_CTIN_6_IN = 0x0 << 4; sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6; -#endif - } + ) // We configure this register first, because the user manual says to SCT_CONFIG |= SCT_CONFIG_UNIFY_32_BIT | SCT_CONFIG_CLKMODE_PRESCALED_BUS_CLOCK | diff --git a/firmware/common/platform_detect.h b/firmware/common/platform_detect.h index 4f0f0f43..f8bc53b9 100644 --- a/firmware/common/platform_detect.h +++ b/firmware/common/platform_detect.h @@ -31,6 +31,117 @@ #define PLATFORM_HACKRF1_R9 (1 << 3) #define PLATFORM_PRALINE (1 << 4) +/* clang-format off */ + +/* Helper macros for platform-specific code. */ +#if defined(UNIVERSAL) + #define IF_PRALINE(...) \ + if (detected_platform() == BOARD_ID_PRALINE) { __VA_ARGS__; } + #define IF_NOT_PRALINE(...) \ + if (detected_platform() != BOARD_ID_PRALINE) { __VA_ARGS__; } + #define IF_HACKRF_ONE(...) \ + switch (detected_platform()) { \ + case BOARD_ID_HACKRF1_OG: \ + case BOARD_ID_HACKRF1_R9: \ + { __VA_ARGS__; } \ + break; \ + default: \ + break; \ + } + #define IF_NOT_HACKRF_ONE(...) \ + switch (detected_platform()) { \ + case BOARD_ID_HACKRF1_OG: \ + case BOARD_ID_HACKRF1_R9: \ + break; \ + default: \ + { __VA_ARGS__; } \ + break; \ + } + #define IF_H1_R9(...) \ + if (detected_platform() == BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } + #define IF_NOT_H1_R9(...) \ + if (detected_platform() != BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } + #define IF_RAD1O(...) + #define IF_NOT_RAD1O(...) { __VA_ARGS__; } + #define IF_JAWBREAKER(...) + #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } + #define IF_H1_OR_RAD1O(...) IF_HACKRF_ONE(__VA_ARGS__) + #define IF_H1_OR_JAWBREAKER(...) IF_HACKRF_ONE(__VA_ARGS__) + #define IF_FOUR_LEDS(...) IF_PRALINE(__VA_ARGS__) + #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } +#elif defined(HACKRF_ONE) + #define IF_PRALINE(...) + #define IF_NOT_PRALINE(...) { __VA_ARGS__; } + #define IF_HACKRF_ONE(...) { __VA_ARGS__; } + #define IF_NOT_HACKRF_ONE(...) + #define IF_H1_R9(...) \ + if (detected_platform() == BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } + #define IF_NOT_H1_R9(...) \ + if (detected_platform() != BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } + #define IF_RAD1O(...) + #define IF_NOT_RAD1O(...) { __VA_ARGS__; } + #define IF_JAWBREAKER(...) + #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } + #define IF_H1_OR_RAD1O(...) { __VA_ARGS__; } + #define IF_H1_OR_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_FOUR_LEDS(...) + #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } +#elif defined(PRALINE) + #define IF_PRALINE(...) { __VA_ARGS__; } + #define IF_NOT_PRALINE(...) + #define IF_HACKRF_ONE(...) + #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } + #define IF_H1_R9(...) + #define IF_NOT_H1_R9(...) { __VA_ARGS__; } + #define IF_RAD1O(...) + #define IF_NOT_RAD1O(...) { __VA_ARGS__; } + #define IF_JAWBREAKER(...) + #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } + #define IF_H1_OR_RAD1O(...) + #define IF_H1_OR_JAWBREAKER(...) + #define IF_FOUR_LEDS(...) + #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } +#elif defined(RAD1O) + #define IF_PRALINE(...) + #define IF_NOT_PRALINE(...) { __VA_ARGS__; } + #define IF_HACKRF_ONE(...) + #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } + #define IF_H1_R9(...) + #define IF_NOT_H1_R9(...) { __VA_ARGS__; } + #define IF_RAD1O(...) { __VA_ARGS__; } + #define IF_NOT_RAD1O(...) + #define IF_JAWBREAKER(...) + #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_H1_OR_PRALINE(...) + #define IF_H1_OR_RAD1O(...) { __VA_ARGS__; } + #define IF_H1_OR_JAWBREAKER(...) + #define IF_FOUR_LEDS(...) { __VA_ARGS__; } + #define IF_EXPANSION_COMPATIBLE(...) +#elif defined(JAWBREAKER) + #define IF_PRALINE(...) + #define IF_NOT_PRALINE(...) { __VA_ARGS__; } + #define IF_HACKRF_ONE(...) + #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } + #define IF_H1_R9(...) + #define IF_NOT_H1_R9(...) { __VA_ARGS__; } + #define IF_RAD1O(...) + #define IF_NOT_RAD1O(...) { __VA_ARGS__; } + #define IF_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_NOT_JAWBREAKER(...) + #define IF_H1_OR_PRALINE(...) + #define IF_H1_OR_RAD1O(...) + #define IF_H1_OR_JAWBREAKER(...) { __VA_ARGS__; } + #define IF_FOUR_LEDS(...) + #define IF_EXPANSION_COMPATIBLE(...) +#else + #error "No recognised platform defined" +#endif + +/* clang-format on */ + typedef enum { BOARD_ID_JELLYBEAN = 0, BOARD_ID_JAWBREAKER = 1, diff --git a/firmware/common/platform_gpio.c b/firmware/common/platform_gpio.c index f9a44903..7f773b20 100644 --- a/firmware/common/platform_gpio.c +++ b/firmware/common/platform_gpio.c @@ -34,93 +34,61 @@ const platform_gpio_t* platform_gpio(void) return _platform_gpio; } - board_id_t board_id = detected_platform(); static platform_gpio_t gpio; /* LEDs */ gpio.led[0] = &GPIO2_1; gpio.led[1] = &GPIO2_2; gpio.led[2] = &GPIO2_8; - switch (board_id) { - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( gpio.led[3] = &GPIO5_26; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( gpio.led[3] = &GPIO4_6; -#endif - break; - default: - break; - } + ) /* Power Supply Control */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( gpio.gpio_1v2_enable = &GPIO4_7; gpio.gpio_3v3aux_enable_n = &GPIO5_15; -#endif - break; - default: + ) + IF_NOT_PRALINE ( gpio.gpio_1v8_enable = &GPIO3_6; - break; - } + ) /* MAX283x GPIO (XCVR_CTL / CS_XCVR) PinMux */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( gpio.max283x_select = &GPIO6_28; gpio.max283x_enable = &GPIO7_1; gpio.max283x_rx_enable = &GPIO7_2; gpio.max2831_rxhp = &GPIO6_29; gpio.max2831_ld = &GPIO4_11; -#endif - break; - default: + ) + IF_NOT_PRALINE ( gpio.max283x_select = &GPIO0_15; gpio.max283x_enable = &GPIO2_6; gpio.max283x_rx_enable = &GPIO2_5; gpio.max283x_tx_enable = &GPIO2_4; - break; - } + ) /* MAX5864 SPI chip select (AD_CS / CS_AD) GPIO PinMux */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( gpio.max5864_select = &GPIO6_30; -#endif - break; - default: + ) + IF_NOT_PRALINE ( gpio.max5864_select = &GPIO2_7; - break; - } + ) /* RF supply (VAA) control */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( gpio.vaa_disable = &GPIO2_9; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( gpio.vaa_disable = &GPIO4_1; -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( gpio.vaa_enable = &GPIO2_9; -#endif - break; - default: - break; - } + ) /* W25Q80BV Flash */ gpio.w25q80bv_hold = &GPIO1_14; @@ -128,10 +96,7 @@ const platform_gpio_t* platform_gpio(void) gpio.w25q80bv_select = &GPIO5_11; /* RF switch control */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( gpio.hp = &GPIO2_0; gpio.lp = &GPIO2_10; gpio.tx_mix_bp = &GPIO2_11; @@ -145,13 +110,12 @@ const platform_gpio_t* platform_gpio(void) gpio.amp_bypass = &GPIO0_14; gpio.rx_amp = &GPIO1_11; gpio.no_rx_amp_pwr = &GPIO1_12; - // HackRF One rev 9 - gpio.h1r9_rx = &GPIO0_7; - gpio.h1r9_no_ant_pwr = &GPIO2_4; -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_H1_R9 ( + gpio.h1r9_rx = &GPIO0_7; + gpio.h1r9_no_ant_pwr = &GPIO2_4; + ) + ) + IF_RAD1O ( gpio.tx_rx_n = &GPIO1_11; gpio.tx_rx = &GPIO0_14; gpio.by_mix = &GPIO1_12; @@ -163,118 +127,70 @@ const platform_gpio_t* platform_gpio(void) gpio.low_high_filt_n = &GPIO2_12; gpio.tx_amp = &GPIO2_15; gpio.rx_lna = &GPIO5_15; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( gpio.tx_en = &GPIO3_4; gpio.mix_en_n = &GPIO3_2; gpio.mix_en_n_r1_0 = &GPIO5_6; gpio.lpf_en = &GPIO4_8; gpio.rf_amp_en = &GPIO4_9; gpio.ant_bias_en_n = &GPIO1_12; -#endif - break; - case BOARD_ID_JELLYBEAN: - case BOARD_ID_JAWBREAKER: - break; - default: - break; - } + ) /* CPLD JTAG interface GPIO pins_FPGA config pins in Praline */ gpio.cpld_tck = &GPIO3_0; - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( gpio.fpga_cfg_creset = &GPIO2_11; gpio.fpga_cfg_cdone = &GPIO5_14; gpio.fpga_cfg_spi_cs = &GPIO2_10; -#endif - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) + ) + IF_NOT_PRALINE ( gpio.cpld_tdo = &GPIO5_18; - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_RAD1O: + IF_H1_OR_RAD1O ( gpio.cpld_tms = &GPIO3_4; gpio.cpld_tdi = &GPIO3_1; - break; - case BOARD_ID_JAWBREAKER: + ) + IF_JAWBREAKER ( gpio.cpld_tms = &GPIO3_1; gpio.cpld_tdi = &GPIO3_4; - break; - default: - break; - } -#endif - break; - } - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + ) + ) + IF_EXPANSION_COMPATIBLE ( gpio.cpld_pp_tms = &GPIO1_1; gpio.cpld_pp_tdo = &GPIO1_8; -#endif - break; - default: - break; - } + ) /* Other CPLD interface GPIO pins */ - switch (board_id) { - case BOARD_ID_PRALINE: - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( gpio.trigger_enable = &GPIO5_12; -#endif - break; - } + ) gpio.q_invert = &GPIO0_13; /* RFFC5071 GPIO serial interface PinMux */ - switch (board_id) { - case BOARD_ID_JAWBREAKER: - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_NOT_RAD1O ( gpio.rffc5072_select = &GPIO2_13; gpio.rffc5072_clock = &GPIO5_6; gpio.rffc5072_data = &GPIO3_3; gpio.rffc5072_reset = &GPIO2_14; -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( gpio.vco_ce = &GPIO2_13; gpio.vco_sclk = &GPIO5_6; gpio.vco_sdata = &GPIO3_3; gpio.vco_le = &GPIO2_14; gpio.vco_mux = &GPIO5_25; gpio.synt_rfout_en = &GPIO3_5; -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( gpio.rffc5072_select = &GPIO2_13; gpio.rffc5072_clock = &GPIO5_18; gpio.rffc5072_data = &GPIO4_14; gpio.rffc5072_reset = &GPIO2_14; gpio.rffc5072_ld = &GPIO6_25; -#endif - break; - default: - break; - } + ) -#if defined(PRALINE) || defined(UNIVERSAL) - switch (board_id) { - case BOARD_ID_PRALINE: + /* Praline */ + IF_PRALINE ( gpio.p2_ctrl0 = &GPIO7_3; gpio.p2_ctrl1 = &GPIO7_4; gpio.p1_ctrl0 = &GPIO0_14; @@ -285,50 +201,33 @@ const platform_gpio_t* platform_gpio(void) gpio.trigger_in = &GPIO6_26; gpio.trigger_out = &GPIO5_6; gpio.pps_out = &GPIO5_5; - break; - default: - break; - } -#endif + ) -#if defined(HACKRF_ONE) || defined(UNIVERSAL) /* HackRF One r9 clock control */ - switch (board_id) { - case BOARD_ID_HACKRF1_R9: + IF_H1_R9 ( gpio.h1r9_clkin_en = &GPIO5_15; gpio.h1r9_clkout_en = &GPIO0_9; gpio.h1r9_mcu_clk_en = &GPIO0_8; - break; - default: - break; - } - - switch (board_id) { - case BOARD_ID_HACKRF1_R9: gpio.h1r9_1v8_enable = &GPIO2_9; gpio.h1r9_vaa_disable = &GPIO3_6; gpio.h1r9_trigger_enable = &GPIO5_5; - break; - default: - break; - } -#endif + ) /* rad1o LCD */ -#if defined(RAD1O) - gpio.lcd_cs = &GPIO4_12; /* P9_0 */ - gpio.lcd_bl_en = &GPIO0_8; /* P1_1 */ - gpio.lcd_reset = &GPIO5_17; /* P9_4 */ -#endif + IF_RAD1O ( + gpio.lcd_cs = &GPIO4_12; /* P9_0 */ + gpio.lcd_bl_en = &GPIO0_8; /* P1_1 */ + gpio.lcd_reset = &GPIO5_17; /* P9_4 */ + ) /* Portapack */ -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) - gpio.io_stbx = &GPIO5_0; /* P2_0 */ - gpio.addr = &GPIO5_1; /* P2_1 */ - gpio.lcd_rdx = &GPIO5_4; /* P2_4 */ - gpio.lcd_wrx = &GPIO1_10; /* P2_9 */ - gpio.dir = &GPIO1_13; /* P2_13 */ -#endif + IF_EXPANSION_COMPATIBLE ( + gpio.io_stbx = &GPIO5_0; /* P2_0 */ + gpio.addr = &GPIO5_1; /* P2_1 */ + gpio.lcd_rdx = &GPIO5_4; /* P2_4 */ + gpio.lcd_wrx = &GPIO1_10; /* P2_9 */ + gpio.dir = &GPIO1_13; /* P2_13 */ + ) _platform_gpio = &gpio; diff --git a/firmware/common/platform_scu.c b/firmware/common/platform_scu.c index edd6ee0e..9d3b0c5d 100644 --- a/firmware/common/platform_scu.c +++ b/firmware/common/platform_scu.c @@ -36,68 +36,40 @@ const platform_scu_t* platform_scu(void) return _platform_scu; } - board_id_t board_id = detected_platform(); static platform_scu_t scu; /* LED PinMux */ scu.PINMUX_LED1 = SCU_PINMUX_LED1; /* GPIO2[1] on P4_1 */ scu.PINMUX_LED2 = SCU_PINMUX_LED2; /* GPIO2[2] on P4_2 */ scu.PINMUX_LED3 = SCU_PINMUX_LED3; /* GPIO2[8] on P6_12 */ - switch (board_id) { - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( scu.PINMUX_LED4 = (PB_6); /* GPIO5[26] on PB_6 */ -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.PINMUX_LED4 = (P8_6); /* GPIO4[6] on P8_6 */ -#endif - break; - default: - break; - } + ) /* Power Supply PinMux */ scu.PINMUX_EN1V8 = (P6_10); /* GPIO3[6] on P6_10 */ scu.PINMUX_EN1V2 = (P8_7); /* GPIO4[7] on P8_7 */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu.PINMUX_EN3V3_AUX_N = (P6_7); /* GPIO5[15] on P6_7 */ scu.PINMUX_EN3V3_OC_N = (P6_11); /* GPIO3[7] on P6_11 */ -#endif - break; - default: - break; - } + ) /* GPIO Input PinMux */ scu.PINMUX_BOOT0 = (P1_1); /* GPIO0[8] on P1_1 */ scu.PINMUX_BOOT1 = (P1_2); /* GPIO0[9] on P1_2 */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - break; - default: -#if !defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_NOT_HACKRF_ONE ( scu.PINMUX_BOOT2 = (P2_8); /* GPIO5[7] on P2_8 */ scu.PINMUX_BOOT3 = (P2_9); /* GPIO1[10] on P2_9 */ -#endif - break; - } + ) /* USB peripheral */ - switch (board_id) { - case BOARD_ID_JAWBREAKER: -#if defined(JAWBREAKER) + IF_JAWBREAKER ( scu.PINMUX_USB_LED0 = (P6_8); scu.PINMUX_USB_LED1 = (P6_7); -#endif - break; - default: - break; - } + ) /* SSP1 Peripheral PinMux */ scu.SSP1_CIPO = (P1_3); /* P1_3 */ @@ -107,37 +79,25 @@ const platform_scu_t* platform_scu(void) /* CPLD JTAG interface */ scu.PINMUX_CPLD_TCK = (P6_1); /* GPIO3[ 0] */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu.PINMUX_FPGA_CRESET = (P5_2); /* GPIO2[11] on P5_2 */ scu.PINMUX_FPGA_CDONE = (P4_10); /* GPIO5[14] */ scu.PINMUX_FPGA_SPI_CS = (P5_1); /* GPIO2[10] */ -#endif - break; - default: - break; - } -#if !defined(PRALINE) || defined(UNIVERSAL) - scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ - switch (board_id) { - case BOARD_ID_RAD1O: - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: + ) + IF_NOT_PRALINE ( + scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ + ) + IF_H1_OR_RAD1O ( scu.PINMUX_CPLD_TMS = (P6_5); /* GPIO3[ 4] */ scu.PINMUX_CPLD_TDI = (P6_2); /* GPIO3[ 1] */ - break; - default: + ) + IF_JAWBREAKER ( scu.PINMUX_CPLD_TMS = (P6_2); /* GPIO3[ 1] */ scu.PINMUX_CPLD_TDI = (P6_5); /* GPIO3[ 4] */ - break; - } -#endif + ) /* CPLD SGPIO interface */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu.PINMUX_SGPIO0 = (P0_0); scu.PINMUX_SGPIO1 = (P0_1); scu.PINMUX_SGPIO2 = (P1_15); @@ -169,10 +129,8 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#endif - break; - default: -#if !defined(PRALINE) || defined(UNIVERSAL) + ) + IF_NOT_PRALINE ( scu.PINMUX_SGPIO0 = (P0_0); scu.PINMUX_SGPIO1 = (P0_1); scu.PINMUX_SGPIO2 = (P1_15); @@ -204,22 +162,16 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#endif - break; - } + ) scu.TRIGGER_EN = (P4_8); /* GPIO5[12] on P4_8 */ /* MAX283x GPIO (XCVR_CTL) PinMux */ - switch (board_id) { - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( scu.XCVR_RXHP = P8_1; /* GPIO[] on P8_1 */ scu.XCVR_B6 = P8_2; /* GPIO[] on P8_2 */ scu.XCVR_B7 = P9_3; /* GPIO[] on P9_3 */ -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.XCVR_ENABLE = PE_1; /* GPIO7[1] on PE_1 */ scu.XCVR_RXENABLE = PE_2; /* GPIO7[2] on PE_2 */ scu.XCVR_CS = PD_14; /* GPIO6[28] on PD_14 */ @@ -232,12 +184,8 @@ const platform_scu_t* platform_scu(void) scu.XCVR_RXHP_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.XCVR_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | SCU_CONF_EPD_EN_PULLDOWN | SCU_CONF_EPUN_DIS_PULLUP); -#endif - break; - case BOARD_ID_JAWBREAKER: - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + ) + IF_H1_OR_JAWBREAKER ( scu.XCVR_ENABLE = P4_6; /* GPIO2[6] on P4_6 */ scu.XCVR_RXENABLE = P4_5; /* GPIO2[5] on P4_5 */ scu.XCVR_TXENABLE = P4_4; /* GPIO2[4] on P4_4 */ @@ -247,32 +195,20 @@ const platform_scu_t* platform_scu(void) scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST); scu.XCVR_TXENABLE_PINCFG = (SCU_GPIO_FAST); scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST); -#endif - break; - default: - break; - } + ) /* MAX5864 SPI chip select (AD_CS) GPIO PinMux */ - switch (board_id) { - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( scu.AD_CS = (PD_16); /* GPIO6[30] on PD_16 */ scu.AD_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#endif - break; - default: + ) + IF_NOT_PRALINE ( scu.AD_CS = (P5_7); /* GPIO2[7] on P5_7 */ scu.AD_CS_PINCFG = (SCU_GPIO_FAST); - break; - } + ) /* RFFC5071 GPIO serial interface PinMux */ - switch (board_id) { - case BOARD_ID_JAWBREAKER: - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(JAWBREAKER) || defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_OR_JAWBREAKER ( scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ scu.MIXER_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ scu.MIXER_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ @@ -280,10 +216,8 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ scu.MIXER_SCLK = (P9_5); /* GPIO5[18] on P9_5 */ scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ @@ -293,10 +227,8 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( scu.VCO_CE = (P5_4); /* GPIO2[13] on P5_4 */ scu.VCO_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ scu.VCO_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ @@ -304,44 +236,23 @@ const platform_scu_t* platform_scu(void) scu.VCO_MUX = (PB_5); /* GPIO5[25] on PB_5 */ scu.MIXER_EN = (P6_8); /* GPIO5[16] on P6_8 */ scu.SYNT_RFOUT_EN = (P6_9); /* GPIO3[5] on P6_9 */ -#endif - break; - default: - break; - } + ) /* RF LDO control */ - switch (board_id) { - case BOARD_ID_JAWBREAKER: -#if defined(JAWBREAKER) + IF_JAWBREAKER ( scu.RF_LDO_ENABLE = (P5_0); /* GPIO2[9] on P5_0 */ -#endif - break; - default: - break; - } + ) /* RF supply (VAA) control */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( scu.NO_VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.NO_VAA_ENABLE = P8_1; /* GPIO4[1] on P8_1 */ -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( scu.VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ -#endif - break; - default: - break; - } + ) /* SPI flash */ scu.SSP0_CIPO = (P3_6); @@ -352,10 +263,7 @@ const platform_scu_t* platform_scu(void) scu.FLASH_WP = (P3_5); /* GPIO1[15] on P3_5 */ /* RF switch control */ - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( scu.HP = P4_0; /* GPIO2[0] on P4_0 */ scu.LP = P5_1; /* GPIO2[10] on P5_1 */ scu.TX_MIX_BP = P5_2; /* GPIO2[11] on P5_2 */ @@ -369,10 +277,8 @@ const platform_scu_t* platform_scu(void) scu.AMP_BYPASS = P2_10; /* GPIO0[14] on P2_10 */ scu.RX_AMP = P2_11; /* GPIO1[11] on P2_11 */ scu.NO_RX_AMP_PWR = P2_12; /* GPIO1[12] on P2_12 */ -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( scu.BY_AMP = P1_7; /* GPIO1[0] on P1_7 */ scu.BY_AMP_N = P2_5; /* GPIO5[5] on P2_5 */ scu.TX_RX = P2_10; /* GPIO0[14] on P2_10 */ @@ -383,10 +289,8 @@ const platform_scu_t* platform_scu(void) scu.LOW_HIGH_FILT_N = P5_3; /* GPIO2[12] on P5_3 */ scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ scu.RX_LNA = P6_7; /* GPIO5[15] on P6_7 */ -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( scu.TX_EN = P6_5; /* GPIO3[4] on P6_5 */ scu.MIX_EN_N = P6_3; /* GPIO3[2] on P6_3 */ scu.MIX_EN_N_R1_0 = P2_6; /* GPIO5[6] on P2_6 */ @@ -394,16 +298,10 @@ const platform_scu_t* platform_scu(void) scu.RF_AMP_EN = PA_2; /* GPIO4[9] on PA_2 */ scu.ANT_BIAS_EN_N = P2_12; /* GPIO1[12] on P2_12 */ scu.ANT_BIAS_OC_N = P2_11; /* GPIO1[11] on P2_11 */ -#endif - break; - default: - break; - } + ) /* Praline */ -#if defined(PRALINE) || defined(UNIVERSAL) - switch (board_id) { - case BOARD_ID_PRALINE: + IF_PRALINE ( scu.P2_CTRL0 = (PE_3); /* GPIO7[3] on PE_3 */ scu.P2_CTRL1 = (PE_4); /* GPIO7[4] on PE_4 */ scu.P1_CTRL0 = (P2_10); /* GPIO0[14] on P2_10 */ @@ -425,16 +323,10 @@ const platform_scu_t* platform_scu(void) scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - break; - default: - break; - } -#endif + ) /* HackRF One r9 */ -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - switch (board_id) { - case BOARD_ID_HACKRF1_R9: + IF_H1_R9 ( scu.H1R9_CLKIN_EN = P6_7; /* GPIO5[15] on P6_7 */ scu.H1R9_CLKOUT_EN = P1_2; /* GPIO0[9] on P1_2 = has boot pull-down; */ scu.H1R9_MCU_CLK_EN = P1_1; /* GPIO0[8] on P1_1 = has boot pull-up; */ @@ -443,54 +335,50 @@ const platform_scu_t* platform_scu(void) scu.H1R9_EN1V8 = P5_0; /* GPIO2[9] on P5_0 */ scu.H1R9_NO_VAA_EN = P6_10; /* GPIO3[6] on P6_10 */ scu.H1R9_TRIGGER_EN = P2_5; /* GPIO5[5] on P2_5 */ - break; - default: - break; - } -#endif + ) /* Expansion headers */ -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) - scu.PINMUX_PP_D0 = (P7_0); /* GPIO3[8] */ - scu.PINMUX_PP_D1 = (P7_1); /* GPIO3[9] */ - scu.PINMUX_PP_D2 = (P7_2); /* GPIO3[10] */ - scu.PINMUX_PP_D3 = (P7_3); /* GPIO3[11] */ - scu.PINMUX_PP_D4 = (P7_4); /* GPIO3[12] */ - scu.PINMUX_PP_D5 = (P7_5); /* GPIO3[13] */ - scu.PINMUX_PP_D6 = (P7_6); /* GPIO3[14] */ - scu.PINMUX_PP_D7 = (P7_7); /* GPIO3[15] */ + IF_EXPANSION_COMPATIBLE ( + scu.PINMUX_PP_D0 = (P7_0); /* GPIO3[8] */ + scu.PINMUX_PP_D1 = (P7_1); /* GPIO3[9] */ + scu.PINMUX_PP_D2 = (P7_2); /* GPIO3[10] */ + scu.PINMUX_PP_D3 = (P7_3); /* GPIO3[11] */ + scu.PINMUX_PP_D4 = (P7_4); /* GPIO3[12] */ + scu.PINMUX_PP_D5 = (P7_5); /* GPIO3[13] */ + scu.PINMUX_PP_D6 = (P7_6); /* GPIO3[14] */ + scu.PINMUX_PP_D7 = (P7_7); /* GPIO3[15] */ - scu.PINMUX_PP_LCD_TE = (P2_3); /* GPIO5[3] on P2_3 */ - scu.PINMUX_PP_LCD_RDX = (P2_4); /* GPIO5[4] on P2_4 */ - scu.PINMUX_PP_UNUSED = (P2_8); /* GPIO5[7] on P2_8 */ - scu.PINMUX_PP_LCD_WRX = (P2_9); /* GPIO1[10] on P2_9 */ - scu.PINMUX_PP_DIR = (P2_13); /* GPIO1[13] on P2_13 */ + scu.PINMUX_PP_LCD_TE = (P2_3); /* GPIO5[3] on P2_3 */ + scu.PINMUX_PP_LCD_RDX = (P2_4); /* GPIO5[4] on P2_4 */ + scu.PINMUX_PP_UNUSED = (P2_8); /* GPIO5[7] on P2_8 */ + scu.PINMUX_PP_LCD_WRX = (P2_9); /* GPIO1[10] on P2_9 */ + scu.PINMUX_PP_DIR = (P2_13); /* GPIO1[13] on P2_13 */ - scu.PINMUX_PP_TDO = (P1_5); /* GPIO1[8] */ - scu.PINMUX_SD_POW = (P1_5); /* GPIO1[8] */ - scu.PINMUX_SD_CMD = (P1_6); /* GPIO1[9] */ - scu.PINMUX_PP_TMS = (P1_8); /* GPIO1[1] */ - scu.PINMUX_SD_VOLT0 = (P1_8); /* GPIO1[1] */ - scu.PINMUX_SD_DAT0 = (P1_9); /* GPIO1[2] */ - scu.PINMUX_SD_DAT1 = (P1_10); /* GPIO1[3] */ - scu.PINMUX_SD_DAT2 = (P1_11); /* GPIO1[4] */ - scu.PINMUX_SD_DAT3 = (P1_12); /* GPIO1[5] */ - scu.PINMUX_SD_CD = (P1_13); /* GPIO1[6] */ + scu.PINMUX_PP_TDO = (P1_5); /* GPIO1[8] */ + scu.PINMUX_SD_POW = (P1_5); /* GPIO1[8] */ + scu.PINMUX_SD_CMD = (P1_6); /* GPIO1[9] */ + scu.PINMUX_PP_TMS = (P1_8); /* GPIO1[1] */ + scu.PINMUX_SD_VOLT0 = (P1_8); /* GPIO1[1] */ + scu.PINMUX_SD_DAT0 = (P1_9); /* GPIO1[2] */ + scu.PINMUX_SD_DAT1 = (P1_10); /* GPIO1[3] */ + scu.PINMUX_SD_DAT2 = (P1_11); /* GPIO1[4] */ + scu.PINMUX_SD_DAT3 = (P1_12); /* GPIO1[5] */ + scu.PINMUX_SD_CD = (P1_13); /* GPIO1[6] */ - scu.PINMUX_PP_IO_STBX = (P2_0); /* GPIO5[0] */ - scu.PINMUX_PP_ADDR = (P2_1); /* GPIO5[1] */ - scu.PINMUX_U0_TXD = (P2_0); /* GPIO5[0] */ - scu.PINMUX_U0_RXD = (P2_1); /* GPIO5[1] */ + scu.PINMUX_PP_IO_STBX = (P2_0); /* GPIO5[0] */ + scu.PINMUX_PP_ADDR = (P2_1); /* GPIO5[1] */ + scu.PINMUX_U0_TXD = (P2_0); /* GPIO5[0] */ + scu.PINMUX_U0_RXD = (P2_1); /* GPIO5[1] */ - scu.PINMUX_GPIO3_8 = (P7_0); /* GPIO3[8] */ - scu.PINMUX_GPIO3_9 = (P7_1); /* GPIO3[9] */ - scu.PINMUX_GPIO3_10 = (P7_2); /* GPIO3[10] */ - scu.PINMUX_GPIO3_11 = (P7_3); /* GPIO3[11] */ - scu.PINMUX_GPIO3_12 = (P7_4); /* GPIO3[12] */ - scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ - scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ - scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ -#endif + scu.PINMUX_GPIO3_8 = (P7_0); /* GPIO3[8] */ + scu.PINMUX_GPIO3_9 = (P7_1); /* GPIO3[9] */ + scu.PINMUX_GPIO3_10 = (P7_2); /* GPIO3[10] */ + scu.PINMUX_GPIO3_11 = (P7_3); /* GPIO3[11] */ + scu.PINMUX_GPIO3_12 = (P7_4); /* GPIO3[12] */ + scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ + scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ + scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ + ) scu.PINMUX_ISP = (P2_7); /* GPIO0[7] */ diff --git a/firmware/common/radio.c b/firmware/common/radio.c index ff97a814..a4d41c68 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -195,22 +195,18 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) case TRANSCEIVER_MODE_SS: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) { - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( fpga_set_tx_interpolation_ratio(&fpga, n); -#endif - } + ) radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX] = n; } break; default: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) { - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( fpga_set_rx_decimation_ratio(&fpga, n); -#endif - } + ) radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX] = n; } } @@ -354,7 +350,9 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) const uint64_t requested_if = bank[RADIO_FREQUENCY_IF]; const uint64_t requested_lo = bank[RADIO_FREQUENCY_LO]; const uint64_t requested_img_reject = bank[RADIO_IMAGE_REJECT]; +#if defined(PRALINE) || defined(UNIVERSAL) const uint64_t requested_rotation = bank[RADIO_ROTATION]; +#endif const uint64_t applied_rf = radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_RF]; const uint64_t applied_if = radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_IF]; @@ -393,7 +391,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) if (requested_lo != RADIO_UNSET) { freq_lo = mixer_set_frequency(&mixer, freq_lo, false); } - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( if (requested_rotation != RADIO_UNSET) { rotation = requested_rotation; } @@ -409,9 +407,10 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) default: rotation = 0; } - } else { + ) + IF_NOT_PRALINE ( rotation = 0; - } + ) /* Handle requested RF (auto-tune). */ if (requested_rf != RADIO_UNSET) { @@ -421,8 +420,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) freq_rf = restrict_rf(requested_rf, img_reject); /* Look up settings appropriate for requested RF. */ - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( const tune_config_t* tune_config = select_tune_config(opmode, freq_rf); if (requested_rotation == RADIO_UNSET) { @@ -438,15 +436,15 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) } } high_lo = tune_config->high_lo; -#endif - } else { + ) + IF_NOT_PRALINE ( /* Use graduated or fixed IF on older platforms. */ if (requested_if == RADIO_UNSET) { freq_if = select_graduated_if(freq_rf, img_reject); } high_lo = (img_reject == RF_PATH_FILTER_LOW_PASS); analog_rf = freq_rf; - } + ) /* Compute precise LO. This is done first because the mixer LO is set in coarse steps. */ if (requested_lo == RADIO_UNSET) { @@ -502,11 +500,9 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) changed |= (1 << RADIO_IMAGE_REJECT); } if ((rotation != applied_rotation) && (rotation != RADIO_UNSET)) { - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); -#endif - } + ) radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION] = rotation; changed |= (1 << RADIO_ROTATION); } @@ -571,20 +567,29 @@ static uint32_t radio_update_bandwidth(radio_t* const radio, uint64_t* bank) opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE]; } - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( /* Praline legacy mode always sets baseband bandwidth automatically. */ (void) bank; uint32_t lpf_bandwidth = auto_bandwidth(radio, opmode); - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != + lpf_bandwidth) { + max283x_set_lpf_bandwidth( + &max283x, + MAX283x_MODE_TX, + lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = + lpf_bandwidth; changed |= (1 << RADIO_XCVR_TX_LPF); } - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_RX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] != + lpf_bandwidth) { + max283x_set_lpf_bandwidth( + &max283x, + MAX283x_MODE_RX, + lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = + lpf_bandwidth; changed |= (1 << RADIO_XCVR_RX_LPF); } bool narrow_lpf_enable = false; @@ -601,14 +606,15 @@ static uint32_t radio_update_bandwidth(radio_t* const radio, uint64_t* bank) } /* Always set HPF bandwidth to 30 kHz for now. */ const max283x_rx_hpf_freq_t hpf_bandwidth = MAX283x_RX_HPF_30_KHZ; - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] != hpf_bandwidth) { + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] != + hpf_bandwidth) { max283x_set_rx_hpf_frequency(&max283x, hpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] = hpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_HPF] = + hpf_bandwidth; changed |= (1 << RADIO_XCVR_RX_HPF); } -#endif - } else { -#if !defined(PRALINE) || defined(UNIVERSAL) + ) + IF_NOT_PRALINE ( uint64_t lpf_bandwidth; lpf_bandwidth = bank[RADIO_XCVR_TX_LPF]; if (lpf_bandwidth == RADIO_UNSET) { @@ -621,24 +627,33 @@ static uint32_t radio_update_bandwidth(radio_t* const radio, uint64_t* bank) lpf_bandwidth = bank[RADIO_BB_BANDWIDTH_RX]; } if (lpf_bandwidth == RADIO_UNSET) { - lpf_bandwidth = radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF]; + lpf_bandwidth = + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF]; } if (lpf_bandwidth == RADIO_UNSET) { lpf_bandwidth = auto_bandwidth(radio, opmode); } - if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != lpf_bandwidth) { - max283x_set_lpf_bandwidth(&max283x, MAX283x_MODE_TX, lpf_bandwidth); - radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_RX] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_TX] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = lpf_bandwidth; - radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = lpf_bandwidth; + if (radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] != + lpf_bandwidth) { + max283x_set_lpf_bandwidth( + &max283x, + MAX283x_MODE_TX, + lpf_bandwidth); + radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_RX] = + lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_BB_BANDWIDTH_TX] = + lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_TX_LPF] = + lpf_bandwidth; + radio->config[RADIO_BANK_APPLIED][RADIO_XCVR_RX_LPF] = + lpf_bandwidth; changed |= - ((1 << RADIO_BB_BANDWIDTH_RX) | (1 << RADIO_BB_BANDWIDTH_TX) | - (1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF)); + ((1 << RADIO_BB_BANDWIDTH_RX) | + (1 << RADIO_BB_BANDWIDTH_TX) | (1 << RADIO_XCVR_TX_LPF) | + (1 << RADIO_XCVR_RX_LPF)); } -#endif - } + ) return changed; } @@ -774,24 +789,23 @@ static uint32_t radio_update_trigger(radio_t* const radio, uint64_t* bank) static uint32_t radio_update_dc_block(radio_t* const radio, uint64_t* bank) { -#if defined(PRALINE) || defined(UNIVERSAL) - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( const uint64_t requested = bank[RADIO_DC_BLOCK]; bool enable = requested; - if (radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] == (uint64_t) enable) { + if (radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] == + (uint64_t) enable) { return 0; } fpga_set_rx_dc_block_enable(&fpga, enable); radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; return (1 << RADIO_DC_BLOCK); - } -#else + ) + (void) radio; (void) bank; -#endif - return 0; + return false; } bool radio_update(radio_t* const radio) diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index f2234413..a757eceb 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -294,35 +294,19 @@ static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) { -#if defined(JAWBREAKER) - (void) rf_path; -#endif - - switch (detected_platform()) { - case BOARD_ID_JAWBREAKER: -#if defined(JAWBREAKER) + IF_JAWBREAKER ( + (void) rf_path; mixer_set_gpo(&mixer, gpo); -#endif - break; - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + ) + IF_HACKRF_ONE ( switchctrl_set_hackrf_one(rf_path, gpo); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( switchctrl_set_praline(rf_path, gpo); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( switchctrl_set_rad1o(rf_path, gpo); -#endif - break; - default: - break; - } + ) } void rf_path_pin_setup(rf_path_t* const rf_path) @@ -331,15 +315,11 @@ void rf_path_pin_setup(rf_path_t* const rf_path) (void) rf_path; #endif - board_id_t board_id = detected_platform(); #if !defined(JAWBREAKER) const platform_scu_t* scu = platform_scu(); #endif - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( /* Configure RF switch control signals */ // clang-format off scu_pinmux(scu->HP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); @@ -354,7 +334,7 @@ void rf_path_pin_setup(rf_path_t* const rf_path) scu_pinmux(scu->RX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux(scu->NO_RX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); // clang-format on - if (board_id == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( scu_pinmux(scu->H1R9_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux( scu->H1R9_NO_ANT_PWR, @@ -364,12 +344,13 @@ void rf_path_pin_setup(rf_path_t* const rf_path) scu_pinmux( scu->H1R9_NO_VAA_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } else { + ) + IF_NOT_H1_R9 ( scu_pinmux(scu->TX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu_pinmux(scu->RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); gpio_output(rf_path->gpio_tx); scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - } + ) /* * Safe (initial) switch settings turn off both amplifiers and antenna port @@ -390,10 +371,8 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_no_tx_amp_pwr); gpio_output(rf_path->gpio_mix_bypass); gpio_output(rf_path->gpio_rx); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( /* Configure RF switch control signals */ // clang-format off scu_pinmux(scu->BY_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); @@ -430,10 +409,9 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_low_high_filt_n); gpio_output(rf_path->gpio_tx_amp); gpio_output(rf_path->gpio_rx_lna); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + + IF_PRALINE ( /* Configure RF switch control signals */ scu_pinmux(scu->TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); board_rev_t rev = detected_revision(); @@ -464,17 +442,11 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_mix_en_n); gpio_output(rf_path->gpio_lpf_en); gpio_output(rf_path->gpio_rf_amp_en); -#endif - break; - default: - break; - } + ) } void rf_path_init(rf_path_t* const rf_path) { - board_id_t board_id = detected_platform(); - ssp1_set_mode_max5864(); max5864_setup(&max5864); max5864_shutdown(&max5864); @@ -483,22 +455,14 @@ void rf_path_init(rf_path_t* const rf_path) max283x_setup(&max283x); max283x_start(&max283x); - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - // On HackRF One, the mixer is now set up earlier in boot. - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + IF_RAD1O ( mixer_setup(&mixer, MAX2871_VARIANT); -#endif - break; - default: -#if !defined(RAD1O) - mixer_setup(&mixer, RFFC5071_VARIANT); -#endif - break; - } + ) + IF_NOT_RAD1O ( + IF_NOT_HACKRF_ONE ( + mixer_setup(&mixer, RFFC5071_VARIANT); + ) + ) rf_path->switchctrl = SWITCHCTRL_SAFE; } diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index 89befe98..fbeb2d93 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -110,19 +110,15 @@ void rffc5071_init(rffc5071_driver_t* const drv) */ void rffc5071_setup(rffc5071_driver_t* const drv) { - board_id_t board_id = detected_platform(); - gpio_set(drv->gpio_reset); gpio_output(drv->gpio_reset); - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( /* Configure mixer PLL lock detect pin */ const platform_scu_t* scu = platform_scu(); scu_pinmux(scu->MIXER_LD, scu->MIXER_LD_PINCFG); gpio_input(drv->gpio_ld); -#endif - } + ) rffc5071_init(drv); @@ -140,16 +136,9 @@ void rffc5071_setup(rffc5071_driver_t* const drv) set_RFFC5071_FULLD(drv, 0); set_RFFC5071_MODE(drv, 1); - switch (board_id) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: - case BOARD_ID_PRALINE: - /* Enable GPO Lock output signal */ + IF_H1_OR_PRALINE ( set_RFFC5071_LOCK(drv, 1); - break; - default: - break; - } + ) /* Enable reference oscillator standby */ set_RFFC5071_REFST(drv, 1); @@ -192,17 +181,15 @@ void rffc5071_lock_test(rffc5071_driver_t* const drv) bool rffc5071_check_lock(rffc5071_driver_t* const drv) { - if (detected_platform() == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( return gpio_read(drv->gpio_ld); -#else - return false; -#endif - } else { + ) + IF_NOT_PRALINE ( set_RFFC5071_READSEL(drv, 0b0001); rffc5071_regs_commit(drv); return !!(rffc5071_reg_read(drv, RFFC5071_READBACK_REG) & 0x8000); - } + ) + return false; } static uint16_t rffc5071_spi_read(rffc5071_driver_t* const drv, uint8_t r) @@ -382,9 +369,9 @@ bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) // // For all other boards we'll just return true to avoid a situation where a // a caller is waiting for a lock signal that will never be detected. - if (detected_platform() != BOARD_ID_PRALINE) { + IF_NOT_PRALINE ( return true; - } + ) // The RFFC5072 can be configured to output PLL lock status on // GPO4. The lock detect signal is produced by a window detector @@ -451,9 +438,7 @@ bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) uint8_t rsm_state = (rb >> 11) & 0b11111; // get gpo4 lock detect signal - #if defined(PRALINE) || defined(UNIVERSAL) bool gpo4_ld = gpio_read(drv->gpio_ld); - #endif // parse state switch (rsm_state) { diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index ac755d51..86b6c58e 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -57,27 +57,24 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config) scu_pinmux(scu->PINMUX_SGPIO14, scu->PINMUX_SGPIO14_PINCFG); /* GPIO5[13] */ scu_pinmux(scu->PINMUX_SGPIO15, scu->PINMUX_SGPIO15_PINCFG); /* GPIO5[14] */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( scu_pinmux( scu->H1R9_TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[5] */ -#endif - } else { + ) + IF_NOT_H1_R9 ( scu_pinmux( scu->TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[12] */ - } + ) sgpio_cpld_set_mixer_invert(config, 0); gpio_output(config->gpio_q_invert); - if (detected_platform() != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( trigger_enable(false); gpio_output(config->gpio_trigger_enable); -#endif - } + ) } void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index b50aad81..dbedddf3 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -235,7 +235,6 @@ void si5351c_configure_multisynth( void si5351c_configure_clock_control(si5351c_driver_t* const drv) { uint8_t clkout_ctrl; - board_id_t board_id = detected_platform(); if (clkout_enabled) { clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | @@ -270,8 +269,7 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ }; - if (board_id == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); @@ -282,10 +280,8 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) data[4] = SI5351C_CLK_POWERDOWN; data[5] = SI5351C_CLK_POWERDOWN; data[6] = SI5351C_CLK_POWERDOWN; -#endif - } - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( /* CLK0: AFE_CLK */ data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | @@ -315,8 +311,7 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) /* CLK2: MCU_CLK */ data[3] = SI5351C_CLK_POWERDOWN; } -#endif - } + ) si5351c_write(drv, data, sizeof(data)); } @@ -331,11 +326,10 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) /* 7: Clock to CPU is deactivated as it is not used and creates noise */ /* 3: External clock output is deactivated by default */ - board_id_t board_id = detected_platform(); uint8_t clkout = 3; - uint8_t value; + uint8_t value = 0; - if (board_id == BOARD_ID_PRALINE) { + IF_PRALINE ( value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); @@ -345,21 +339,22 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) } else { value |= SI5351C_CLK_DISABLE(2); } - } else { + ) + IF_NOT_PRALINE ( value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); /* HackRF One r9 has only three clock generator outputs. */ - if (board_id == BOARD_ID_HACKRF1_R9) { + IF_H1_R9 ( clkout = 2; value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); - } - } + ) + ) value |= (clkout_enabled) ? SI5351C_CLK_ENABLE(clkout) : SI5351C_CLK_DISABLE(clkout); @@ -367,16 +362,14 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) si5351c_write(drv, data, sizeof(data)); outputs_disabled = value; - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( const platform_gpio_t* gpio = platform_gpio(); if (clkout_enabled) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); } -#endif - } + ) } void si5351c_set_int_mode( @@ -406,8 +399,7 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source return; } si5351c_disable_all_outputs(drv); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) + IF_H1_R9 ( /* * HackRF One r9 always uses PLL A on the XTAL input * but externally switches that input to CLKIN. @@ -418,10 +410,10 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source } else { gpio_clear(platform_gpio()->h1r9_clkin_en); } -#endif - } else { + ) + IF_NOT_H1_R9 ( si5351c_configure_pll_sources(drv, source); - } + ) si5351c_configure_pll_multisynth(drv, source); active_clock_source = source; si5351c_reset_pll(drv, SI5351C_PLL_BOTH); @@ -476,8 +468,7 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -495,8 +486,7 @@ void si5351c_init(si5351c_driver_t* const drv) scu_pinmux(scu->H1R9_MCU_CLK_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); gpio_clear(gpio->h1r9_mcu_clk_en); gpio_output(gpio->h1r9_mcu_clk_en); -#endif - } + ) } /* diff --git a/firmware/common/tuning.c b/firmware/common/tuning.c index 9c863beb..347be0ae 100644 --- a/firmware/common/tuning.c +++ b/firmware/common/tuning.c @@ -38,12 +38,10 @@ static fp_40_24_t max_bypass_freq = FP_MHZ(2740); void tuning_setup(void) { -#if defined(PRALINE) || defined(UNIVERSAL) - if (detected_platform() == BOARD_ID_PRALINE) { + IF_PRALINE ( min_bypass_freq = FP_MHZ(2320); max_bypass_freq = FP_MHZ(2580); - } -#endif + ) } fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) @@ -52,12 +50,13 @@ fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) switch (img_reject) { case RF_PATH_FILTER_LOW_PASS: - if (detected_platform() == BOARD_ID_RAD1O) { + IF_RAD1O ( freq_if = FP_MHZ(2300); - } else { + ) + IF_NOT_RAD1O ( /* IF is graduated from 2650 MHz to 2340 MHz */ freq_if = FP_MHZ(2650) - (freq_rf / 7); - } + ) break; case RF_PATH_FILTER_HIGH_PASS: if (freq_rf < MID1_HP_FREQ) { diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 4952f85d..97a04737 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -347,9 +347,9 @@ void radio_changed(const uint32_t changed) if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { hackrf_ui()->set_frequency(freq / FP_ONE_HZ); } -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) - operacake_set_range(freq / FP_ONE_MHZ); -#endif + IF_EXPANSION_COMPATIBLE ( + operacake_set_range(freq / FP_ONE_MHZ); + ) } if (changed & (1 << RADIO_IMAGE_REJECT)) { if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { @@ -443,20 +443,9 @@ int main(void) } delay_us_at_mhz(10000, 96); pin_setup(); - if (board_id != BOARD_ID_PRALINE) { - enable_1v8_power(); - if (board_id != BOARD_ID_RAD1O) { - /* - * On rad1o, the clock generator power supply comes from the RF supply - * which is enabled later. On H1 and Jawbreaker, the clock generator is - * on the main 3V3 supply. - */ - clock_gen_init(); - } - } else { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( enable_3v3aux_power(); - #if !defined(DFU_MODE) && !defined(RAM_MODE) +#if !defined(DFU_MODE) && !defined(RAM_MODE) enable_1v2_power(); enable_rf_power(); /* @@ -464,62 +453,60 @@ int main(void) * which is enabled when 1V2FPGA is turned on. */ clock_gen_init(); - #endif #endif - } + ) + IF_NOT_PRALINE ( + enable_1v8_power(); + IF_NOT_RAD1O ( + /* + * On rad1o, the clock generator power supply comes from the RF supply + * which is enabled later. On H1 and Jawbreaker, the clock generator is + * on the main 3V3 supply. + */ + clock_gen_init(); + ) + ) tuning_setup(); - if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( // Set up mixer before enabling RF power, because its // GPO is used to control the antenna bias tee. mixer_setup(&mixer, RFFC5071_VARIANT); -#endif - } - if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9 || - board_id == BOARD_ID_RAD1O) { -#if defined(RAD1O) || defined(HACKRF_ONE) || defined(UNIVERSAL) + ) + IF_H1_OR_RAD1O ( enable_rf_power(); -#endif - } - if (board_id == BOARD_ID_RAD1O) { + ) + IF_RAD1O ( clock_gen_init(); - } + ) cpu_clock_init(); /* Wake the M0 */ ipc_halt_m0(); ipc_start_m0((uint32_t) &__ram_m0_start__); -#if !defined(PRALINE) || defined(UNIVERSAL) - if (board_id != BOARD_ID_PRALINE) { + IF_NOT_PRALINE ( if (!cpld_jtag_sram_load(&jtag_cpld)) { halt_and_flash(6000000); } - } -#endif -#if defined(PRALINE) || defined(UNIVERSAL) - if (board_id == BOARD_ID_PRALINE) { - #if defined(DFU_MODE) || defined(RAM_MODE) + ) + IF_PRALINE ( +#if defined(DFU_MODE) || defined(RAM_MODE) selftest.fpga_image_load = SKIPPED; selftest.report.pass = false; - #else +#else fpga_image_load(&fpga_loader, 0); - #endif +#endif delay_us_at_mhz(100, 204); fpga_spi_selftest(); fpga_sgpio_selftest(); - } -#endif + ) radio.update_cb = radio_changed; radio_init(&radio); - if (board_id == BOARD_ID_HACKRF1_OG || board_id == BOARD_ID_HACKRF1_R9 || - board_id == BOARD_ID_PRALINE) { -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) + IF_EXPANSION_COMPATIBLE ( portapack_init(); -#endif - } + ) #ifndef DFU_MODE usb_set_descriptor_by_serial_number(); @@ -528,35 +515,22 @@ int main(void) usb_set_configuration_changed_cb(usb_configuration_changed); usb_peripheral_reset(); - switch (detected_platform()) { - case BOARD_ID_HACKRF1_OG: - case BOARD_ID_HACKRF1_R9: -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_HACKRF_ONE ( memcpy(&usb_device, &usb_device_hackrf_one, sizeof(usb_device_hackrf_one)); -#endif - break; - case BOARD_ID_JAWBREAKER: -#if defined(JAWBREAKER) + ) + IF_JAWBREAKER ( memcpy(&usb_device, &usb_device_jawbreaker, sizeof(usb_device_jawbreaker)); -#endif - break; - case BOARD_ID_RAD1O: -#if defined(RAD1O) + ) + IF_RAD1O ( memcpy(&usb_device, &usb_device_rad1o, sizeof(usb_device_rad1o)); -#endif - break; - case BOARD_ID_PRALINE: -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( memcpy(&usb_device, &usb_device_praline, sizeof(usb_device_praline)); -#endif - break; - default: - break; - }; + ) usb_device_init(0, &usb_device); usb_queue_init(&usb_endpoint_control_out_queue); @@ -579,11 +553,9 @@ int main(void) rffc5071_lock_test(&mixer.rffc5071); #endif - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( fpga_if_xcvr_selftest(); -#endif - } + ) if (da7219_detect()) { operacake_skip_i2c_address(DA7219_ADDRESS); @@ -630,11 +602,9 @@ int main(void) sweep_mode(request.seq); break; case TRANSCEIVER_MODE_CPLD_UPDATE: - if (board_id != BOARD_ID_PRALINE) { -#if !defined(PRALINE) || defined(UNIVERSAL) + IF_NOT_PRALINE ( cpld_update(); -#endif - } + ) break; default: break; diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index 8e64ad7f..eb91e5c9 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -131,8 +131,7 @@ usb_request_status_t usb_vendor_request_reset( if (stage == USB_TRANSFER_STAGE_SETUP) { pin_shutdown(); clock_gen_shutdown(); - if (detected_platform() == BOARD_ID_HACKRF1_R9) { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) + IF_H1_R9 ( /* * Set boot pins as inputs so that the bootloader reads them * correctly after the reset. @@ -141,8 +140,7 @@ usb_request_status_t usb_vendor_request_reset( gpio_input(gpio->h1r9_mcu_clk_en); gpio_input(gpio->h1r9_clkout_en); gpio_input(gpio->h1r9_rx); -#endif - } + ) wwdt_reset(100000); diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index b74589ec..bf7e4985 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -142,64 +142,60 @@ usb_request_status_t usb_vendor_request_write_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { -#if defined(RAD1O) - (void) endpoint; - (void) stage; - if (detected_platform() == BOARD_ID_RAD1O) { + IF_RAD1O ( + (void) endpoint; + (void) stage; return USB_REQUEST_STATUS_STALL; - } -#endif + ) - if (stage == USB_TRANSFER_STAGE_SETUP) { -#if !defined(RAD1O) - if (endpoint->setup.index < RFFC5071_NUM_REGS) { - rffc5071_reg_write( - &mixer.rffc5071, - endpoint->setup.index, - endpoint->setup.value); - usb_transfer_schedule_ack(endpoint->in); - return USB_REQUEST_STATUS_OK; + IF_NOT_RAD1O ( + if (stage == USB_TRANSFER_STAGE_SETUP) { + if (endpoint->setup.index < RFFC5071_NUM_REGS) { + rffc5071_reg_write( + &mixer.rffc5071, + endpoint->setup.index, + endpoint->setup.value); + usb_transfer_schedule_ack(endpoint->in); + return USB_REQUEST_STATUS_OK; + } + return USB_REQUEST_STATUS_STALL; } -#endif - return USB_REQUEST_STATUS_STALL; - } else { return USB_REQUEST_STATUS_OK; - } + ) } usb_request_status_t usb_vendor_request_read_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { -#if defined(RAD1O) - (void) endpoint; - (void) stage; - if (detected_platform() == BOARD_ID_RAD1O) { + IF_RAD1O ( + (void) endpoint; + (void) stage; return USB_REQUEST_STATUS_STALL; - } -#endif + ) - if (stage == USB_TRANSFER_STAGE_SETUP) { -#if !defined(RAD1O) + IF_NOT_RAD1O ( uint16_t value; - if (endpoint->setup.index < RFFC5071_NUM_REGS) { - value = rffc5071_reg_read(&mixer.rffc5071, endpoint->setup.index); - endpoint->buffer[0] = value & 0xff; - endpoint->buffer[1] = value >> 8; - usb_transfer_schedule_block( - endpoint->in, - &endpoint->buffer, - 2, - NULL, - NULL); - usb_transfer_schedule_ack(endpoint->out); - return USB_REQUEST_STATUS_OK; + if (stage == USB_TRANSFER_STAGE_SETUP) { + if (endpoint->setup.index < RFFC5071_NUM_REGS) { + value = rffc5071_reg_read( + &mixer.rffc5071, + endpoint->setup.index); + endpoint->buffer[0] = value & 0xff; + endpoint->buffer[1] = value >> 8; + usb_transfer_schedule_block( + endpoint->in, + &endpoint->buffer, + 2, + NULL, + NULL); + usb_transfer_schedule_ack(endpoint->out); + return USB_REQUEST_STATUS_OK; + } + return USB_REQUEST_STATUS_STALL; } -#endif - return USB_REQUEST_STATUS_STALL; - } else { return USB_REQUEST_STATUS_OK; - } + ) } usb_request_status_t usb_vendor_request_set_clkout_enable( @@ -286,47 +282,47 @@ usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( return USB_REQUEST_STATUS_OK; } +#if defined(PRALINE) || defined(UNIVERSAL) usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - if (detected_platform() != BOARD_ID_PRALINE) { - return USB_REQUEST_STATUS_STALL; - } - - if (stage == USB_TRANSFER_STAGE_SETUP) { -#if defined(PRALINE) || defined(UNIVERSAL) - fpga_reg_write(&fpga, endpoint->setup.index, endpoint->setup.value); -#endif - usb_transfer_schedule_ack(endpoint->in); + IF_PRALINE ( + if (stage == USB_TRANSFER_STAGE_SETUP) { + fpga_reg_write( + &fpga, + endpoint->setup.index, + endpoint->setup.value); + usb_transfer_schedule_ack(endpoint->in); + } return USB_REQUEST_STATUS_OK; - } - return USB_REQUEST_STATUS_OK; + ) + + return USB_REQUEST_STATUS_STALL; } usb_request_status_t usb_vendor_request_read_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - if (detected_platform() != BOARD_ID_PRALINE) { - return USB_REQUEST_STATUS_STALL; - } + IF_PRALINE ( + if (stage == USB_TRANSFER_STAGE_SETUP) { + const uint8_t value = fpga_reg_read(&fpga, endpoint->setup.index); + endpoint->buffer[0] = value; + usb_transfer_schedule_block( + endpoint->in, + &endpoint->buffer, + 1, + NULL, + NULL); + usb_transfer_schedule_ack(endpoint->out); + } + return USB_REQUEST_STATUS_OK; + ) - if (stage == USB_TRANSFER_STAGE_SETUP) { -#if defined(PRALINE) || defined(UNIVERSAL) - const uint8_t value = fpga_reg_read(&fpga, endpoint->setup.index); - endpoint->buffer[0] = value; - usb_transfer_schedule_block( - endpoint->in, - &endpoint->buffer, - 1, - NULL, - NULL); -#endif - usb_transfer_schedule_ack(endpoint->out); - } - return USB_REQUEST_STATUS_OK; + return USB_REQUEST_STATUS_STALL; } +#endif /* * Each register is transferred as a uint8_t register number followed by a diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index fa5267b5..e25f387a 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -80,18 +80,14 @@ static const char* test_result_to_str(test_result_t result) void generate_selftest_report(void) { - board_id_t board_id = detected_platform(); - char* s = &selftest.report.msg[0]; size_t c = sizeof(selftest.report.msg); - if (board_id == BOARD_ID_RAD1O) { -#if defined(RAD1O) + IF_RAD1O ( append(&s, &c, "Mixer: MAX2871, ID: "); append(&s, &c, itoa(selftest.mixer_id, 10)); append(&s, &c, "\n"); -#endif - } else { -#if !defined(RAD1O) + ) + IF_NOT_RAD1O ( append(&s, &c, "Mixer: RFFC5072, ID: "); append(&s, &c, itoa(selftest.mixer_id >> 3, 10)); append(&s, &c, ", Rev: "); @@ -108,8 +104,7 @@ void generate_selftest_report(void) append(&s, &c, " (FAIL)"); } append(&s, &c, "\n"); -#endif - } + ) append(&s, &c, "Clock: Si5351"); append(&s, &c, ", Rev: "); @@ -118,16 +113,19 @@ void generate_selftest_report(void) append(&s, &c, selftest.si5351_readback_ok ? "OK" : "FAIL"); append(&s, &c, "\n"); - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + IF_PRALINE ( append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); append(&s, &c, "\n"); -#endif - } else { -#if !defined(PRALINE) || defined(UNIVERSAL) + ) + IF_NOT_PRALINE ( append(&s, &c, "Transceiver: "); - append(&s, &c, (board_id == BOARD_ID_HACKRF1_R9 ? "MAX2839" : "MAX2837")); + IF_H1_R9 ( + append(&s, &c, "MAX2839"); + ) + IF_NOT_H1_R9 ( + append(&s, &c, "MAX2837"); + ) append(&s, &c, ", readback success: "); append(&s, &c, itoa(selftest.max283x_readback_register_count, 10)); append(&s, &c, "/"); @@ -142,10 +140,8 @@ void generate_selftest_report(void) itoa(selftest.max283x_readback_expected_value, 10)); } append(&s, &c, "\n"); -#endif - } - if (board_id == BOARD_ID_PRALINE) { -#if defined(PRALINE) || defined(UNIVERSAL) + ) + IF_PRALINE ( append(&s, &c, "FPGA configuration: "); append(&s, &c, test_result_to_str(selftest.fpga_image_load)); append(&s, &c, "\n"); @@ -177,8 +173,7 @@ void generate_selftest_report(void) append(&s, &c, itoa(m->avg_mag_sq_q, 10)); append(&s, &c, "\n"); } -#endif - } + ) } usb_request_status_t usb_vendor_request_read_selftest( From 7a6b09962402836745d74e133d46a5d95102a232 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 9 Apr 2026 18:27:46 +0100 Subject: [PATCH 053/161] firmware: Separate compile-time and runtime platform checks. --- firmware/common/cpld_jtag.c | 36 ++- firmware/common/hackrf_core.c | 366 +++++++++++++++-------- firmware/common/hackrf_ui.c | 16 +- firmware/common/max283x.c | 24 +- firmware/common/mixer.c | 74 +++-- firmware/common/operacake_sctimer.c | 12 +- firmware/common/platform_detect.h | 145 +++------ firmware/common/platform_gpio.c | 176 +++++++---- firmware/common/platform_scu.c | 176 +++++++---- firmware/common/radio.c | 60 ++-- firmware/common/rf_path.c | 72 +++-- firmware/common/rffc5071.c | 30 +- firmware/common/sgpio.c | 18 +- firmware/common/si5351c.c | 54 ++-- firmware/common/tuning.c | 18 +- firmware/hackrf_usb/hackrf_usb.c | 98 +++--- firmware/hackrf_usb/usb_api_board_info.c | 6 +- firmware/hackrf_usb/usb_api_register.c | 36 ++- firmware/hackrf_usb/usb_api_selftest.c | 42 ++- 19 files changed, 921 insertions(+), 538 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 58359051..b277b462 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -42,30 +42,38 @@ void cpld_jtag_take(jtag_t* const jtag) /* Set initial GPIO state to the voltages of the internal or external pull-ups/downs, * to avoid any glitches. */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { gpio_set(gpio->gpio_pp_tms); - ) + } +#endif gpio_clear(gpio->gpio_tck); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_set(gpio->gpio_tms); gpio_set(gpio->gpio_tdi); - ) + } +#endif - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { /* Do not drive PortaPack-specific TMS pin initially, just to be cautious. */ gpio_input(gpio->gpio_pp_tms); gpio_input(gpio->gpio_pp_tdo); - ) + } +#endif gpio_output(gpio->gpio_tck); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_output(gpio->gpio_tms); gpio_output(gpio->gpio_tdi); gpio_input(gpio->gpio_tdo); - ) + } +#endif } void cpld_jtag_release(jtag_t* const jtag) @@ -75,19 +83,23 @@ void cpld_jtag_release(jtag_t* const jtag) /* Make all pins inputs when JTAG interface not active. * Let the pull-ups/downs do the work. */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { /* Do not drive PortaPack-specific pins, initially, just to be cautious. */ gpio_input(gpio->gpio_pp_tms); gpio_input(gpio->gpio_pp_tdo); - ) + } +#endif gpio_input(gpio->gpio_tck); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_input(gpio->gpio_tms); gpio_input(gpio->gpio_tdi); gpio_input(gpio->gpio_tdo); - ) + } +#endif } #if !defined(PRALINE) || defined(UNIVERSAL) diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 689a2a30..0ac7ae65 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -341,7 +341,8 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) sgpio_cpld_stream_disable(&sgpio_config); } - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { /* Integer mode can be enabled if p1 is even and p2 is zero. */ if (p1 & 0x1 || p2) { si5351c_set_int_mode(&clock_gen, 0, 0); @@ -349,14 +350,17 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) si5351c_set_int_mode(&clock_gen, 0, 1); } - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { /* * On HackRF One r9 all sample clocks are externally derived * from MS1/CLK1 operating at twice the sample rate. */ si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { /* * On other platforms the clock generator produces three * different sample clocks, all derived from multisynth 0. @@ -381,9 +385,12 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) 0, 0, 0); //p1 doesn't matter - ) - ) - IF_PRALINE ( + } + #endif + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); @@ -409,7 +416,8 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) /* Reset PLL to synchronize output clock phase. */ si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); - ) + } +#endif if (streaming) { sgpio_cpld_stream_enable(&sgpio_config); @@ -561,7 +569,8 @@ void cpu_clock_init(void) CGU_BASE_SSP1_CLK = CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { /* Disable unused clocks */ /* Start with PLLs */ CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); @@ -629,7 +638,8 @@ void cpu_clock_init(void) // CCU2_CLK_APB2_USART3_CFG = 0; // CCU2_CLK_APLL_CFG = 0; // CCU2_CLK_SDIO_CFG = 0; - ) + } +#endif } void clock_gen_init(void) @@ -670,7 +680,8 @@ void clock_gen_init(void) * CLK7 -> AUX_CLK2 */ - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { /* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */ si5351c_configure_multisynth( &clock_gen, @@ -679,8 +690,10 @@ void clock_gen_init(void) 0, 1, 0); /* 800/20 = 40MHz */ - ) - IF_NOT_H1_R9 ( + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { /* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */ si5351c_configure_multisynth( &clock_gen, @@ -697,7 +710,8 @@ void clock_gen_init(void) 0, 1, 0); /* 800/20 = 40MHz */ - ) + } +#endif /* MS6/CLK6 is unused. */ /* MS7/CLK7 is unused. */ @@ -722,12 +736,14 @@ void clock_gen_shutdown(void) clock_source_t activate_best_clock_source(void) { - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(false); } - ) + } +#endif clock_source_t source = CLOCK_SOURCE_HACKRF; @@ -735,7 +751,8 @@ clock_source_t activate_best_clock_source(void) if (si5351c_clkin_signal_valid(&clock_gen)) { source = CLOCK_SOURCE_EXTERNAL; } else { - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(true); @@ -746,7 +763,8 @@ clock_source_t activate_best_clock_source(void) portapack_reference_oscillator(false); } } - ) + } +#endif /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ } @@ -798,69 +816,94 @@ void pin_shutdown(void) * * LPC43xx pull-up and pull-down resistors are approximately 53K. */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { scu_pinmux(scu->PINMUX_PP_TMS, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_PP_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - ) + } +#endif scu_pinmux(scu->PINMUX_CPLD_TCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { scu_pinmux(scu->PINMUX_CPLD_TMS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_CPLD_TDI, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_CPLD_TDO, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); - ) + } +#endif /* Configure SCU Pin Mux as GPIO */ scu_pinmux(scu->PINMUX_LED1, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED2, SCU_GPIO_NOPULL); scu_pinmux(scu->PINMUX_LED3, SCU_GPIO_NOPULL); - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION4); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu_pinmux(scu->PINMUX_LED4, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - ) + } +#endif /* Configure USB indicators */ - IF_JAWBREAKER ( +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { scu_pinmux(scu->PINMUX_USB_LED0, SCU_CONF_FUNCTION3); scu_pinmux(scu->PINMUX_USB_LED1, SCU_CONF_FUNCTION3); - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { disable_1v2_power(); disable_3v3aux_power(); gpio_output(gpio->gpio_1v2_enable); gpio_output(gpio->gpio_3v3aux_enable_n); scu_pinmux(scu->PINMUX_EN1V2, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_EN3V3_AUX_N, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) + } +#endif - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { disable_1v8_power(); - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { gpio_output(gpio->h1r9_1v8_enable); scu_pinmux(scu->H1R9_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { gpio_output(gpio->gpio_1v8_enable); scu_pinmux(scu->PINMUX_EN1V8, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - ) + } + #endif + } +#endif - IF_H1_OR_PRALINE ( +#ifdef IS_H1_OR_PRALINE + if (IS_H1_OR_PRALINE) { /* Safe state: start with VAA turned off: */ disable_rf_power(); /* Configure RF power supply (VAA) switch control signal as output */ - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { gpio_output(gpio->h1r9_vaa_disable); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { gpio_output(gpio->vaa_disable); - ) - ) + } + #endif + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { /* Safe state: start with VAA turned off: */ disable_rf_power(); @@ -873,9 +916,11 @@ void pin_shutdown(void) scu_pinmux(scu->PINMUX_GPIO3_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); scu_pinmux(scu->PINMUX_GPIO3_11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu_pinmux(scu->P2_CTRL0, scu->P2_CTRL0_PINCFG); scu_pinmux(scu->P2_CTRL1, scu->P2_CTRL1_PINCFG); scu_pinmux(scu->P1_CTRL0, scu->P1_CTRL0_PINCFG); @@ -910,7 +955,8 @@ void pin_shutdown(void) gpio_clear(gpio->fpga_cfg_creset); gpio_output(gpio->fpga_cfg_creset); gpio_input(gpio->fpga_cfg_cdone); - ) + } +#endif /* enable input on SCL and SDA pins */ SCU_SFSI2C0 = SCU_I2C0_NOMINAL; @@ -927,26 +973,34 @@ void pin_setup(void) led_off(0); led_off(1); led_off(2); - IF_FOUR_LEDS ( +#ifdef IS_FOUR_LEDS + if (IS_FOUR_LEDS) { led_off(3); - ) + } +#endif gpio_output(gpio->led[0]); gpio_output(gpio->led[1]); gpio_output(gpio->led[2]); - IF_FOUR_LEDS ( +#ifdef IS_FOUR_LEDS + if (IS_FOUR_LEDS) { gpio_output(gpio->led[3]); - ) + } +#endif /* Configure drivers and driver pins */ ssp_config_max283x.gpio_select = gpio->max283x_select; - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { ssp_config_max283x.data_bits = SSP_DATA_16BITS; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words - ) + } +#endif ssp_config_max5864.gpio_select = gpio->max5864_select; @@ -956,40 +1010,51 @@ void pin_setup(void) sgpio_config.gpio_q_invert = gpio->q_invert; - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { sgpio_config.gpio_trigger_enable = gpio->trigger_enable; - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; ice40.gpio_select = gpio->fpga_cfg_spi_cs; ice40.gpio_creset = gpio->fpga_cfg_creset; ice40.gpio_cdone = gpio->fpga_cfg_cdone; - ) + } +#endif jtag_gpio_cpld.gpio_tck = gpio->cpld_tck; - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; - ) + } +#endif - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; - ) + } +#endif ssp1_set_mode_max283x(); mixer_bus_setup(&mixer); - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { sgpio_config.gpio_trigger_enable = gpio->h1r9_trigger_enable; - ) + } +#endif // initialize rf_path struct and assign gpio's - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { rf_path = (rf_path_t){ .switchctrl = 0, .gpio_hp = gpio->hp, @@ -1006,13 +1071,17 @@ void pin_setup(void) .gpio_rx_amp = gpio->rx_amp, .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, }; - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { rf_path.gpio_rx = gpio->h1r9_rx; rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; - ) - ) + } + #endif + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { rf_path = (rf_path_t){ .switchctrl = 0, .gpio_tx_rx_n = gpio->tx_rx_n, @@ -1027,9 +1096,11 @@ void pin_setup(void) .gpio_tx_amp = gpio->tx_amp, .gpio_rx_lna = gpio->rx_lna, }; - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { rf_path = (rf_path_t){ .switchctrl = 0, .gpio_tx_en = gpio->tx_en, @@ -1042,7 +1113,8 @@ void pin_setup(void) (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; } - ) + } +#endif rf_path_pin_setup(&rf_path); @@ -1055,55 +1127,75 @@ void pin_setup(void) #if defined(PRALINE) || defined(UNIVERSAL) void enable_1v2_power(void) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { gpio_set(platform_gpio()->gpio_1v2_enable); - ) + } + #endif } void disable_1v2_power(void) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { gpio_clear(platform_gpio()->gpio_1v2_enable); - ) + } + #endif } void enable_3v3aux_power(void) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); - ) + } + #endif } void disable_3v3aux_power(void) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { gpio_set(platform_gpio()->gpio_3v3aux_enable_n); - ) + } + #endif } #endif void enable_1v8_power(void) { - IF_NOT_PRALINE ( - IF_H1_R9 ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + #ifdef IS_H1_R9 + if (IS_H1_R9) { gpio_set(platform_gpio()->h1r9_1v8_enable); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { gpio_set(platform_gpio()->gpio_1v8_enable); - ) - ) + } + #endif + } +#endif } void disable_1v8_power(void) { - IF_NOT_PRALINE ( - IF_H1_R9 ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + #ifdef IS_H1_R9 + if (IS_H1_R9) { gpio_clear(platform_gpio()->h1r9_1v8_enable); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { gpio_clear(platform_gpio()->gpio_1v8_enable); - ) - ) + } + #endif + } +#endif } #if defined(HACKRF_ONE) || defined(UNIVERSAL) @@ -1166,48 +1258,68 @@ static inline void disable_rf_power_rad1o(void) void enable_rf_power(void) { - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { enable_rf_power_hackrf_one(); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { enable_rf_power_praline(); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { enable_rf_power_rad1o(); - ) + } +#endif } void disable_rf_power(void) { - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { disable_rf_power_hackrf_one(); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { disable_rf_power_praline(); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { disable_rf_power_rad1o(); - ) + } +#endif } void led_on(const led_t led) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio_clear(platform_gpio()->led[led]); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_set(platform_gpio()->led[led]); - ) + } +#endif } void led_off(const led_t led) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio_set(platform_gpio()->led[led]); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_clear(platform_gpio()->led[led]); - ) + } +#endif } void led_toggle(const led_t led) @@ -1218,28 +1330,38 @@ void led_toggle(const led_t led) void set_leds(const uint8_t state) { int num_leds = 3; - IF_FOUR_LEDS ( +#ifdef IS_FOUR_LEDS + if (IS_FOUR_LEDS) { num_leds = 4; - ) + } +#endif for (int i = 0; i < num_leds; i++) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); - ) + } +#endif } } void trigger_enable(const bool enable) { - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio_write(sgpio_config.gpio_trigger_enable, enable); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_set_trigger_enable(&fpga, enable); - ) + } +#endif } void halt_and_flash(const uint32_t duration) diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index 092ec12b..b90ccc14 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -23,8 +23,10 @@ #include #include "hackrf_ui.h" -#include "platform_detect.h" #include "transceiver_mode.h" +#if !defined(JAWBREAKER) + #include "platform_detect.h" +#endif #if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) #include "ui_portapack.h" #endif @@ -83,16 +85,20 @@ const hackrf_ui_t* hackrf_ui(void) { /* Detect on first use. If no UI hardware is detected, use a stub function table. */ if (ui == NULL && ui_enabled) { - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { if (portapack_hackrf_ui_init) { ui = portapack_hackrf_ui_init(); } - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { if (rad1o_ui_setup) { ui = rad1o_ui_setup(); } - ) + } +#endif } if (ui == NULL) { diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index feb7837d..856c5d99 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -73,7 +73,8 @@ void max283x_setup(max283x_driver_t* const drv) const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { drv->type = MAX2831_VARIANT; max2831.gpio_enable = gpio->max283x_enable; max2831.gpio_rxtx = gpio->max283x_rx_enable; @@ -81,24 +82,31 @@ void max283x_setup(max283x_driver_t* const drv) max2831.gpio_ld = gpio->max2831_ld; memcpy(&drv->drv.max2831, &max2831, sizeof(max2831)); max2831_setup(&drv->drv.max2831); - ) - IF_NOT_PRALINE ( - IF_H1_R9 ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + #ifdef IS_H1_R9 + if (IS_H1_R9) { drv->type = MAX2839_VARIANT; max2839.gpio_enable = gpio->max283x_enable; max2839.gpio_rxtx = gpio->max283x_rx_enable; memcpy(&drv->drv.max2839, &max2839, sizeof(max2839)); max2839_setup(&drv->drv.max2839); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { drv->type = MAX2837_VARIANT; max2837.gpio_enable = gpio->max283x_enable; max2837.gpio_rx_enable = gpio->max283x_rx_enable; max2837.gpio_tx_enable = gpio->max283x_tx_enable; memcpy(&drv->drv.max2837, &max2837, sizeof(max2837)); max2837_setup(&drv->drv.max2837); - ) - ) + } + #endif + } +#endif } /* Macros to simplify dispatch of variant-specific operations. */ diff --git a/firmware/common/mixer.c b/firmware/common/mixer.c index 0706170b..fb921350 100644 --- a/firmware/common/mixer.c +++ b/firmware/common/mixer.c @@ -56,7 +56,8 @@ void mixer_bus_setup(mixer_driver_t* const mixer) { (void) mixer; - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { const platform_gpio_t* gpio = platform_gpio(); rffc5071_spi_config = (rffc5071_spi_config_t){ @@ -65,7 +66,8 @@ void mixer_bus_setup(mixer_driver_t* const mixer) .gpio_data = gpio->rffc5072_data, }; spi_bus_start(&spi_bus_rffc5071, &rffc5071_spi_config); - ) + } +#endif } void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) @@ -75,16 +77,21 @@ void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) const platform_gpio_t* gpio = platform_gpio(); /* Mixer GPIO serial interface PinMux */ - IF_PRALINE ( - mixer->rffc5071.gpio_ld = gpio->rffc5072_ld - ); +#ifdef IS_PRALINE + if (IS_PRALINE) { + mixer->rffc5071.gpio_ld = gpio->rffc5072_ld; + } +#endif - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { mixer->rffc5071.gpio_reset = gpio->rffc5072_reset; rffc5071_setup(&mixer->rffc5071); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { mixer->max2871.gpio_vco_ce = gpio->vco_ce; mixer->max2871.gpio_vco_sclk = gpio->vco_sclk; mixer->max2871.gpio_vco_sdata = gpio->vco_sdata; @@ -92,50 +99,67 @@ void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type) mixer->max2871.gpio_synt_rfout_en = gpio->synt_rfout_en; mixer->max2871.gpio_vco_mux = gpio->vco_mux; max2871_setup(&mixer->max2871); - ) + } +#endif } fp_40_24_t mixer_set_frequency(mixer_driver_t* const mixer, fp_40_24_t lo, bool program) { - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { return rffc5071_set_frequency(&mixer->rffc5071, lo, program); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { return max2871_set_frequency(&mixer->max2871, lo, program); - ) + } +#endif } void mixer_enable(mixer_driver_t* const mixer) { - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { rffc5071_enable(&mixer->rffc5071); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { max2871_enable(&mixer->max2871); - ) + } +#endif } void mixer_disable(mixer_driver_t* const mixer) { - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { rffc5071_disable(&mixer->rffc5071); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { max2871_disable(&mixer->max2871); - ) + } +#endif } void mixer_set_gpo(mixer_driver_t* const mixer, uint8_t gpo) { - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { rffc5071_set_gpo(&mixer->rffc5071, gpo); - ) + } +#endif - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { (void) mixer; (void) gpo; - ) + } +#endif } diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 82bb6400..2dbe07d9 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -98,7 +98,8 @@ void operacake_sctimer_init(void) SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); uint8_t sct_clock_input; - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { // Configure the SGPIO to output the clock (f=2 * sample clock) on pin 12 SGPIO_OUT_MUX_CFG12 = SGPIO_OUT_MUX_CFG_P_OUT_CFG(0x08) | // clkout output mode @@ -109,8 +110,10 @@ void operacake_sctimer_init(void) GIMA_CTIN_1_IN = 0x2 << 4; // Route SGPIO12 to SCTIN1 sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_1; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { // Configure pin P6_4 as SCT_IN_6 scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); @@ -118,7 +121,8 @@ void operacake_sctimer_init(void) GIMA_CTIN_6_IN = 0x0 << 4; sct_clock_input = SCT_CONFIG_CKSEL_RISING_EDGES_ON_INPUT_6; - ) + } +#endif // We configure this register first, because the user manual says to SCT_CONFIG |= SCT_CONFIG_UNIFY_32_BIT | SCT_CONFIG_CLKMODE_PRESCALED_BUS_CLOCK | diff --git a/firmware/common/platform_detect.h b/firmware/common/platform_detect.h index f8bc53b9..0331fe9c 100644 --- a/firmware/common/platform_detect.h +++ b/firmware/common/platform_detect.h @@ -21,6 +21,7 @@ #pragma once +#include #include #define BOARD_REV_GSG (0x80) @@ -35,107 +36,57 @@ /* Helper macros for platform-specific code. */ #if defined(UNIVERSAL) - #define IF_PRALINE(...) \ - if (detected_platform() == BOARD_ID_PRALINE) { __VA_ARGS__; } - #define IF_NOT_PRALINE(...) \ - if (detected_platform() != BOARD_ID_PRALINE) { __VA_ARGS__; } - #define IF_HACKRF_ONE(...) \ - switch (detected_platform()) { \ - case BOARD_ID_HACKRF1_OG: \ - case BOARD_ID_HACKRF1_R9: \ - { __VA_ARGS__; } \ - break; \ - default: \ - break; \ - } - #define IF_NOT_HACKRF_ONE(...) \ - switch (detected_platform()) { \ - case BOARD_ID_HACKRF1_OG: \ - case BOARD_ID_HACKRF1_R9: \ - break; \ - default: \ - { __VA_ARGS__; } \ - break; \ - } - #define IF_H1_R9(...) \ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } - #define IF_NOT_H1_R9(...) \ - if (detected_platform() != BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } - #define IF_RAD1O(...) - #define IF_NOT_RAD1O(...) { __VA_ARGS__; } - #define IF_JAWBREAKER(...) - #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } - #define IF_H1_OR_RAD1O(...) IF_HACKRF_ONE(__VA_ARGS__) - #define IF_H1_OR_JAWBREAKER(...) IF_HACKRF_ONE(__VA_ARGS__) - #define IF_FOUR_LEDS(...) IF_PRALINE(__VA_ARGS__) - #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } + #define IS_PRALINE (detected_platform() == BOARD_ID_PRALINE) + #define IS_NOT_PRALINE (!IS_PRALINE) + #define IS_HACKRF_ONE ( \ + detected_platform() == BOARD_ID_HACKRF1_OG || \ + detected_platform() == BOARD_ID_HACKRF1_R9 \ + ) + #define IS_NOT_HACKRF_ONE (!IS_HACKRF_ONE) + #define IS_H1_R9 (detected_platform() == BOARD_ID_HACKRF1_R9) + #define IS_NOT_H1_R9 (!IS_H1_R9) + #define IS_NOT_RAD1O true + #define IS_NOT_JAWBREAKER true + #define IS_H1_OR_PRALINE true + #define IS_H1_OR_RAD1O IS_HACKRF_ONE + #define IS_H1_OR_JAWBREAKER IS_HACKRF_ONE + #define IS_FOUR_LEDS IS_PRALINE + #define IS_EXPANSION_COMPATIBLE true #elif defined(HACKRF_ONE) - #define IF_PRALINE(...) - #define IF_NOT_PRALINE(...) { __VA_ARGS__; } - #define IF_HACKRF_ONE(...) { __VA_ARGS__; } - #define IF_NOT_HACKRF_ONE(...) - #define IF_H1_R9(...) \ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } - #define IF_NOT_H1_R9(...) \ - if (detected_platform() != BOARD_ID_HACKRF1_R9) { __VA_ARGS__; } - #define IF_RAD1O(...) - #define IF_NOT_RAD1O(...) { __VA_ARGS__; } - #define IF_JAWBREAKER(...) - #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } - #define IF_H1_OR_RAD1O(...) { __VA_ARGS__; } - #define IF_H1_OR_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_FOUR_LEDS(...) - #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } + #define IS_NOT_PRALINE true + #define IS_HACKRF_ONE true + #define IS_H1_R9 (detected_platform() == BOARD_ID_HACKRF1_R9) + #define IS_NOT_H1_R9 (!IS_H1_R9) + #define IS_NOT_RAD1O true + #define IS_NOT_JAWBREAKER true + #define IS_H1_OR_PRALINE true + #define IS_H1_OR_RAD1O true + #define IS_H1_OR_JAWBREAKER true + #define IS_EXPANSION_COMPATIBLE true #elif defined(PRALINE) - #define IF_PRALINE(...) { __VA_ARGS__; } - #define IF_NOT_PRALINE(...) - #define IF_HACKRF_ONE(...) - #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } - #define IF_H1_R9(...) - #define IF_NOT_H1_R9(...) { __VA_ARGS__; } - #define IF_RAD1O(...) - #define IF_NOT_RAD1O(...) { __VA_ARGS__; } - #define IF_JAWBREAKER(...) - #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_H1_OR_PRALINE(...) { __VA_ARGS__; } - #define IF_H1_OR_RAD1O(...) - #define IF_H1_OR_JAWBREAKER(...) - #define IF_FOUR_LEDS(...) - #define IF_EXPANSION_COMPATIBLE(...) { __VA_ARGS__; } + #define IS_PRALINE true + #define IS_NOT_HACKRF_ONE true + #define IS_NOT_H1_R9 true + #define IS_NOT_RAD1O true + #define IS_NOT_JAWBREAKER true + #define IS_H1_OR_PRALINE true + #define IS_FOUR_LEDS true + #define IS_EXPANSION_COMPATIBLE true #elif defined(RAD1O) - #define IF_PRALINE(...) - #define IF_NOT_PRALINE(...) { __VA_ARGS__; } - #define IF_HACKRF_ONE(...) - #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } - #define IF_H1_R9(...) - #define IF_NOT_H1_R9(...) { __VA_ARGS__; } - #define IF_RAD1O(...) { __VA_ARGS__; } - #define IF_NOT_RAD1O(...) - #define IF_JAWBREAKER(...) - #define IF_NOT_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_H1_OR_PRALINE(...) - #define IF_H1_OR_RAD1O(...) { __VA_ARGS__; } - #define IF_H1_OR_JAWBREAKER(...) - #define IF_FOUR_LEDS(...) { __VA_ARGS__; } - #define IF_EXPANSION_COMPATIBLE(...) + #define IS_NOT_PRALINE true + #define IS_NOT_HACKRF_ONE true + #define IS_NOT_H1_R9 true + #define IS_RAD1O true + #define IS_NOT_JAWBREAKER true + #define IS_H1_OR_RAD1O true + #define IS_FOUR_LEDS true #elif defined(JAWBREAKER) - #define IF_PRALINE(...) - #define IF_NOT_PRALINE(...) { __VA_ARGS__; } - #define IF_HACKRF_ONE(...) - #define IF_NOT_HACKRF_ONE(...) { __VA_ARGS__; } - #define IF_H1_R9(...) - #define IF_NOT_H1_R9(...) { __VA_ARGS__; } - #define IF_RAD1O(...) - #define IF_NOT_RAD1O(...) { __VA_ARGS__; } - #define IF_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_NOT_JAWBREAKER(...) - #define IF_H1_OR_PRALINE(...) - #define IF_H1_OR_RAD1O(...) - #define IF_H1_OR_JAWBREAKER(...) { __VA_ARGS__; } - #define IF_FOUR_LEDS(...) - #define IF_EXPANSION_COMPATIBLE(...) + #define IS_NOT_PRALINE true + #define IS_NOT_HACKRF_ONE true + #define IS_NOT_H1_R9 true + #define IS_NOT_RAD1O true + #define IS_JAWBREAKER true + #define IS_H1_OR_JAWBREAKER true #else #error "No recognised platform defined" #endif diff --git a/firmware/common/platform_gpio.c b/firmware/common/platform_gpio.c index 7f773b20..a5b892e8 100644 --- a/firmware/common/platform_gpio.c +++ b/firmware/common/platform_gpio.c @@ -40,55 +40,77 @@ const platform_gpio_t* platform_gpio(void) gpio.led[0] = &GPIO2_1; gpio.led[1] = &GPIO2_2; gpio.led[2] = &GPIO2_8; - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.led[3] = &GPIO5_26; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.led[3] = &GPIO4_6; - ) + } +#endif /* Power Supply Control */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.gpio_1v2_enable = &GPIO4_7; gpio.gpio_3v3aux_enable_n = &GPIO5_15; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.gpio_1v8_enable = &GPIO3_6; - ) + } +#endif /* MAX283x GPIO (XCVR_CTL / CS_XCVR) PinMux */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.max283x_select = &GPIO6_28; gpio.max283x_enable = &GPIO7_1; gpio.max283x_rx_enable = &GPIO7_2; gpio.max2831_rxhp = &GPIO6_29; gpio.max2831_ld = &GPIO4_11; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.max283x_select = &GPIO0_15; gpio.max283x_enable = &GPIO2_6; gpio.max283x_rx_enable = &GPIO2_5; gpio.max283x_tx_enable = &GPIO2_4; - ) + } +#endif /* MAX5864 SPI chip select (AD_CS / CS_AD) GPIO PinMux */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.max5864_select = &GPIO6_30; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.max5864_select = &GPIO2_7; - ) + } +#endif /* RF supply (VAA) control */ - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { gpio.vaa_disable = &GPIO2_9; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.vaa_disable = &GPIO4_1; - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.vaa_enable = &GPIO2_9; - ) + } +#endif /* W25Q80BV Flash */ gpio.w25q80bv_hold = &GPIO1_14; @@ -96,7 +118,8 @@ const platform_gpio_t* platform_gpio(void) gpio.w25q80bv_select = &GPIO5_11; /* RF switch control */ - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { gpio.hp = &GPIO2_0; gpio.lp = &GPIO2_10; gpio.tx_mix_bp = &GPIO2_11; @@ -110,12 +133,16 @@ const platform_gpio_t* platform_gpio(void) gpio.amp_bypass = &GPIO0_14; gpio.rx_amp = &GPIO1_11; gpio.no_rx_amp_pwr = &GPIO1_12; - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { gpio.h1r9_rx = &GPIO0_7; gpio.h1r9_no_ant_pwr = &GPIO2_4; - ) - ) - IF_RAD1O ( + } +#endif + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.tx_rx_n = &GPIO1_11; gpio.tx_rx = &GPIO0_14; gpio.by_mix = &GPIO1_12; @@ -127,70 +154,92 @@ const platform_gpio_t* platform_gpio(void) gpio.low_high_filt_n = &GPIO2_12; gpio.tx_amp = &GPIO2_15; gpio.rx_lna = &GPIO5_15; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.tx_en = &GPIO3_4; gpio.mix_en_n = &GPIO3_2; gpio.mix_en_n_r1_0 = &GPIO5_6; gpio.lpf_en = &GPIO4_8; gpio.rf_amp_en = &GPIO4_9; gpio.ant_bias_en_n = &GPIO1_12; - ) + } +#endif /* CPLD JTAG interface GPIO pins_FPGA config pins in Praline */ gpio.cpld_tck = &GPIO3_0; - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.fpga_cfg_creset = &GPIO2_11; gpio.fpga_cfg_cdone = &GPIO5_14; gpio.fpga_cfg_spi_cs = &GPIO2_10; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.cpld_tdo = &GPIO5_18; - IF_H1_OR_RAD1O ( - gpio.cpld_tms = &GPIO3_4; - gpio.cpld_tdi = &GPIO3_1; - ) - IF_JAWBREAKER ( - gpio.cpld_tms = &GPIO3_1; - gpio.cpld_tdi = &GPIO3_4; - ) - ) - IF_EXPANSION_COMPATIBLE ( + } +#endif +#ifdef IS_H1_OR_RAD1O + if (IS_H1_OR_RAD1O) { + gpio.cpld_tms = &GPIO3_4; + gpio.cpld_tdi = &GPIO3_1; + } +#endif +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { + gpio.cpld_tms = &GPIO3_1; + gpio.cpld_tdi = &GPIO3_4; + } +#endif +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { gpio.cpld_pp_tms = &GPIO1_1; gpio.cpld_pp_tdo = &GPIO1_8; - ) + } +#endif /* Other CPLD interface GPIO pins */ - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { gpio.trigger_enable = &GPIO5_12; - ) + } +#endif gpio.q_invert = &GPIO0_13; /* RFFC5071 GPIO serial interface PinMux */ - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { gpio.rffc5072_select = &GPIO2_13; gpio.rffc5072_clock = &GPIO5_6; gpio.rffc5072_data = &GPIO3_3; gpio.rffc5072_reset = &GPIO2_14; - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.vco_ce = &GPIO2_13; gpio.vco_sclk = &GPIO5_6; gpio.vco_sdata = &GPIO3_3; gpio.vco_le = &GPIO2_14; gpio.vco_mux = &GPIO5_25; gpio.synt_rfout_en = &GPIO3_5; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.rffc5072_select = &GPIO2_13; gpio.rffc5072_clock = &GPIO5_18; gpio.rffc5072_data = &GPIO4_14; gpio.rffc5072_reset = &GPIO2_14; gpio.rffc5072_ld = &GPIO6_25; - ) + } +#endif /* Praline */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { gpio.p2_ctrl0 = &GPIO7_3; gpio.p2_ctrl1 = &GPIO7_4; gpio.p1_ctrl0 = &GPIO0_14; @@ -201,33 +250,40 @@ const platform_gpio_t* platform_gpio(void) gpio.trigger_in = &GPIO6_26; gpio.trigger_out = &GPIO5_6; gpio.pps_out = &GPIO5_5; - ) + } +#endif /* HackRF One r9 clock control */ - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { gpio.h1r9_clkin_en = &GPIO5_15; gpio.h1r9_clkout_en = &GPIO0_9; gpio.h1r9_mcu_clk_en = &GPIO0_8; gpio.h1r9_1v8_enable = &GPIO2_9; gpio.h1r9_vaa_disable = &GPIO3_6; gpio.h1r9_trigger_enable = &GPIO5_5; - ) + } +#endif /* rad1o LCD */ - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { gpio.lcd_cs = &GPIO4_12; /* P9_0 */ gpio.lcd_bl_en = &GPIO0_8; /* P1_1 */ gpio.lcd_reset = &GPIO5_17; /* P9_4 */ - ) + } +#endif /* Portapack */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { gpio.io_stbx = &GPIO5_0; /* P2_0 */ gpio.addr = &GPIO5_1; /* P2_1 */ gpio.lcd_rdx = &GPIO5_4; /* P2_4 */ gpio.lcd_wrx = &GPIO1_10; /* P2_9 */ gpio.dir = &GPIO1_13; /* P2_13 */ - ) + } +#endif _platform_gpio = &gpio; diff --git a/firmware/common/platform_scu.c b/firmware/common/platform_scu.c index 9d3b0c5d..ef894745 100644 --- a/firmware/common/platform_scu.c +++ b/firmware/common/platform_scu.c @@ -42,34 +42,44 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_LED1 = SCU_PINMUX_LED1; /* GPIO2[1] on P4_1 */ scu.PINMUX_LED2 = SCU_PINMUX_LED2; /* GPIO2[2] on P4_2 */ scu.PINMUX_LED3 = SCU_PINMUX_LED3; /* GPIO2[8] on P6_12 */ - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.PINMUX_LED4 = (PB_6); /* GPIO5[26] on PB_6 */ - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.PINMUX_LED4 = (P8_6); /* GPIO4[6] on P8_6 */ - ) + } +#endif /* Power Supply PinMux */ scu.PINMUX_EN1V8 = (P6_10); /* GPIO3[6] on P6_10 */ scu.PINMUX_EN1V2 = (P8_7); /* GPIO4[7] on P8_7 */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.PINMUX_EN3V3_AUX_N = (P6_7); /* GPIO5[15] on P6_7 */ scu.PINMUX_EN3V3_OC_N = (P6_11); /* GPIO3[7] on P6_11 */ - ) + } +#endif /* GPIO Input PinMux */ scu.PINMUX_BOOT0 = (P1_1); /* GPIO0[8] on P1_1 */ scu.PINMUX_BOOT1 = (P1_2); /* GPIO0[9] on P1_2 */ - IF_NOT_HACKRF_ONE ( +#ifdef IS_NOT_HACKRF_ONE + if (IS_NOT_HACKRF_ONE) { scu.PINMUX_BOOT2 = (P2_8); /* GPIO5[7] on P2_8 */ scu.PINMUX_BOOT3 = (P2_9); /* GPIO1[10] on P2_9 */ - ) + } +#endif /* USB peripheral */ - IF_JAWBREAKER ( +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { scu.PINMUX_USB_LED0 = (P6_8); scu.PINMUX_USB_LED1 = (P6_7); - ) + } +#endif /* SSP1 Peripheral PinMux */ scu.SSP1_CIPO = (P1_3); /* P1_3 */ @@ -79,25 +89,34 @@ const platform_scu_t* platform_scu(void) /* CPLD JTAG interface */ scu.PINMUX_CPLD_TCK = (P6_1); /* GPIO3[ 0] */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.PINMUX_FPGA_CRESET = (P5_2); /* GPIO2[11] on P5_2 */ scu.PINMUX_FPGA_CDONE = (P4_10); /* GPIO5[14] */ scu.PINMUX_FPGA_SPI_CS = (P5_1); /* GPIO2[10] */ - ) - IF_NOT_PRALINE ( - scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ - ) - IF_H1_OR_RAD1O ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + scu.PINMUX_CPLD_TDO = (P9_5); /* GPIO5[18] */ + } +#endif +#ifdef IS_H1_OR_RAD1O + if (IS_H1_OR_RAD1O) { scu.PINMUX_CPLD_TMS = (P6_5); /* GPIO3[ 4] */ scu.PINMUX_CPLD_TDI = (P6_2); /* GPIO3[ 1] */ - ) - IF_JAWBREAKER ( + } +#endif +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { scu.PINMUX_CPLD_TMS = (P6_2); /* GPIO3[ 1] */ scu.PINMUX_CPLD_TDI = (P6_5); /* GPIO3[ 4] */ - ) + } +#endif /* CPLD SGPIO interface */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.PINMUX_SGPIO0 = (P0_0); scu.PINMUX_SGPIO1 = (P0_1); scu.PINMUX_SGPIO2 = (P1_15); @@ -129,8 +148,10 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { scu.PINMUX_SGPIO0 = (P0_0); scu.PINMUX_SGPIO1 = (P0_1); scu.PINMUX_SGPIO2 = (P1_15); @@ -162,16 +183,20 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_SGPIO12_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.PINMUX_SGPIO14_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PINMUX_SGPIO15_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) + } +#endif scu.TRIGGER_EN = (P4_8); /* GPIO5[12] on P4_8 */ /* MAX283x GPIO (XCVR_CTL) PinMux */ - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.XCVR_RXHP = P8_1; /* GPIO[] on P8_1 */ scu.XCVR_B6 = P8_2; /* GPIO[] on P8_2 */ scu.XCVR_B7 = P9_3; /* GPIO[] on P9_3 */ - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.XCVR_ENABLE = PE_1; /* GPIO7[1] on PE_1 */ scu.XCVR_RXENABLE = PE_2; /* GPIO7[2] on PE_2 */ scu.XCVR_CS = PD_14; /* GPIO6[28] on PD_14 */ @@ -184,8 +209,10 @@ const platform_scu_t* platform_scu(void) scu.XCVR_RXHP_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.XCVR_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0 | SCU_CONF_EPD_EN_PULLDOWN | SCU_CONF_EPUN_DIS_PULLUP); - ) - IF_H1_OR_JAWBREAKER ( + } +#endif +#ifdef IS_H1_OR_JAWBREAKER + if (IS_H1_OR_JAWBREAKER) { scu.XCVR_ENABLE = P4_6; /* GPIO2[6] on P4_6 */ scu.XCVR_RXENABLE = P4_5; /* GPIO2[5] on P4_5 */ scu.XCVR_TXENABLE = P4_4; /* GPIO2[4] on P4_4 */ @@ -195,20 +222,26 @@ const platform_scu_t* platform_scu(void) scu.XCVR_RXENABLE_PINCFG = (SCU_GPIO_FAST); scu.XCVR_TXENABLE_PINCFG = (SCU_GPIO_FAST); scu.XCVR_CS_PINCFG = (SCU_GPIO_FAST); - ) + } +#endif /* MAX5864 SPI chip select (AD_CS) GPIO PinMux */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.AD_CS = (PD_16); /* GPIO6[30] on PD_16 */ scu.AD_CS_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { scu.AD_CS = (P5_7); /* GPIO2[7] on P5_7 */ scu.AD_CS_PINCFG = (SCU_GPIO_FAST); - ) + } +#endif /* RFFC5071 GPIO serial interface PinMux */ - IF_H1_OR_JAWBREAKER ( +#ifdef IS_H1_OR_JAWBREAKER + if (IS_H1_OR_JAWBREAKER) { scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ scu.MIXER_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ scu.MIXER_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ @@ -216,8 +249,10 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.MIXER_ENX = (P5_4); /* GPIO2[13] on P5_4 */ scu.MIXER_SCLK = (P9_5); /* GPIO5[18] on P9_5 */ scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ @@ -227,8 +262,10 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.VCO_CE = (P5_4); /* GPIO2[13] on P5_4 */ scu.VCO_SCLK = (P2_6); /* GPIO5[6] on P2_6 */ scu.VCO_SDATA = (P6_4); /* GPIO3[3] on P6_4 */ @@ -236,23 +273,32 @@ const platform_scu_t* platform_scu(void) scu.VCO_MUX = (PB_5); /* GPIO5[25] on PB_5 */ scu.MIXER_EN = (P6_8); /* GPIO5[16] on P6_8 */ scu.SYNT_RFOUT_EN = (P6_9); /* GPIO3[5] on P6_9 */ - ) + } +#endif /* RF LDO control */ - IF_JAWBREAKER ( +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { scu.RF_LDO_ENABLE = (P5_0); /* GPIO2[9] on P5_0 */ - ) + } +#endif /* RF supply (VAA) control */ - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { scu.NO_VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.NO_VAA_ENABLE = P8_1; /* GPIO4[1] on P8_1 */ - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.VAA_ENABLE = P5_0; /* GPIO2[9] on P5_0 */ - ) + } +#endif /* SPI flash */ scu.SSP0_CIPO = (P3_6); @@ -263,7 +309,8 @@ const platform_scu_t* platform_scu(void) scu.FLASH_WP = (P3_5); /* GPIO1[15] on P3_5 */ /* RF switch control */ - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { scu.HP = P4_0; /* GPIO2[0] on P4_0 */ scu.LP = P5_1; /* GPIO2[10] on P5_1 */ scu.TX_MIX_BP = P5_2; /* GPIO2[11] on P5_2 */ @@ -277,8 +324,10 @@ const platform_scu_t* platform_scu(void) scu.AMP_BYPASS = P2_10; /* GPIO0[14] on P2_10 */ scu.RX_AMP = P2_11; /* GPIO1[11] on P2_11 */ scu.NO_RX_AMP_PWR = P2_12; /* GPIO1[12] on P2_12 */ - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { scu.BY_AMP = P1_7; /* GPIO1[0] on P1_7 */ scu.BY_AMP_N = P2_5; /* GPIO5[5] on P2_5 */ scu.TX_RX = P2_10; /* GPIO0[14] on P2_10 */ @@ -289,8 +338,10 @@ const platform_scu_t* platform_scu(void) scu.LOW_HIGH_FILT_N = P5_3; /* GPIO2[12] on P5_3 */ scu.TX_AMP = P5_6; /* GPIO2[15] on P5_6 */ scu.RX_LNA = P6_7; /* GPIO5[15] on P6_7 */ - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.TX_EN = P6_5; /* GPIO3[4] on P6_5 */ scu.MIX_EN_N = P6_3; /* GPIO3[2] on P6_3 */ scu.MIX_EN_N_R1_0 = P2_6; /* GPIO5[6] on P2_6 */ @@ -298,10 +349,12 @@ const platform_scu_t* platform_scu(void) scu.RF_AMP_EN = PA_2; /* GPIO4[9] on PA_2 */ scu.ANT_BIAS_EN_N = P2_12; /* GPIO1[12] on P2_12 */ scu.ANT_BIAS_OC_N = P2_11; /* GPIO1[11] on P2_11 */ - ) + } +#endif /* Praline */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { scu.P2_CTRL0 = (PE_3); /* GPIO7[3] on PE_3 */ scu.P2_CTRL1 = (PE_4); /* GPIO7[4] on PE_4 */ scu.P1_CTRL0 = (P2_10); /* GPIO0[14] on P2_10 */ @@ -323,10 +376,12 @@ const platform_scu_t* platform_scu(void) scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); - ) + } +#endif /* HackRF One r9 */ - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { scu.H1R9_CLKIN_EN = P6_7; /* GPIO5[15] on P6_7 */ scu.H1R9_CLKOUT_EN = P1_2; /* GPIO0[9] on P1_2 = has boot pull-down; */ scu.H1R9_MCU_CLK_EN = P1_1; /* GPIO0[8] on P1_1 = has boot pull-up; */ @@ -335,10 +390,12 @@ const platform_scu_t* platform_scu(void) scu.H1R9_EN1V8 = P5_0; /* GPIO2[9] on P5_0 */ scu.H1R9_NO_VAA_EN = P6_10; /* GPIO3[6] on P6_10 */ scu.H1R9_TRIGGER_EN = P2_5; /* GPIO5[5] on P2_5 */ - ) + } +#endif /* Expansion headers */ - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { scu.PINMUX_PP_D0 = (P7_0); /* GPIO3[8] */ scu.PINMUX_PP_D1 = (P7_1); /* GPIO3[9] */ scu.PINMUX_PP_D2 = (P7_2); /* GPIO3[10] */ @@ -378,7 +435,8 @@ const platform_scu_t* platform_scu(void) scu.PINMUX_GPIO3_13 = (P7_5); /* GPIO3[13] */ scu.PINMUX_GPIO3_14 = (P7_6); /* GPIO3[14] */ scu.PINMUX_GPIO3_15 = (P7_7); /* GPIO3[15] */ - ) + } +#endif scu.PINMUX_ISP = (P2_7); /* GPIO0[7] */ diff --git a/firmware/common/radio.c b/firmware/common/radio.c index a4d41c68..a98587f4 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -195,18 +195,22 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) case TRANSCEIVER_MODE_SS: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_set_tx_interpolation_ratio(&fpga, n); - ) + } +#endif radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX] = n; } break; default: n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_set_rx_decimation_ratio(&fpga, n); - ) + } +#endif radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX] = n; } } @@ -391,7 +395,8 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) if (requested_lo != RADIO_UNSET) { freq_lo = mixer_set_frequency(&mixer, freq_lo, false); } - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { if (requested_rotation != RADIO_UNSET) { rotation = requested_rotation; } @@ -407,10 +412,13 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) default: rotation = 0; } - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { rotation = 0; - ) + } +#endif /* Handle requested RF (auto-tune). */ if (requested_rf != RADIO_UNSET) { @@ -420,7 +428,8 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) freq_rf = restrict_rf(requested_rf, img_reject); /* Look up settings appropriate for requested RF. */ - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { const tune_config_t* tune_config = select_tune_config(opmode, freq_rf); if (requested_rotation == RADIO_UNSET) { @@ -436,15 +445,18 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) } } high_lo = tune_config->high_lo; - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { /* Use graduated or fixed IF on older platforms. */ if (requested_if == RADIO_UNSET) { freq_if = select_graduated_if(freq_rf, img_reject); } high_lo = (img_reject == RF_PATH_FILTER_LOW_PASS); analog_rf = freq_rf; - ) + } +#endif /* Compute precise LO. This is done first because the mixer LO is set in coarse steps. */ if (requested_lo == RADIO_UNSET) { @@ -500,9 +512,11 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) changed |= (1 << RADIO_IMAGE_REJECT); } if ((rotation != applied_rotation) && (rotation != RADIO_UNSET)) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_set_rx_quarter_shift_mode(&fpga, rotation >> 30); - ) + } +#endif radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION] = rotation; changed |= (1 << RADIO_ROTATION); } @@ -567,7 +581,8 @@ static uint32_t radio_update_bandwidth(radio_t* const radio, uint64_t* bank) opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE]; } - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { /* Praline legacy mode always sets baseband bandwidth automatically. */ (void) bank; uint32_t lpf_bandwidth = auto_bandwidth(radio, opmode); @@ -613,8 +628,10 @@ static uint32_t radio_update_bandwidth(radio_t* const radio, uint64_t* bank) hpf_bandwidth; changed |= (1 << RADIO_XCVR_RX_HPF); } - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { uint64_t lpf_bandwidth; lpf_bandwidth = bank[RADIO_XCVR_TX_LPF]; if (lpf_bandwidth == RADIO_UNSET) { @@ -653,7 +670,8 @@ static uint32_t radio_update_bandwidth(radio_t* const radio, uint64_t* bank) (1 << RADIO_BB_BANDWIDTH_TX) | (1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF)); } - ) + } +#endif return changed; } @@ -789,7 +807,8 @@ static uint32_t radio_update_trigger(radio_t* const radio, uint64_t* bank) static uint32_t radio_update_dc_block(radio_t* const radio, uint64_t* bank) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { const uint64_t requested = bank[RADIO_DC_BLOCK]; bool enable = requested; @@ -801,7 +820,8 @@ static uint32_t radio_update_dc_block(radio_t* const radio, uint64_t* bank) fpga_set_rx_dc_block_enable(&fpga, enable); radio->config[RADIO_BANK_APPLIED][RADIO_DC_BLOCK] = enable; return (1 << RADIO_DC_BLOCK); - ) + } +#endif (void) radio; (void) bank; diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index a757eceb..5043bfe8 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -294,19 +294,27 @@ static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) { - IF_JAWBREAKER ( +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { (void) rf_path; mixer_set_gpo(&mixer, gpo); - ) - IF_HACKRF_ONE ( + } +#endif +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { switchctrl_set_hackrf_one(rf_path, gpo); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { switchctrl_set_praline(rf_path, gpo); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { switchctrl_set_rad1o(rf_path, gpo); - ) + } +#endif } void rf_path_pin_setup(rf_path_t* const rf_path) @@ -319,7 +327,8 @@ void rf_path_pin_setup(rf_path_t* const rf_path) const platform_scu_t* scu = platform_scu(); #endif - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { /* Configure RF switch control signals */ // clang-format off scu_pinmux(scu->HP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); @@ -334,7 +343,8 @@ void rf_path_pin_setup(rf_path_t* const rf_path) scu_pinmux(scu->RX_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux(scu->NO_RX_AMP_PWR, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); // clang-format on - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { scu_pinmux(scu->H1R9_RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu_pinmux( scu->H1R9_NO_ANT_PWR, @@ -344,13 +354,16 @@ void rf_path_pin_setup(rf_path_t* const rf_path) scu_pinmux( scu->H1R9_NO_VAA_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { scu_pinmux(scu->TX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu_pinmux(scu->RX, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); gpio_output(rf_path->gpio_tx); scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); - ) + } + #endif /* * Safe (initial) switch settings turn off both amplifiers and antenna port @@ -371,8 +384,10 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_no_tx_amp_pwr); gpio_output(rf_path->gpio_mix_bypass); gpio_output(rf_path->gpio_rx); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { /* Configure RF switch control signals */ // clang-format off scu_pinmux(scu->BY_AMP, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); @@ -409,9 +424,11 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_low_high_filt_n); gpio_output(rf_path->gpio_tx_amp); gpio_output(rf_path->gpio_rx_lna); - ) + } +#endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { /* Configure RF switch control signals */ scu_pinmux(scu->TX_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); board_rev_t rev = detected_revision(); @@ -442,7 +459,8 @@ void rf_path_pin_setup(rf_path_t* const rf_path) gpio_output(rf_path->gpio_mix_en_n); gpio_output(rf_path->gpio_lpf_en); gpio_output(rf_path->gpio_rf_amp_en); - ) + } +#endif } void rf_path_init(rf_path_t* const rf_path) @@ -455,14 +473,20 @@ void rf_path_init(rf_path_t* const rf_path) max283x_setup(&max283x); max283x_start(&max283x); - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { mixer_setup(&mixer, MAX2871_VARIANT); - ) - IF_NOT_RAD1O ( - IF_NOT_HACKRF_ONE ( + } +#endif +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { + #ifdef IS_NOT_HACKRF_ONE + if (IS_NOT_HACKRF_ONE) { mixer_setup(&mixer, RFFC5071_VARIANT); - ) - ) + } + #endif + } +#endif rf_path->switchctrl = SWITCHCTRL_SAFE; } diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index fbeb2d93..ab0717ea 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -113,12 +113,14 @@ void rffc5071_setup(rffc5071_driver_t* const drv) gpio_set(drv->gpio_reset); gpio_output(drv->gpio_reset); - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { /* Configure mixer PLL lock detect pin */ const platform_scu_t* scu = platform_scu(); scu_pinmux(scu->MIXER_LD, scu->MIXER_LD_PINCFG); gpio_input(drv->gpio_ld); - ) + } +#endif rffc5071_init(drv); @@ -136,9 +138,11 @@ void rffc5071_setup(rffc5071_driver_t* const drv) set_RFFC5071_FULLD(drv, 0); set_RFFC5071_MODE(drv, 1); - IF_H1_OR_PRALINE ( +#ifdef IS_H1_OR_PRALINE + if (IS_H1_OR_PRALINE) { set_RFFC5071_LOCK(drv, 1); - ) + } +#endif /* Enable reference oscillator standby */ set_RFFC5071_REFST(drv, 1); @@ -181,14 +185,18 @@ void rffc5071_lock_test(rffc5071_driver_t* const drv) bool rffc5071_check_lock(rffc5071_driver_t* const drv) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { return gpio_read(drv->gpio_ld); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { set_RFFC5071_READSEL(drv, 0b0001); rffc5071_regs_commit(drv); return !!(rffc5071_reg_read(drv, RFFC5071_READBACK_REG) & 0x8000); - ) + } +#endif return false; } @@ -369,9 +377,11 @@ bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) // // For all other boards we'll just return true to avoid a situation where a // a caller is waiting for a lock signal that will never be detected. - IF_NOT_PRALINE ( + #ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { return true; - ) + } + #endif // The RFFC5072 can be configured to output PLL lock status on // GPO4. The lock detect signal is produced by a window detector diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index 86b6c58e..4aa98265 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -57,24 +57,30 @@ void sgpio_configure_pin_functions(sgpio_config_t* const config) scu_pinmux(scu->PINMUX_SGPIO14, scu->PINMUX_SGPIO14_PINCFG); /* GPIO5[13] */ scu_pinmux(scu->PINMUX_SGPIO15, scu->PINMUX_SGPIO15_PINCFG); /* GPIO5[14] */ - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { scu_pinmux( scu->H1R9_TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[5] */ - ) - IF_NOT_H1_R9 ( + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { scu_pinmux( scu->TRIGGER_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION4); /* GPIO5[12] */ - ) + } +#endif sgpio_cpld_set_mixer_invert(config, 0); gpio_output(config->gpio_q_invert); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { trigger_enable(false); gpio_output(config->gpio_trigger_enable); - ) + } +#endif } void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index dbedddf3..a1466393 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -269,7 +269,8 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ }; - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); @@ -280,8 +281,10 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) data[4] = SI5351C_CLK_POWERDOWN; data[5] = SI5351C_CLK_POWERDOWN; data[6] = SI5351C_CLK_POWERDOWN; - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { /* CLK0: AFE_CLK */ data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | @@ -311,7 +314,8 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) /* CLK2: MCU_CLK */ data[3] = SI5351C_CLK_POWERDOWN; } - ) + } +#endif si5351c_write(drv, data, sizeof(data)); } @@ -329,7 +333,8 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) uint8_t clkout = 3; uint8_t value = 0; - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); @@ -339,22 +344,27 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) } else { value |= SI5351C_CLK_DISABLE(2); } - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); /* HackRF One r9 has only three clock generator outputs. */ - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { clkout = 2; value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); - ) - ) + } + #endif + } +#endif value |= (clkout_enabled) ? SI5351C_CLK_ENABLE(clkout) : SI5351C_CLK_DISABLE(clkout); @@ -362,14 +372,16 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) si5351c_write(drv, data, sizeof(data)); outputs_disabled = value; - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); if (clkout_enabled) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); } - ) + } +#endif } void si5351c_set_int_mode( @@ -399,7 +411,8 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source return; } si5351c_disable_all_outputs(drv); - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { /* * HackRF One r9 always uses PLL A on the XTAL input * but externally switches that input to CLKIN. @@ -410,10 +423,13 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source } else { gpio_clear(platform_gpio()->h1r9_clkin_en); } - ) - IF_NOT_H1_R9 ( + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { si5351c_configure_pll_sources(drv, source); - ) + } +#endif si5351c_configure_pll_multisynth(drv, source); active_clock_source = source; si5351c_reset_pll(drv, SI5351C_PLL_BOTH); @@ -468,7 +484,8 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); @@ -486,7 +503,8 @@ void si5351c_init(si5351c_driver_t* const drv) scu_pinmux(scu->H1R9_MCU_CLK_EN, SCU_GPIO_FAST | SCU_CONF_FUNCTION0); gpio_clear(gpio->h1r9_mcu_clk_en); gpio_output(gpio->h1r9_mcu_clk_en); - ) + } +#endif } /* diff --git a/firmware/common/tuning.c b/firmware/common/tuning.c index 347be0ae..9e3c41ba 100644 --- a/firmware/common/tuning.c +++ b/firmware/common/tuning.c @@ -38,10 +38,12 @@ static fp_40_24_t max_bypass_freq = FP_MHZ(2740); void tuning_setup(void) { - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { min_bypass_freq = FP_MHZ(2320); max_bypass_freq = FP_MHZ(2580); - ) + } +#endif } fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) @@ -50,13 +52,17 @@ fp_40_24_t select_graduated_if(fp_40_24_t freq_rf, rf_path_filter_t img_reject) switch (img_reject) { case RF_PATH_FILTER_LOW_PASS: - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { freq_if = FP_MHZ(2300); - ) - IF_NOT_RAD1O ( + } +#endif +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { /* IF is graduated from 2650 MHz to 2340 MHz */ freq_if = FP_MHZ(2650) - (freq_rf / 7); - ) + } +#endif break; case RF_PATH_FILTER_HIGH_PASS: if (freq_rf < MID1_HP_FREQ) { diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 97a04737..653a477a 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -443,9 +443,10 @@ int main(void) } delay_us_at_mhz(10000, 96); pin_setup(); - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { enable_3v3aux_power(); -#if !defined(DFU_MODE) && !defined(RAM_MODE) + #if !defined(DFU_MODE) && !defined(RAM_MODE) enable_1v2_power(); enable_rf_power(); /* @@ -453,60 +454,77 @@ int main(void) * which is enabled when 1V2FPGA is turned on. */ clock_gen_init(); + #endif + } #endif - ) - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { enable_1v8_power(); - IF_NOT_RAD1O ( + #ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { /* * On rad1o, the clock generator power supply comes from the RF supply * which is enabled later. On H1 and Jawbreaker, the clock generator is * on the main 3V3 supply. */ clock_gen_init(); - ) - ) + } + #endif + } +#endif tuning_setup(); - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { // Set up mixer before enabling RF power, because its // GPO is used to control the antenna bias tee. mixer_setup(&mixer, RFFC5071_VARIANT); - ) - IF_H1_OR_RAD1O ( + } +#endif +#ifdef IS_H1_OR_RAD1O + if (IS_H1_OR_RAD1O) { enable_rf_power(); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { clock_gen_init(); - ) + } +#endif cpu_clock_init(); /* Wake the M0 */ ipc_halt_m0(); ipc_start_m0((uint32_t) &__ram_m0_start__); - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { if (!cpld_jtag_sram_load(&jtag_cpld)) { halt_and_flash(6000000); } - ) - IF_PRALINE ( -#if defined(DFU_MODE) || defined(RAM_MODE) + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { + #if defined(DFU_MODE) || defined(RAM_MODE) selftest.fpga_image_load = SKIPPED; selftest.report.pass = false; -#else + #else fpga_image_load(&fpga_loader, 0); -#endif + #endif delay_us_at_mhz(100, 204); fpga_spi_selftest(); fpga_sgpio_selftest(); - ) + } +#endif radio.update_cb = radio_changed; radio_init(&radio); - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { portapack_init(); - ) + } +#endif #ifndef DFU_MODE usb_set_descriptor_by_serial_number(); @@ -515,22 +533,30 @@ int main(void) usb_set_configuration_changed_cb(usb_configuration_changed); usb_peripheral_reset(); - IF_HACKRF_ONE ( +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { memcpy(&usb_device, &usb_device_hackrf_one, sizeof(usb_device_hackrf_one)); - ) - IF_JAWBREAKER ( + } +#endif +#ifdef IS_JAWBREAKER + if (IS_JAWBREAKER) { memcpy(&usb_device, &usb_device_jawbreaker, sizeof(usb_device_jawbreaker)); - ) - IF_RAD1O ( + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { memcpy(&usb_device, &usb_device_rad1o, sizeof(usb_device_rad1o)); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { memcpy(&usb_device, &usb_device_praline, sizeof(usb_device_praline)); - ) + } +#endif usb_device_init(0, &usb_device); usb_queue_init(&usb_endpoint_control_out_queue); @@ -553,9 +579,11 @@ int main(void) rffc5071_lock_test(&mixer.rffc5071); #endif - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { fpga_if_xcvr_selftest(); - ) + } +#endif if (da7219_detect()) { operacake_skip_i2c_address(DA7219_ADDRESS); @@ -602,9 +630,11 @@ int main(void) sweep_mode(request.seq); break; case TRANSCEIVER_MODE_CPLD_UPDATE: - IF_NOT_PRALINE ( +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { cpld_update(); - ) + } +#endif break; default: break; diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index eb91e5c9..393b102d 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -131,7 +131,8 @@ usb_request_status_t usb_vendor_request_reset( if (stage == USB_TRANSFER_STAGE_SETUP) { pin_shutdown(); clock_gen_shutdown(); - IF_H1_R9 ( +#ifdef IS_H1_R9 + if (IS_H1_R9) { /* * Set boot pins as inputs so that the bootloader reads them * correctly after the reset. @@ -140,7 +141,8 @@ usb_request_status_t usb_vendor_request_reset( gpio_input(gpio->h1r9_mcu_clk_en); gpio_input(gpio->h1r9_clkout_en); gpio_input(gpio->h1r9_rx); - ) + } +#endif wwdt_reset(100000); diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index bf7e4985..d17a87d8 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -142,13 +142,16 @@ usb_request_status_t usb_vendor_request_write_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { (void) endpoint; (void) stage; return USB_REQUEST_STATUS_STALL; - ) + } +#endif - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { if (stage == USB_TRANSFER_STAGE_SETUP) { if (endpoint->setup.index < RFFC5071_NUM_REGS) { rffc5071_reg_write( @@ -161,20 +164,24 @@ usb_request_status_t usb_vendor_request_write_rffc5071( return USB_REQUEST_STATUS_STALL; } return USB_REQUEST_STATUS_OK; - ) + } +#endif } usb_request_status_t usb_vendor_request_read_rffc5071( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { (void) endpoint; (void) stage; return USB_REQUEST_STATUS_STALL; - ) + } +#endif - IF_NOT_RAD1O ( +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { uint16_t value; if (stage == USB_TRANSFER_STAGE_SETUP) { if (endpoint->setup.index < RFFC5071_NUM_REGS) { @@ -195,7 +202,8 @@ usb_request_status_t usb_vendor_request_read_rffc5071( return USB_REQUEST_STATUS_STALL; } return USB_REQUEST_STATUS_OK; - ) + } +#endif } usb_request_status_t usb_vendor_request_set_clkout_enable( @@ -287,7 +295,8 @@ usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { if (stage == USB_TRANSFER_STAGE_SETUP) { fpga_reg_write( &fpga, @@ -296,7 +305,8 @@ usb_request_status_t usb_vendor_request_write_fpga_reg( usb_transfer_schedule_ack(endpoint->in); } return USB_REQUEST_STATUS_OK; - ) + } + #endif return USB_REQUEST_STATUS_STALL; } @@ -305,7 +315,8 @@ usb_request_status_t usb_vendor_request_read_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) { - IF_PRALINE ( + #ifdef IS_PRALINE + if (IS_PRALINE) { if (stage == USB_TRANSFER_STAGE_SETUP) { const uint8_t value = fpga_reg_read(&fpga, endpoint->setup.index); endpoint->buffer[0] = value; @@ -318,7 +329,8 @@ usb_request_status_t usb_vendor_request_read_fpga_reg( usb_transfer_schedule_ack(endpoint->out); } return USB_REQUEST_STATUS_OK; - ) + } + #endif return USB_REQUEST_STATUS_STALL; } diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index e25f387a..403f3753 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -82,12 +82,15 @@ void generate_selftest_report(void) { char* s = &selftest.report.msg[0]; size_t c = sizeof(selftest.report.msg); - IF_RAD1O ( +#ifdef IS_RAD1O + if (IS_RAD1O) { append(&s, &c, "Mixer: MAX2871, ID: "); append(&s, &c, itoa(selftest.mixer_id, 10)); append(&s, &c, "\n"); - ) - IF_NOT_RAD1O ( + } +#endif +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { append(&s, &c, "Mixer: RFFC5072, ID: "); append(&s, &c, itoa(selftest.mixer_id >> 3, 10)); append(&s, &c, ", Rev: "); @@ -104,7 +107,8 @@ void generate_selftest_report(void) append(&s, &c, " (FAIL)"); } append(&s, &c, "\n"); - ) + } +#endif append(&s, &c, "Clock: Si5351"); append(&s, &c, ", Rev: "); @@ -113,19 +117,26 @@ void generate_selftest_report(void) append(&s, &c, selftest.si5351_readback_ok ? "OK" : "FAIL"); append(&s, &c, "\n"); - IF_PRALINE ( +#ifdef IS_PRALINE + if (IS_PRALINE) { append(&s, &c, "Transceiver: MAX2831, RSSI mux test: "); append(&s, &c, selftest.max2831_mux_test_ok ? "PASS" : "FAIL"); append(&s, &c, "\n"); - ) - IF_NOT_PRALINE ( + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { append(&s, &c, "Transceiver: "); - IF_H1_R9 ( + #ifdef IS_H1_R9 + if (IS_H1_R9) { append(&s, &c, "MAX2839"); - ) - IF_NOT_H1_R9 ( + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { append(&s, &c, "MAX2837"); - ) + } + #endif append(&s, &c, ", readback success: "); append(&s, &c, itoa(selftest.max283x_readback_register_count, 10)); append(&s, &c, "/"); @@ -140,8 +151,10 @@ void generate_selftest_report(void) itoa(selftest.max283x_readback_expected_value, 10)); } append(&s, &c, "\n"); - ) - IF_PRALINE ( + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { append(&s, &c, "FPGA configuration: "); append(&s, &c, test_result_to_str(selftest.fpga_image_load)); append(&s, &c, "\n"); @@ -173,7 +186,8 @@ void generate_selftest_report(void) append(&s, &c, itoa(m->avg_mag_sq_q, 10)); append(&s, &c, "\n"); } - ) + } +#endif } usb_request_status_t usb_vendor_request_read_selftest( From f7a3d434075d0fa5cdedcc546e2a426c3a628f30 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 14 Apr 2026 11:52:21 +0100 Subject: [PATCH 054/161] Use IS macros for all conditional compilation. --- firmware/common/cpld_jtag.c | 13 ++++---- firmware/common/cpld_jtag.h | 5 +-- firmware/common/hackrf_core.c | 32 ++++++++----------- firmware/common/hackrf_core.h | 13 ++++---- firmware/common/hackrf_ui.c | 8 ++--- firmware/common/max283x.c | 22 ++++++------- firmware/common/max283x.h | 20 ++++++------ firmware/common/max2871.c | 5 +-- firmware/common/mixer.c | 9 +++--- firmware/common/mixer.h | 13 +++++--- firmware/common/operacake_sctimer.c | 12 +++---- firmware/common/platform_gpio.h | 33 +++++++++---------- firmware/common/platform_scu.h | 40 +++++++++++++----------- firmware/common/radio.c | 6 ++-- firmware/common/rf_path.c | 21 +++++-------- firmware/common/rf_path.h | 12 ++++--- firmware/common/rffc5071.c | 9 ++---- firmware/common/rffc5071.h | 5 +-- firmware/common/selftest.h | 8 +++-- firmware/common/sgpio.c | 2 +- firmware/common/sgpio.h | 3 +- firmware/common/si5351c.c | 7 ++--- firmware/common/tune_config.h | 4 ++- firmware/hackrf_usb/hackrf_usb.c | 32 ++++++++++--------- firmware/hackrf_usb/usb_api_board_info.c | 3 +- firmware/hackrf_usb/usb_api_register.c | 7 ++--- firmware/hackrf_usb/usb_api_register.h | 4 ++- firmware/hackrf_usb/usb_api_selftest.c | 11 ++++--- firmware/hackrf_usb/usb_descriptor.c | 34 ++++++++------------ firmware/hackrf_usb/usb_descriptor.h | 16 +++++----- firmware/hackrf_usb/usb_device.c | 9 +++--- firmware/hackrf_usb/usb_device.h | 10 +++--- 32 files changed, 212 insertions(+), 216 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index b277b462..034ba196 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -19,17 +19,16 @@ * Boston, MA 02110-1301, USA. */ -#if !defined(PRALINE) || defined(UNIVERSAL) - #include -#endif - #include "cpld_jtag.h" + #include "platform_detect.h" -#if !defined(PRALINE) || defined(UNIVERSAL) + +#ifdef IS_NOT_PRALINE + #include #include "xapp058/micro.h" #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE static refill_buffer_cb refill_buffer; static uint32_t xsvf_buffer_len, xsvf_pos; static unsigned char* xsvf_buffer; @@ -102,7 +101,7 @@ void cpld_jtag_release(jtag_t* const jtag) #endif } -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE /* return 0 if success else return error code see xsvfExecute() */ int cpld_jtag_program( jtag_t* const jtag, diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index 939e5d94..0994c858 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -24,15 +24,16 @@ #include #include "gpio.h" +#include "platform_detect.h" typedef struct { gpio_t gpio_tck; -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE gpio_t gpio_tms; gpio_t gpio_tdi; gpio_t gpio_tdo; #endif -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE gpio_t gpio_pp_tms; gpio_t gpio_pp_tdo; #endif diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 0ac7ae65..d5679942 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -25,7 +25,10 @@ #include #include #include -#if !defined(RAD1O) + +#include "platform_detect.h" + +#ifdef IS_NOT_RAD1O #include #endif @@ -36,15 +39,14 @@ #include "i2c_lpc.h" #include "max283x.h" #include "max5864_target.h" -#include "platform_detect.h" #include "platform_gpio.h" #include "platform_scu.h" #include "spi_bus.h" #include "w25q80bv_target.h" -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE #include "portapack.h" #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "ice40_spi.h" #endif @@ -142,7 +144,7 @@ sgpio_config_t sgpio_config = { .slice_mode_multislice = true, }; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE static ssp_config_t ssp_config_ice40_fpga = { .data_bits = SSP_DATA_8BITS, .spi_mode = SSP_CPOL_1_CPHA_1, @@ -786,7 +788,7 @@ void ssp1_set_mode_max5864(void) spi_bus_start(max5864.bus, &ssp_config_max5864); } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void ssp1_set_mode_ice40(void) { spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); @@ -1124,41 +1126,33 @@ void pin_setup(void) sgpio_configure_pin_functions(&sgpio_config); } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void enable_1v2_power(void) { - #ifdef IS_PRALINE if (IS_PRALINE) { gpio_set(platform_gpio()->gpio_1v2_enable); } - #endif } void disable_1v2_power(void) { - #ifdef IS_PRALINE if (IS_PRALINE) { gpio_clear(platform_gpio()->gpio_1v2_enable); } - #endif } void enable_3v3aux_power(void) { - #ifdef IS_PRALINE if (IS_PRALINE) { gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); } - #endif } void disable_3v3aux_power(void) { - #ifdef IS_PRALINE if (IS_PRALINE) { gpio_set(platform_gpio()->gpio_3v3aux_enable_n); } - #endif } #endif @@ -1198,7 +1192,7 @@ void disable_1v8_power(void) #endif } -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE static inline void enable_rf_power_hackrf_one(void) { const platform_gpio_t* gpio = platform_gpio(); @@ -1226,7 +1220,7 @@ static inline void disable_rf_power_hackrf_one(void) } #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE static inline void enable_rf_power_praline(void) { gpio_clear(platform_gpio()->vaa_disable); @@ -1241,7 +1235,7 @@ static inline void disable_rf_power_praline(void) } #endif -#if defined(RAD1O) +#ifdef IS_RAD1O static inline void enable_rf_power_rad1o(void) { gpio_set(platform_gpio()->vaa_enable); @@ -1379,7 +1373,7 @@ void halt_and_flash(const uint32_t duration) } } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void p1_ctrl_set(const p1_ctrl_signal_t signal) { const platform_gpio_t* gpio = platform_gpio(); diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 93bc44c9..56221f6b 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -36,13 +36,14 @@ extern "C" { #include "max283x.h" #include "max5864.h" #include "mixer.h" +#include "platform_detect.h" // IWYU pragma: keep #include "radio.h" #include "rf_path.h" #include "sgpio.h" #include "si5351c.h" #include "spi_ssp.h" #include "w25q80bv.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "fpga.h" #include "ice40_spi.h" #endif @@ -52,7 +53,7 @@ extern si5351c_driver_t clock_gen; extern ssp_config_t ssp_config_w25q80bv; extern max283x_driver_t max283x; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif @@ -71,14 +72,14 @@ void clock_gen_init(void); void clock_gen_shutdown(void); void ssp1_set_mode_max283x(void); void ssp1_set_mode_max5864(void); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void ssp1_set_mode_ice40(void); #endif void pin_shutdown(void); void pin_setup(void); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE void enable_1v2_power(void); void disable_1v2_power(void); void enable_3v3aux_power(void); @@ -91,7 +92,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program); clock_source_t activate_best_clock_source(void); -#if defined(RAD1O) || defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_RAD1O) || defined(IS_HACKRF_ONE) || defined(IS_PRALINE) void enable_rf_power(void); void disable_rf_power(void); #endif @@ -112,7 +113,7 @@ void trigger_enable(const bool enable); void halt_and_flash(const uint32_t duration); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE typedef enum { P1_SIGNAL_TRIGGER_IN = 0, P1_SIGNAL_AUX_CLK1 = 1, diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index b90ccc14..4224d40f 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -23,14 +23,12 @@ #include #include "hackrf_ui.h" +#include "platform_detect.h" // IWYU pragma: keep #include "transceiver_mode.h" -#if !defined(JAWBREAKER) - #include "platform_detect.h" -#endif -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE #include "ui_portapack.h" #endif -#if defined(RAD1O) +#ifdef IS_RAD1O #include "ui_rad1o.h" #endif diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 856c5d99..3b49ab6e 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -31,19 +31,19 @@ #include "platform_gpio.h" #include "spi_bus.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "max2831_target.h" #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE #include "max2837_target.h" #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_H1_R9 #include "max2839_target.h" #endif extern spi_bus_t spi_bus_ssp1; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE max2831_driver_t max2831 = { .bus = &spi_bus_ssp1, .target_init = max2831_target_init, @@ -51,7 +51,7 @@ max2831_driver_t max2831 = { }; #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE max2837_driver_t max2837 = { .bus = &spi_bus_ssp1, .target_init = max2837_target_init, @@ -59,7 +59,7 @@ max2837_driver_t max2837 = { }; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE max2839_driver_t max2839 = { .bus = &spi_bus_ssp1, .target_init = max2839_target_init, @@ -113,7 +113,7 @@ void max283x_setup(max283x_driver_t* const drv) /* clang-format off */ -#if defined(UNIVERSAL) +#if UNIVERSAL // UNIVERSAL: all variants #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ if (_drv->type == MAX2831_VARIANT) { \ @@ -123,7 +123,7 @@ void max283x_setup(max283x_driver_t* const drv) } else { \ _max2839; \ } -#elif defined(HACKRF_ONE) +#elif HACKRF_ONE // HACKRF_ONE: MAX2837 and MAX2839 only #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ if (_drv->type == MAX2837_VARIANT) { \ @@ -131,7 +131,7 @@ void max283x_setup(max283x_driver_t* const drv) } else { \ _max2839; \ } -#elif defined(PRALINE) +#elif PRALINE // PRALINE: MAX2831 only #define DISPATCH(_drv, _max2831, _max2837, _max2839) \ (void) drv; \ @@ -251,9 +251,7 @@ uint32_t max283x_set_lpf_bandwidth( const max283x_mode_t mode, const uint32_t bandwidth_hz) { -#if !defined(PRALINE) (void) mode; -#endif DISPATCH( drv, return max2831_set_lpf_bandwidth( @@ -294,9 +292,7 @@ void max283x_set_rx_hpf_frequency( max283x_driver_t* const drv, const max283x_rx_hpf_freq_t freq) { -#if !defined(PRALINE) && !defined(UNIVERSAL) (void) freq; -#endif PRALINE_ONLY( drv, max2831_set_rx_hpf_frequency( diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index bcafff06..01bb40b0 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -27,13 +27,15 @@ #include #include "fixed_point.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#include "platform_detect.h" + +#ifdef IS_PRALINE #include "max2831.h" #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE #include "max2837.h" #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_H1_R9 #include "max2839.h" #endif @@ -55,13 +57,13 @@ typedef enum { } max283x_rx_hpf_freq_t; typedef enum { -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE MAX2831_VARIANT, #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE MAX2837_VARIANT, #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_H1_R9 MAX2839_VARIANT, #endif } max283x_variant_t; @@ -70,13 +72,13 @@ typedef struct { max283x_variant_t type; union { -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE max2831_driver_t max2831; #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE max2837_driver_t max2837; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_H1_R9 max2839_driver_t max2839; #endif } drv; diff --git a/firmware/common/max2871.c b/firmware/common/max2871.c index 4c5a94b2..05840678 100644 --- a/firmware/common/max2871.c +++ b/firmware/common/max2871.c @@ -24,17 +24,18 @@ #include #include +#include + #include "fixed_point.h" #include "max2871_regs.h" -#include "selftest.h" #include "platform_scu.h" +#include "selftest.h" #if (defined DEBUG) #include #define LOG printf #else #define LOG(x, ...) - #include #endif #define MIN(x, y) ((x) < (y) ? (x) : (y)) diff --git a/firmware/common/mixer.c b/firmware/common/mixer.c index fb921350..e38c28bf 100644 --- a/firmware/common/mixer.c +++ b/firmware/common/mixer.c @@ -26,15 +26,16 @@ #include "fixed_point.h" #include "platform_detect.h" #include "platform_gpio.h" -#if defined(RAD1O) +#ifdef IS_RAD1O #include "max2871.h" -#else +#endif +#ifdef IS_NOT_RAD1O #include "rffc5071.h" #include "rffc5071_spi.h" #include "spi_bus.h" #endif -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O static rffc5071_spi_config_t rffc5071_spi_config; static spi_bus_t spi_bus_rffc5071 = { @@ -47,7 +48,7 @@ static spi_bus_t spi_bus_rffc5071 = { #endif mixer_driver_t mixer = { -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O .rffc5071.bus = &spi_bus_rffc5071, #endif }; diff --git a/firmware/common/mixer.h b/firmware/common/mixer.h index 0afacb6b..f7861725 100644 --- a/firmware/common/mixer.h +++ b/firmware/common/mixer.h @@ -23,12 +23,15 @@ #pragma once #include +#include #include "fixed_point.h" +#include "platform_detect.h" -#if defined(RAD1O) +#ifdef IS_RAD1O #include "max2871.h" -#else +#endif +#ifdef IS_NOT_RAD1O #include "rffc5071.h" #endif @@ -41,15 +44,15 @@ typedef struct { mixer_variant_t type; union { -#if defined(RAD1O) +#ifdef IS_RAD1O max2871_driver_t max2871; -#else +#endif +#ifdef IS_NOT_RAD1O rffc5071_driver_t rffc5071; #endif }; } mixer_driver_t; -#include extern void mixer_bus_setup(mixer_driver_t* const mixer); extern void mixer_setup(mixer_driver_t* const mixer, mixer_variant_t type); diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 2dbe07d9..3b95f6a1 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -25,16 +25,16 @@ #include #include #include -#if !defined(PRALINE) + +#include "delay.h" +#include "platform_detect.h" +#include "sct.h" + +#ifdef IS_NOT_PRALINE #include #include #endif -#include - -#include "delay.h" -#include "sct.h" - #define U1CTRL_SET SCT_OUT14_SET #define U1CTRL_CLR SCT_OUT14_CLR #define U2CTRL0_SET SCT_OUT13_SET diff --git a/firmware/common/platform_gpio.h b/firmware/common/platform_gpio.h index 16767136..eeb37b05 100644 --- a/firmware/common/platform_gpio.h +++ b/firmware/common/platform_gpio.h @@ -27,10 +27,11 @@ extern "C" { #include "gpio.h" #include "gpio_lpc.h" +#include "platform_detect.h" typedef struct { /* LEDs */ -#if defined(PRALINE) || defined(UNIVERSAL) || defined(RAD1O) +#ifdef IS_FOUR_LEDS gpio_t led[4]; #else gpio_t led[3]; @@ -38,7 +39,7 @@ typedef struct { /* Power Supply Control */ gpio_t gpio_1v8_enable; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t gpio_1v2_enable; gpio_t gpio_3v3aux_enable_n; #endif @@ -48,7 +49,7 @@ typedef struct { gpio_t max283x_enable; gpio_t max283x_rx_enable; gpio_t max283x_tx_enable; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t max2831_rxhp; gpio_t max2831_ld; #endif @@ -58,7 +59,7 @@ typedef struct { /* RF supply (VAA) control */ gpio_t vaa_disable; -#if defined(RAD1O) +#ifdef IS_RAD1O gpio_t vaa_enable; #endif @@ -68,7 +69,7 @@ typedef struct { gpio_t w25q80bv_select; /* RF switch control */ -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE gpio_t hp; gpio_t lp; gpio_t tx_mix_bp; @@ -85,7 +86,7 @@ typedef struct { gpio_t h1r9_no_ant_pwr; // HACKRF_ONE r9 gpio_t h1r9_rx; // HACKRF_ONE r9 #endif -#if defined(RAD1O) +#ifdef IS_RAD1O gpio_t tx_rx_n; gpio_t tx_rx; gpio_t by_mix; @@ -98,7 +99,7 @@ typedef struct { gpio_t tx_amp; gpio_t rx_lna; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t tx_en; gpio_t mix_en_n; gpio_t mix_en_n_r1_0; @@ -109,23 +110,23 @@ typedef struct { /* CPLD JTAG interface GPIO pins, FPGA config pins in Praline */ gpio_t cpld_tck; -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE + gpio_t cpld_tdo; gpio_t cpld_tms; gpio_t cpld_tdi; - gpio_t cpld_tdo; #endif -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE gpio_t cpld_pp_tms; gpio_t cpld_pp_tdo; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t fpga_cfg_creset; gpio_t fpga_cfg_cdone; gpio_t fpga_cfg_spi_cs; #endif /* Other CPLD interface GPIO pins */ -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE gpio_t trigger_enable; #endif gpio_t q_invert; @@ -144,7 +145,7 @@ typedef struct { gpio_t synt_rfout_en; // RAD1O /* Praline */ -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t p2_ctrl0; gpio_t p2_ctrl1; gpio_t p1_ctrl0; @@ -157,7 +158,7 @@ typedef struct { gpio_t pps_out; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE /* HackRF One r9 clock control */ gpio_t h1r9_clkin_en; gpio_t h1r9_clkout_en; @@ -169,14 +170,14 @@ typedef struct { #endif /* RAD1O */ -#if defined(RAD1O) +#ifdef IS_RAD1O gpio_t lcd_cs; gpio_t lcd_bl_en; gpio_t lcd_reset; #endif /* Portapack */ -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE gpio_t io_stbx; gpio_t addr; gpio_t lcd_rdx; diff --git a/firmware/common/platform_scu.h b/firmware/common/platform_scu.h index 2c5365e3..1ffb224c 100644 --- a/firmware/common/platform_scu.h +++ b/firmware/common/platform_scu.h @@ -29,6 +29,8 @@ extern "C" { #include +#include "platform_detect.h" + /* * SCU PinMux */ @@ -38,13 +40,13 @@ typedef struct { scu_grp_pin_t PINMUX_LED1; scu_grp_pin_t PINMUX_LED2; scu_grp_pin_t PINMUX_LED3; -#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_FOUR_LEDS scu_grp_pin_t PINMUX_LED4; #endif scu_grp_pin_t PINMUX_EN1V8; scu_grp_pin_t PINMUX_EN1V2; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t PINMUX_EN3V3_AUX_N; scu_grp_pin_t PINMUX_EN3V3_OC_N; #endif @@ -52,7 +54,7 @@ typedef struct { /* GPIO Input PinMux */ scu_grp_pin_t PINMUX_BOOT0; scu_grp_pin_t PINMUX_BOOT1; -#if !defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_NOT_HACKRF_ONE scu_grp_pin_t PINMUX_BOOT2; scu_grp_pin_t PINMUX_BOOT3; #endif @@ -63,7 +65,7 @@ typedef struct { scu_grp_pin_t PINMUX_PP_DIR; /* USB peripheral */ -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER scu_grp_pin_t PINMUX_USB_LED0; scu_grp_pin_t PINMUX_USB_LED1; #endif @@ -75,7 +77,7 @@ typedef struct { scu_grp_pin_t SSP1_CS; /* CPLD JTAG interface */ -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t PINMUX_FPGA_CRESET; scu_grp_pin_t PINMUX_FPGA_CDONE; scu_grp_pin_t PINMUX_FPGA_SPI_CS; @@ -127,15 +129,15 @@ typedef struct { uint32_t XCVR_RXENABLE_PINCFG; uint32_t XCVR_TXENABLE_PINCFG; uint32_t XCVR_CS_PINCFG; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t XCVR_LD; uint32_t XCVR_LD_PINCFG; uint32_t XCVR_RXHP_PINCFG; #endif -#if defined(RAD1O) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_RAD1O) || defined(IS_PRALINE) scu_grp_pin_t XCVR_RXHP; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O scu_grp_pin_t XCVR_B6; scu_grp_pin_t XCVR_B7; #endif @@ -145,7 +147,7 @@ typedef struct { scu_grp_pin_t AD_CS_PINCFG; /* RFFC5071 GPIO serial interface PinMux */ -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O scu_grp_pin_t MIXER_ENX; scu_grp_pin_t MIXER_SCLK; scu_grp_pin_t MIXER_SDATA; @@ -153,11 +155,11 @@ typedef struct { uint32_t MIXER_SCLK_PINCFG; uint32_t MIXER_SDATA_PINCFG; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t MIXER_LD; uint32_t MIXER_LD_PINCFG; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O scu_grp_pin_t VCO_CE; scu_grp_pin_t VCO_SCLK; scu_grp_pin_t VCO_SDATA; @@ -168,15 +170,15 @@ typedef struct { #endif /* RF LDO control */ -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER scu_grp_pin_t RF_LDO_ENABLE; #endif /* RF supply (VAA) control */ -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) +#if defined(IS_PRALINE) || defined(IS_HACKRF_ONE) scu_grp_pin_t NO_VAA_ENABLE; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O scu_grp_pin_t VAA_ENABLE; #endif @@ -189,7 +191,7 @@ typedef struct { scu_grp_pin_t FLASH_WP; /* RF switch control */ -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t TX_EN; scu_grp_pin_t MIX_EN_N; scu_grp_pin_t MIX_EN_N_R1_0; @@ -198,7 +200,7 @@ typedef struct { scu_grp_pin_t ANT_BIAS_EN_N; scu_grp_pin_t ANT_BIAS_OC_N; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE scu_grp_pin_t HP; scu_grp_pin_t LP; scu_grp_pin_t TX_MIX_BP; @@ -213,7 +215,7 @@ typedef struct { scu_grp_pin_t RX_AMP; scu_grp_pin_t NO_RX_AMP_PWR; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O scu_grp_pin_t BY_AMP; scu_grp_pin_t BY_AMP_N; scu_grp_pin_t TX_RX; @@ -227,7 +229,7 @@ typedef struct { #endif /* Praline */ -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE scu_grp_pin_t P2_CTRL0; scu_grp_pin_t P2_CTRL1; scu_grp_pin_t P1_CTRL0; @@ -252,7 +254,7 @@ typedef struct { #endif /* HackRF One r9 */ -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE scu_grp_pin_t H1R9_CLKIN_EN; scu_grp_pin_t H1R9_CLKOUT_EN; scu_grp_pin_t H1R9_MCU_CLK_EN; diff --git a/firmware/common/radio.c b/firmware/common/radio.c index a98587f4..760351ac 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -33,7 +33,7 @@ #include "rf_path.h" #include "transceiver_mode.h" #include "tuning.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "fpga.h" #include "tune_config.h" #endif @@ -294,7 +294,7 @@ static fp_40_24_t digital_from_analog_rf( return drf; } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE /* * Convert center frequency of digital baseband (as seen by MCU) to center * frequency of analog baseband (as seen by ADC/DAC). @@ -354,7 +354,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) const uint64_t requested_if = bank[RADIO_FREQUENCY_IF]; const uint64_t requested_lo = bank[RADIO_FREQUENCY_LO]; const uint64_t requested_img_reject = bank[RADIO_IMAGE_REJECT]; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE const uint64_t requested_rotation = bank[RADIO_ROTATION]; #endif diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 5043bfe8..6a8e3caf 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -23,16 +23,13 @@ #include "rf_path.h" -#if !defined(JAWBREAKER) - #include -#endif - #include "hackrf_core.h" #include "max283x.h" #include "max5864.h" #include "mixer.h" #include "platform_detect.h" -#if !defined(JAWBREAKER) +#ifdef IS_NOT_JAWBREAKER + #include #include "platform_scu.h" #endif @@ -92,7 +89,7 @@ #define SWITCHCTRL_ANT_PWR (1 << 6) /* turn on antenna port power */ -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) { board_id_t board_id = detected_platform(); @@ -186,7 +183,7 @@ static void switchctrl_set_hackrf_one(rf_path_t* const rf_path, uint8_t ctrl) } #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl) { if (ctrl & SWITCHCTRL_TX) { @@ -227,7 +224,7 @@ static void switchctrl_set_praline(rf_path_t* const rf_path, uint8_t ctrl) } #endif -#if defined(RAD1O) +#ifdef IS_RAD1O static void switchctrl_set_rad1o(rf_path_t* const rf_path, uint8_t ctrl) { if (ctrl & SWITCHCTRL_TX) { @@ -319,11 +316,9 @@ static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) void rf_path_pin_setup(rf_path_t* const rf_path) { -#if defined(JAWBREAKER) +#ifdef JAWBREAKER (void) rf_path; -#endif - -#if !defined(JAWBREAKER) +#else const platform_scu_t* scu = platform_scu(); #endif @@ -530,7 +525,7 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d max283x_rx(&max283x); break; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE case RF_PATH_DIRECTION_TX_CALIBRATION: case RF_PATH_DIRECTION_RX_CALIBRATION: rf_path->switchctrl &= ~SWITCHCTRL_TX; diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index 64634b3d..42dd91cb 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -25,7 +25,9 @@ #include -#if !defined(JAWBREAKER) +#include "platform_detect.h" // IWYU pragma: keep + +#ifdef IS_NOT_JAWBREAKER #include "gpio.h" #endif @@ -33,7 +35,7 @@ typedef enum { RF_PATH_DIRECTION_OFF, RF_PATH_DIRECTION_RX, RF_PATH_DIRECTION_TX, -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE RF_PATH_DIRECTION_TX_CALIBRATION, RF_PATH_DIRECTION_RX_CALIBRATION, #endif @@ -49,7 +51,7 @@ typedef struct { uint8_t switchctrl; struct { -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE gpio_t gpio_hp; gpio_t gpio_lp; gpio_t gpio_tx_mix_bp; @@ -67,7 +69,7 @@ typedef struct { gpio_t gpio_h1r9_no_ant_pwr; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O gpio_t gpio_tx_rx_n; gpio_t gpio_tx_rx; gpio_t gpio_by_mix; @@ -81,7 +83,7 @@ typedef struct { gpio_t gpio_rx_lna; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t gpio_tx_en; gpio_t gpio_mix_en_n; gpio_t gpio_lpf_en; diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index ab0717ea..2a1866d2 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -35,17 +35,14 @@ #include #include -#if defined(PRALINE) || defined(UNIVERSAL) - #include -#endif - #include "delay.h" #include "fixed_point.h" #include "platform_detect.h" #include "rffc5071.h" #include "rffc5071_regs.def" // private register def macros #include "selftest.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE + #include #include "platform_scu.h" #endif @@ -370,7 +367,7 @@ void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t gpo) rffc5071_regs_commit(drv); } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state) { // This is only supported on Praline hardware. diff --git a/firmware/common/rffc5071.h b/firmware/common/rffc5071.h index 6a0c4ce2..c6b2c0c2 100644 --- a/firmware/common/rffc5071.h +++ b/firmware/common/rffc5071.h @@ -27,6 +27,7 @@ #include "fixed_point.h" #include "gpio.h" +#include "platform_detect.h" // IWYU pragma: keep #include "spi_bus.h" /* 31 registers, each containing 16 bits of data. */ @@ -35,7 +36,7 @@ typedef struct { spi_bus_t* const bus; gpio_t gpio_reset; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE gpio_t gpio_ld; #endif uint16_t regs[RFFC5071_NUM_REGS]; @@ -70,7 +71,7 @@ extern void rffc5071_enable(rffc5071_driver_t* const drv); extern void rffc5071_disable(rffc5071_driver_t* const drv); extern void rffc5071_set_gpo(rffc5071_driver_t* const drv, uint8_t); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE extern bool rffc5071_poll_ld(rffc5071_driver_t* const drv, uint8_t* prelock_state); #endif extern bool rffc5071_check_lock(rffc5071_driver_t* const drv); diff --git a/firmware/common/selftest.h b/firmware/common/selftest.h index 8223a582..9fca53a6 100644 --- a/firmware/common/selftest.h +++ b/firmware/common/selftest.h @@ -24,6 +24,8 @@ #include #include +#include "platform_detect.h" // IWYU pragma: keep + #define NUM_LOCK_ATTEMPTS 3 enum { @@ -37,10 +39,10 @@ typedef uint8_t test_result_t; typedef struct { uint16_t mixer_id; -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O bool mixer_locks[NUM_LOCK_ATTEMPTS]; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE uint16_t max2831_mux_rssi_1; uint16_t max2831_mux_temp; uint16_t max2831_mux_rssi_2; @@ -52,7 +54,7 @@ typedef struct { uint8_t max283x_readback_total_registers; uint8_t si5351_rev_id; bool si5351_readback_ok; -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE test_result_t fpga_image_load; test_result_t fpga_spi; test_result_t sgpio_rx; diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index 4aa98265..9d03924a 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -31,7 +31,7 @@ #include "platform_detect.h" #include "platform_scu.h" #include "sgpio.h" -#if !defined(PRALINE) +#ifdef IS_NOT_PRALINE #include "hackrf_core.h" #endif diff --git a/firmware/common/sgpio.h b/firmware/common/sgpio.h index f6fdf213..409cef0c 100644 --- a/firmware/common/sgpio.h +++ b/firmware/common/sgpio.h @@ -26,6 +26,7 @@ #include #include "gpio.h" +#include "platform_detect.h" // IWYU pragma: keep typedef enum { SGPIO_DIRECTION_RX, @@ -34,7 +35,7 @@ typedef enum { typedef struct { gpio_t gpio_q_invert; -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE gpio_t gpio_trigger_enable; #endif bool slice_mode_multislice; diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index a1466393..0acff63e 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -23,16 +23,13 @@ #include #include -#if defined(HACKRF_ONE) || defined(UNIVERSAL) - #include -#endif - #include "clkin.h" #include "delay.h" #include "platform_detect.h" #include "selftest.h" #include "si5351c.h" -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE + #include #include "gpio.h" #include "platform_gpio.h" #include "platform_scu.h" diff --git a/firmware/common/tune_config.h b/firmware/common/tune_config.h index 34c692e6..dd81f62d 100644 --- a/firmware/common/tune_config.h +++ b/firmware/common/tune_config.h @@ -21,7 +21,9 @@ #pragma once -#if defined(PRALINE) || defined(UNIVERSAL) +#include "platform_detect.h" + +#ifdef IS_PRALINE #include "fpga.h" typedef struct { diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 653a477a..b79dcdec 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -48,14 +48,14 @@ #include #include #include -#if defined(PRALINE) || defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_EXPANSION_COMPATIBLE #include #endif -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O #include #include #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include #if !(defined(DFU_MODE) || defined(RAM_MODE)) #include @@ -63,7 +63,7 @@ #include #endif #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE #include #include #endif @@ -81,10 +81,10 @@ #include "usb_descriptor.h" #include "usb_device.h" #include "usb_endpoint.h" -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include "usb_api_praline.h" #endif -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE #include "usb_api_cpld.h" #endif @@ -102,7 +102,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_si5351c, usb_vendor_request_set_sample_rate_frac, usb_vendor_request_set_baseband_filter_bandwidth, -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O usb_vendor_request_write_rffc5071, usb_vendor_request_read_rffc5071, #else @@ -122,7 +122,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_set_vga_gain, usb_vendor_request_set_txvga_gain, NULL, // was set_if_freq -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE) usb_vendor_request_set_antenna_enable, #else NULL, @@ -139,7 +139,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_spiflash_status, usb_vendor_request_spiflash_clear_status, usb_vendor_request_operacake_gpio_test, -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE usb_vendor_request_cpld_checksum, #else NULL, @@ -156,7 +156,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_read_supported_platform, usb_vendor_request_set_leds, usb_vendor_request_user_config_set_bias_t_opts, -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE usb_vendor_request_write_fpga_reg, usb_vendor_request_read_fpga_reg, usb_vendor_request_p2_ctrl, @@ -252,7 +252,7 @@ void usb_set_descriptor_by_serial_number(void) } } -#if !defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_NOT_PRALINE static bool cpld_jtag_sram_load(jtag_t* const jtag) { cpld_jtag_take(jtag); @@ -280,7 +280,7 @@ static void m0_rom_to_ram(void) memcpy(dest, (uint32_t*) (base + src), len); } -#if (defined(PRALINE) || defined(UNIVERSAL)) && !(defined(DFU_MODE) || defined(RAM_MODE)) +#if defined(IS_PRALINE) && !(defined(DFU_MODE) || defined(RAM_MODE)) extern uint32_t _binary_fpga_bin_start; void fpga_loader_setup(void) @@ -347,9 +347,11 @@ void radio_changed(const uint32_t changed) if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { hackrf_ui()->set_frequency(freq / FP_ONE_HZ); } - IF_EXPANSION_COMPATIBLE ( +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { operacake_set_range(freq / FP_ONE_MHZ); - ) + } +#endif } if (changed & (1 << RADIO_IMAGE_REJECT)) { if (opmode != TRANSCEIVER_MODE_RX_SWEEP) { @@ -575,7 +577,7 @@ int main(void) rf_path_init(&rf_path); -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O rffc5071_lock_test(&mixer.rffc5071); #endif diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index 393b102d..920ad749 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -20,7 +20,6 @@ * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ - #include "usb_api_board_info.h" #include @@ -34,7 +33,7 @@ #include #include #include -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE #include #include #endif diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index d17a87d8..b9498dab 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -21,7 +21,6 @@ * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ - #include "usb_api_register.h" #include @@ -36,10 +35,10 @@ #include #include #include -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE #include #endif -#if !defined(RAD1O) +#ifdef IS_NOT_RAD1O #include #include #endif @@ -290,7 +289,7 @@ usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( return USB_REQUEST_STATUS_OK; } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) diff --git a/firmware/hackrf_usb/usb_api_register.h b/firmware/hackrf_usb/usb_api_register.h index 58a84aff..aef02d03 100644 --- a/firmware/hackrf_usb/usb_api_register.h +++ b/firmware/hackrf_usb/usb_api_register.h @@ -23,6 +23,8 @@ #pragma once +#include "platform_detect.h" // IWYU pragma: keep + #include #include @@ -56,7 +58,7 @@ usb_request_status_t usb_vendor_request_set_leds( usb_request_status_t usb_vendor_request_user_config_set_bias_t_opts( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE usb_request_status_t usb_vendor_request_write_fpga_reg( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index 403f3753..4c812366 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -19,11 +19,10 @@ * Boston, MA 02110-1301, USA. */ +#include "usb_api_selftest.h" + #include #include -#if !defined(RAD1O) - #include -#endif #include #include @@ -34,7 +33,9 @@ #include #include -#include "usb_api_selftest.h" +#ifdef IS_NOT_RAD1O + #include +#endif static char* itoa(int val, int base) { @@ -61,7 +62,7 @@ void append(char** dest, size_t* capacity, const char* str) } } -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE static const char* test_result_to_str(test_result_t result) { switch (result) { diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index f213f484..36531ebc 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -26,17 +26,9 @@ #include -#define USB_VENDOR_ID (0x1D50) +#include "platform_detect.h" -#if (defined HACKRF_ONE || defined PRALINE) - #define USB_PRODUCT_ID (0x6089) -#elif JAWBREAKER - #define USB_PRODUCT_ID (0x604B) -#elif RAD1O - #define USB_PRODUCT_ID (0xCC15) -#else - #define USB_PRODUCT_ID (0xFFFF) -#endif +#define USB_VENDOR_ID (0x1D50) #define USB_API_VERSION (0x0111) @@ -51,7 +43,7 @@ #define USB_STRING_LANGID (0x0409) -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE) uint8_t usb_descriptor_device_hackrf[] = { 18, // bLength USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType @@ -69,7 +61,7 @@ uint8_t usb_descriptor_device_hackrf[] = { 0x01 // bNumConfigurations }; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER uint8_t usb_descriptor_device_jawbreaker[] = { 18, // bLength USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType @@ -87,7 +79,7 @@ uint8_t usb_descriptor_device_jawbreaker[] = { 0x01 // bNumConfigurations }; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O uint8_t usb_descriptor_device_rad1o[] = { 18, // bLength USB_DESCRIPTOR_TYPE_DEVICE, // bDescriptorType @@ -223,7 +215,7 @@ uint8_t usb_descriptor_string_manufacturer[] = { 's', 0x00, }; -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE uint8_t usb_descriptor_string_product_hackrf_one[] = { 22, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType @@ -239,7 +231,7 @@ uint8_t usb_descriptor_string_product_hackrf_one[] = { 'e', 0x00, }; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE uint8_t usb_descriptor_string_product_praline[] = { 22, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType @@ -255,7 +247,7 @@ uint8_t usb_descriptor_string_product_praline[] = { 'o', 0x00, }; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER uint8_t usb_descriptor_string_product_jawbreaker[] = { 36, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType @@ -278,7 +270,7 @@ uint8_t usb_descriptor_string_product_jawbreaker[] = { 'r', 0x00, }; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O uint8_t usb_descriptor_string_product_rad1o[] = { 12, // bLength USB_DESCRIPTOR_TYPE_STRING, // bDescriptorType @@ -329,7 +321,7 @@ uint8_t usb_descriptor_string_serial_number[] = { uint8_t usb_descriptor_string_serial_number[USB_DESCRIPTOR_STRING_SERIAL_BUF_LEN]; #endif -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE uint8_t* usb_descriptor_strings_hackrf_one[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, @@ -339,7 +331,7 @@ uint8_t* usb_descriptor_strings_hackrf_one[] = { 0, // TERMINATOR }; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE uint8_t* usb_descriptor_strings_praline[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, @@ -349,7 +341,7 @@ uint8_t* usb_descriptor_strings_praline[] = { 0, // TERMINATOR }; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER uint8_t* usb_descriptor_strings_jawbreaker[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, @@ -359,7 +351,7 @@ uint8_t* usb_descriptor_strings_jawbreaker[] = { 0, // TERMINATOR }; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O uint8_t* usb_descriptor_strings_rad1o[] = { usb_descriptor_string_languages, usb_descriptor_string_manufacturer, diff --git a/firmware/hackrf_usb/usb_descriptor.h b/firmware/hackrf_usb/usb_descriptor.h index 93f3eb78..ef41d6ac 100644 --- a/firmware/hackrf_usb/usb_descriptor.h +++ b/firmware/hackrf_usb/usb_descriptor.h @@ -24,13 +24,15 @@ #include -#if defined(HACKRF_ONE) || defined(PRALINE) || defined(UNIVERSAL) +#include "platform_detect.h" + +#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE) extern uint8_t usb_descriptor_device_hackrf[]; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER extern uint8_t usb_descriptor_device_jawbreaker[]; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O extern uint8_t usb_descriptor_device_rad1o[]; #endif extern uint8_t usb_descriptor_device_qualifier[]; @@ -38,16 +40,16 @@ extern uint8_t usb_descriptor_configuration_full_speed[]; extern uint8_t usb_descriptor_configuration_high_speed[]; extern uint8_t usb_descriptor_string_languages[]; extern uint8_t usb_descriptor_string_manufacturer[]; -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE extern uint8_t usb_descriptor_string_product_hackrf_one[]; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE extern uint8_t usb_descriptor_string_product_praline[]; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER extern uint8_t usb_descriptor_string_product_jawbreaker[]; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O extern uint8_t usb_descriptor_string_product_rad1o[]; #endif diff --git a/firmware/hackrf_usb/usb_device.c b/firmware/hackrf_usb/usb_device.c index 4ad46424..a2af1158 100644 --- a/firmware/hackrf_usb/usb_device.c +++ b/firmware/hackrf_usb/usb_device.c @@ -25,6 +25,7 @@ #include +#include "platform_detect.h" #include "usb_descriptor.h" usb_configuration_t usb_configuration_high_speed = { @@ -47,7 +48,7 @@ usb_configuration_t* usb_configurations[] = { usb_device_t usb_device; -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE const usb_device_t usb_device_hackrf_one = { .descriptor = usb_descriptor_device_hackrf, .descriptor_strings = usb_descriptor_strings_hackrf_one, @@ -58,7 +59,7 @@ const usb_device_t usb_device_hackrf_one = { .wcid_feature_descriptor = wcid_feature_descriptor, }; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE const usb_device_t usb_device_praline = { .descriptor = usb_descriptor_device_hackrf, .descriptor_strings = usb_descriptor_strings_praline, @@ -69,7 +70,7 @@ const usb_device_t usb_device_praline = { .wcid_feature_descriptor = wcid_feature_descriptor, }; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER const usb_device_t usb_device_jawbreaker = { .descriptor = usb_descriptor_device_jawbreaker, .descriptor_strings = usb_descriptor_strings_jawbreaker, @@ -80,7 +81,7 @@ const usb_device_t usb_device_jawbreaker = { .wcid_feature_descriptor = wcid_feature_descriptor, }; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O const usb_device_t usb_device_rad1o = { .descriptor = usb_descriptor_device_rad1o, .descriptor_strings = usb_descriptor_strings_rad1o, diff --git a/firmware/hackrf_usb/usb_device.h b/firmware/hackrf_usb/usb_device.h index 528d084d..f442db37 100644 --- a/firmware/hackrf_usb/usb_device.h +++ b/firmware/hackrf_usb/usb_device.h @@ -25,17 +25,19 @@ #include +#include "platform_detect.h" + extern usb_device_t usb_device; -#if defined(HACKRF_ONE) || defined(UNIVERSAL) +#ifdef IS_HACKRF_ONE extern const usb_device_t usb_device_hackrf_one; #endif -#if defined(PRALINE) || defined(UNIVERSAL) +#ifdef IS_PRALINE extern const usb_device_t usb_device_praline; #endif -#if defined(JAWBREAKER) +#ifdef IS_JAWBREAKER extern const usb_device_t usb_device_jawbreaker; #endif -#if defined(RAD1O) +#ifdef IS_RAD1O extern const usb_device_t usb_device_rad1o; #endif From f2962ce9b10e85d8d709aa05cd425bea48fd50c1 Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Tue, 21 Apr 2026 14:24:41 -0400 Subject: [PATCH 055/161] By default, turn off bias tee when radio idle This matches the behavior before #1648. --- firmware/common/radio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 760351ac..a0aafb4c 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -53,6 +53,7 @@ void radio_init(radio_t* const radio) radio->config[RADIO_BANK_IDLE][RADIO_OPMODE] = TRANSCEIVER_MODE_OFF; radio->config[RADIO_BANK_RX][RADIO_OPMODE] = TRANSCEIVER_MODE_RX; radio->config[RADIO_BANK_TX][RADIO_OPMODE] = TRANSCEIVER_MODE_TX; + radio->config[RADIO_BANK_IDLE][RADIO_BIAS_TEE] = false; radio->regs_dirty = 0; } From 4bd414194a5ead5b4adcec23afdcab2a0f33534b Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Tue, 21 Apr 2026 17:11:31 -0400 Subject: [PATCH 056/161] Fix explicit TX interpolation --- firmware/common/radio.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 760351ac..6c932ec9 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -158,14 +158,13 @@ static inline uint8_t compute_resample_log( static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) { fp_28_36_t rate, afe_rate, previous_rate, previous_afe_rate; - uint64_t previous_n; + uint64_t previous_n, requested_n; uint8_t n = 0; bool new_afe_rate = false; bool new_rate = false; bool new_n = false; const uint64_t requested_rate = bank[RADIO_SAMPLE_RATE]; - const uint64_t requested_n = bank[RADIO_RESAMPLE_RX]; if (requested_rate != RADIO_UNSET) { rate = MIN(requested_rate, MAX_MCU_RATE); @@ -193,6 +192,7 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) switch (opmode) { case TRANSCEIVER_MODE_TX: case TRANSCEIVER_MODE_SS: + requested_n = bank[RADIO_RESAMPLE_TX]; n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) { #ifdef IS_PRALINE @@ -204,6 +204,7 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) } break; default: + requested_n = bank[RADIO_RESAMPLE_RX]; n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) { #ifdef IS_PRALINE From 230bfbc36f16f15acb2a9380c9e7e814e490200f Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Wed, 22 Apr 2026 10:51:54 -0400 Subject: [PATCH 057/161] Allow explicit resampling ratio of 1 --- firmware/common/radio.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 6c932ec9..4f5eb683 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -127,26 +127,39 @@ static uint32_t radio_update_direction(radio_t* const radio, uint64_t* bank) return (1 << RADIO_OPMODE); } -#define MAX_AFE_RATE_HZ (40000000UL) +#define ABSOLUTE_MIN_AFE_RATE SR_FP_KHZ(200) +#define ABSOLUTE_MAX_AFE_RATE SR_FP_KHZ(43600) +#define MAX_SUPPORTED_AFE_RATE SR_FP_KHZ(40000) static inline uint8_t compute_resample_log( - const uint32_t sample_rate_hz, + const fp_28_36_t sample_rate, const uint64_t requested_n) { if (detected_platform() != BOARD_ID_PRALINE) { return 0; } - const uint8_t min_n = 1; - uint8_t max_n = 5; + uint8_t n = 0; // resampling ratio is 2**n + const uint8_t max_n = 5; + fp_28_36_t afe_rate = 0; - if (requested_n != RADIO_UNSET) { - max_n = MIN(max_n, requested_n); - } - uint8_t n = min_n; // resampling ratio is 2**n - uint32_t afe_rate_x2 = 4 * sample_rate_hz; - while ((afe_rate_x2 <= MAX_AFE_RATE_HZ) && (n < max_n)) { - afe_rate_x2 <<= 1; - n++; + if (requested_n == RADIO_UNSET) { + /* Find highest supported resampling ratio of at least 2 (n=1). */ + while (((afe_rate * 2) <= MAX_SUPPORTED_AFE_RATE) && (n < max_n)) { + n++; + afe_rate = sample_rate << n; + } + } else { + /* Restrict requested resampling ratio within allowable limits. */ + n = MIN(max_n, requested_n); + afe_rate = sample_rate << n; + while ((afe_rate < ABSOLUTE_MIN_AFE_RATE) && (n < max_n)) { + n++; + afe_rate <<= 1; + } + while ((afe_rate > ABSOLUTE_MAX_AFE_RATE) && (n > 0)) { + n--; + afe_rate >>= 1; + } } return n; } @@ -193,7 +206,7 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) case TRANSCEIVER_MODE_TX: case TRANSCEIVER_MODE_SS: requested_n = bank[RADIO_RESAMPLE_TX]; - n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); + n = compute_resample_log(rate, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) { #ifdef IS_PRALINE if (IS_PRALINE) { @@ -205,7 +218,7 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) break; default: requested_n = bank[RADIO_RESAMPLE_RX]; - n = compute_resample_log(rate / SR_FP_ONE_HZ, requested_n); + n = compute_resample_log(rate, requested_n); if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) { #ifdef IS_PRALINE if (IS_PRALINE) { From 902ae831bfe5b93277a816d012b44e2d61e7b61c Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 19 Mar 2026 11:30:54 +0200 Subject: [PATCH 058/161] firmware: move cpu clock logic to cpu_clock.h|c --- firmware/common/cpu_clock.c | 252 +++++++++++++++++++++++++++++++ firmware/common/cpu_clock.h | 34 +++++ firmware/common/hackrf_core.c | 225 +-------------------------- firmware/common/hackrf_core.h | 3 +- firmware/hackrf-common.cmake | 1 + firmware/hackrf_usb/hackrf_usb.c | 1 + 6 files changed, 291 insertions(+), 225 deletions(-) create mode 100644 firmware/common/cpu_clock.c create mode 100644 firmware/common/cpu_clock.h diff --git a/firmware/common/cpu_clock.c b/firmware/common/cpu_clock.c new file mode 100644 index 00000000..1bbd9571 --- /dev/null +++ b/firmware/common/cpu_clock.c @@ -0,0 +1,252 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "cpu_clock.h" + +#include + +#include +#if defined(IS_JAWBREAKER) || defined(IS_HACKRF_ONE) || defined(IS_PRALINE) + #include +#endif + +#include "delay.h" +#include "hackrf_core.h" +#include "i2c_bus.h" +#ifdef IS_NOT_RAD1O + #include "platform_detect.h" +#endif + +/* +Configure PLL1 (Main MCU Clock) to max speed (204MHz). +Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1. +This function shall be called after cpu_clock_init(). +*/ +static void cpu_clock_pll1_max_speed(void) +{ + uint32_t reg_val; + + /* This function implements the sequence recommended in: + * UM10503 Rev 2.4 (Aug 2018), section 13.2.1.1, page 167. */ + + /* 1. Select the IRC as BASE_M4_CLK source. */ + reg_val = CGU_BASE_M4_CLK; + reg_val &= ~CGU_BASE_M4_CLK_CLK_SEL_MASK; + reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_IRC) | CGU_BASE_M4_CLK_AUTOBLOCK(1); + CGU_BASE_M4_CLK = reg_val; + + /* 2. Enable the crystal oscillator. */ + CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_ENABLE_MASK; + + /* 3. Wait 250us. */ + delay_us_at_mhz(250, 12); + + /* 4. Set the AUTOBLOCK bit. */ + CGU_PLL1_CTRL |= CGU_PLL1_CTRL_AUTOBLOCK(1); + + /* 5. Reconfigure PLL1 to produce the final output frequency, with the + * crystal oscillator as clock source. */ + reg_val = CGU_PLL1_CTRL; + // clang-format off + reg_val &= ~( CGU_PLL1_CTRL_CLK_SEL_MASK | + CGU_PLL1_CTRL_PD_MASK | + CGU_PLL1_CTRL_FBSEL_MASK | + CGU_PLL1_CTRL_BYPASS_MASK | + CGU_PLL1_CTRL_DIRECT_MASK | + CGU_PLL1_CTRL_PSEL_MASK | + CGU_PLL1_CTRL_MSEL_MASK | + CGU_PLL1_CTRL_NSEL_MASK ); + /* Set PLL1 up to 12MHz * 17 = 204MHz. + * Direct mode: FCLKOUT = FCCO = M*(FCLKIN/N) */ + reg_val |= CGU_PLL1_CTRL_CLK_SEL(CGU_SRC_XTAL) | + CGU_PLL1_CTRL_PSEL(0) | + CGU_PLL1_CTRL_NSEL(0) | + CGU_PLL1_CTRL_MSEL(16) | + CGU_PLL1_CTRL_FBSEL(0) | + CGU_PLL1_CTRL_DIRECT(1); + // clang-format on + CGU_PLL1_CTRL = reg_val; + + /* 6. Wait for PLL1 to lock. */ + while (!(CGU_PLL1_STAT & CGU_PLL1_STAT_LOCK_MASK)) {} + + /* 7. Set the PLL1 P-divider to divide by 2 (DIRECT=0, PSEL=0). */ + CGU_PLL1_CTRL &= ~CGU_PLL1_CTRL_DIRECT_MASK; + + /* 8. Select PLL1 as BASE_M4_CLK source. */ + reg_val = CGU_BASE_M4_CLK; + reg_val &= ~CGU_BASE_M4_CLK_CLK_SEL_MASK; + reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_PLL1); + CGU_BASE_M4_CLK = reg_val; + + /* 9. Wait 50us. */ + delay_us_at_mhz(50, 102); + + /* 10. Set the PLL1 P-divider to direct output mode (DIRECT=1). */ + CGU_PLL1_CTRL |= CGU_PLL1_CTRL_DIRECT_MASK; +} + +/* clock startup for LPC4320 configure PLL1 to max speed (204MHz). +Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1. */ +void cpu_clock_init(void) +{ + /* use IRC as clock source for APB1 (including I2C0) */ + CGU_BASE_APB1_CLK = CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_IRC); + + /* use IRC as clock source for APB3 */ + CGU_BASE_APB3_CLK = CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_IRC); + + //FIXME disable I2C + /* Kick I2C0 down to 400kHz when we switch over to APB1 clock = 204MHz */ + i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); + + /* + * 12MHz clock is entering LPC XTAL1/OSC input now. + * On HackRF One and Jawbreaker, there is a 12 MHz crystal at the LPC. + * Set up PLL1 to run from XTAL1 input. + */ + + //FIXME a lot of the details here should be in a CGU driver + + /* set xtal oscillator to low frequency mode */ + CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_HF_MASK; + + cpu_clock_pll1_max_speed(); + + /* use XTAL_OSC as clock source for APB1 */ + CGU_BASE_APB1_CLK = + CGU_BASE_APB1_CLK_AUTOBLOCK(1) | CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_XTAL); + + /* use XTAL_OSC as clock source for APB3 */ + CGU_BASE_APB3_CLK = + CGU_BASE_APB3_CLK_AUTOBLOCK(1) | CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_XTAL); + + /* use XTAL_OSC as clock source for PLL0USB */ + CGU_PLL0USB_CTRL = CGU_PLL0USB_CTRL_PD(1) | CGU_PLL0USB_CTRL_AUTOBLOCK(1) | + CGU_PLL0USB_CTRL_CLK_SEL(CGU_SRC_XTAL); + while (CGU_PLL0USB_STAT & CGU_PLL0USB_STAT_LOCK_MASK) {} + + /* configure PLL0USB to produce 480 MHz clock from 12 MHz XTAL_OSC */ + /* Values from User Manual v1.4 Table 94, for 12MHz oscillator. */ + CGU_PLL0USB_MDIV = 0x06167FFA; + CGU_PLL0USB_NP_DIV = 0x00302062; + CGU_PLL0USB_CTRL |= + (CGU_PLL0USB_CTRL_PD(1) | CGU_PLL0USB_CTRL_DIRECTI(1) | + CGU_PLL0USB_CTRL_DIRECTO(1) | CGU_PLL0USB_CTRL_CLKEN(1)); + + /* power on PLL0USB and wait until stable */ + CGU_PLL0USB_CTRL &= ~CGU_PLL0USB_CTRL_PD_MASK; + while (!(CGU_PLL0USB_STAT & CGU_PLL0USB_STAT_LOCK_MASK)) {} + + /* use PLL0USB as clock source for USB0 */ + CGU_BASE_USB0_CLK = CGU_BASE_USB0_CLK_AUTOBLOCK(1) | + CGU_BASE_USB0_CLK_CLK_SEL(CGU_SRC_PLL0USB); + + /* Switch peripheral clock over to use PLL1 (204MHz) */ + CGU_BASE_PERIPH_CLK = CGU_BASE_PERIPH_CLK_AUTOBLOCK(1) | + CGU_BASE_PERIPH_CLK_CLK_SEL(CGU_SRC_PLL1); + + /* Switch APB1 clock over to use PLL1 (204MHz) */ + CGU_BASE_APB1_CLK = + CGU_BASE_APB1_CLK_AUTOBLOCK(1) | CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_PLL1); + + /* Switch APB3 clock over to use PLL1 (204MHz) */ + CGU_BASE_APB3_CLK = + CGU_BASE_APB3_CLK_AUTOBLOCK(1) | CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_PLL1); + + CGU_BASE_SSP0_CLK = + CGU_BASE_SSP0_CLK_AUTOBLOCK(1) | CGU_BASE_SSP0_CLK_CLK_SEL(CGU_SRC_PLL1); + + CGU_BASE_SSP1_CLK = + CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); + +#ifdef IS_NOT_RAD1O + if (IS_NOT_RAD1O) { + /* Disable unused clocks */ + /* Start with PLLs */ + CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); + + /* Dividers */ + CGU_IDIVA_CTRL = CGU_IDIVA_CTRL_PD(1); + CGU_IDIVB_CTRL = CGU_IDIVB_CTRL_PD(1); + CGU_IDIVC_CTRL = CGU_IDIVC_CTRL_PD(1); + CGU_IDIVD_CTRL = CGU_IDIVD_CTRL_PD(1); + CGU_IDIVE_CTRL = CGU_IDIVE_CTRL_PD(1); + + /* Base clocks */ + CGU_BASE_SPIFI_CLK = + CGU_BASE_SPIFI_CLK_PD(1); /* SPIFI is only used at boot */ + CGU_BASE_USB1_CLK = + CGU_BASE_USB1_CLK_PD(1); /* USB1 is not exposed on HackRF */ + CGU_BASE_PHY_RX_CLK = CGU_BASE_PHY_RX_CLK_PD(1); + CGU_BASE_PHY_TX_CLK = CGU_BASE_PHY_TX_CLK_PD(1); + CGU_BASE_LCD_CLK = CGU_BASE_LCD_CLK_PD(1); + CGU_BASE_VADC_CLK = CGU_BASE_VADC_CLK_PD(1); + CGU_BASE_SDIO_CLK = CGU_BASE_SDIO_CLK_PD(1); + CGU_BASE_UART0_CLK = CGU_BASE_UART0_CLK_PD(1); + CGU_BASE_UART1_CLK = CGU_BASE_UART1_CLK_PD(1); + CGU_BASE_UART2_CLK = CGU_BASE_UART2_CLK_PD(1); + CGU_BASE_UART3_CLK = CGU_BASE_UART3_CLK_PD(1); + CGU_BASE_OUT_CLK = CGU_BASE_OUT_CLK_PD(1); + CGU_BASE_AUDIO_CLK = CGU_BASE_AUDIO_CLK_PD(1); + CGU_BASE_CGU_OUT0_CLK = CGU_BASE_CGU_OUT0_CLK_PD(1); + CGU_BASE_CGU_OUT1_CLK = CGU_BASE_CGU_OUT1_CLK_PD(1); + + /* Disable unused peripheral clocks */ + CCU1_CLK_APB1_CAN1_CFG = 0; + CCU1_CLK_APB1_I2S_CFG = 0; + CCU1_CLK_APB1_MOTOCONPWM_CFG = 0; + //CCU1_CLK_APB3_ADC0_CFG = 0; + CCU1_CLK_APB3_ADC1_CFG = 0; + CCU1_CLK_APB3_CAN0_CFG = 0; + CCU1_CLK_APB3_DAC_CFG = 0; + //CCU1_CLK_M4_DMA_CFG = 0; + CCU1_CLK_M4_EMC_CFG = 0; + CCU1_CLK_M4_EMCDIV_CFG = 0; + CCU1_CLK_M4_ETHERNET_CFG = 0; + CCU1_CLK_M4_LCD_CFG = 0; + CCU1_CLK_M4_QEI_CFG = 0; + CCU1_CLK_M4_RITIMER_CFG = 0; + // CCU1_CLK_M4_SCT_CFG = 0; + CCU1_CLK_M4_SDIO_CFG = 0; + CCU1_CLK_M4_SPIFI_CFG = 0; + CCU1_CLK_M4_TIMER0_CFG = 0; + //CCU1_CLK_M4_TIMER1_CFG = 0; + //CCU1_CLK_M4_TIMER2_CFG = 0; + CCU1_CLK_M4_TIMER3_CFG = 0; + CCU1_CLK_M4_UART1_CFG = 0; + CCU1_CLK_M4_USART0_CFG = 0; + CCU1_CLK_M4_USART2_CFG = 0; + CCU1_CLK_M4_USART3_CFG = 0; + CCU1_CLK_M4_USB1_CFG = 0; + CCU1_CLK_M4_VADC_CFG = 0; + // CCU1_CLK_SPIFI_CFG = 0; + // CCU1_CLK_USB1_CFG = 0; + // CCU1_CLK_VADC_CFG = 0; + // CCU2_CLK_APB0_UART1_CFG = 0; + // CCU2_CLK_APB0_USART0_CFG = 0; + // CCU2_CLK_APB2_USART2_CFG = 0; + // CCU2_CLK_APB2_USART3_CFG = 0; + // CCU2_CLK_APLL_CFG = 0; + // CCU2_CLK_SDIO_CFG = 0; + } +#endif +} diff --git a/firmware/common/cpu_clock.h b/firmware/common/cpu_clock.h new file mode 100644 index 00000000..b0393081 --- /dev/null +++ b/firmware/common/cpu_clock.h @@ -0,0 +1,34 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "platform_detect.h" // IWYU pragma: keep + +void cpu_clock_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index d5679942..e9bc4c2e 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -21,17 +21,12 @@ * Boston, MA 02110-1301, USA. */ -#include #include #include #include #include "platform_detect.h" -#ifdef IS_NOT_RAD1O - #include -#endif - #include "delay.h" #include "gpio.h" #include "hackrf_core.h" @@ -44,14 +39,12 @@ #include "spi_bus.h" #include "w25q80bv_target.h" #ifdef IS_EXPANSION_COMPATIBLE - #include "portapack.h" + #include "portapack.h" #endif #ifdef IS_PRALINE #include "ice40_spi.h" #endif -#include - i2c_bus_t i2c0 = { .obj = (void*) I2C0_BASE, .start = i2c_lpc_start, @@ -428,222 +421,6 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) return resultant_rate; } -/* -Configure PLL1 (Main MCU Clock) to max speed (204MHz). -Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1. -This function shall be called after cpu_clock_init(). -*/ -static void cpu_clock_pll1_max_speed(void) -{ - uint32_t reg_val; - - /* This function implements the sequence recommended in: - * UM10503 Rev 2.4 (Aug 2018), section 13.2.1.1, page 167. */ - - /* 1. Select the IRC as BASE_M4_CLK source. */ - reg_val = CGU_BASE_M4_CLK; - reg_val &= ~CGU_BASE_M4_CLK_CLK_SEL_MASK; - reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_IRC) | CGU_BASE_M4_CLK_AUTOBLOCK(1); - CGU_BASE_M4_CLK = reg_val; - - /* 2. Enable the crystal oscillator. */ - CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_ENABLE_MASK; - - /* 3. Wait 250us. */ - delay_us_at_mhz(250, 12); - - /* 4. Set the AUTOBLOCK bit. */ - CGU_PLL1_CTRL |= CGU_PLL1_CTRL_AUTOBLOCK(1); - - /* 5. Reconfigure PLL1 to produce the final output frequency, with the - * crystal oscillator as clock source. */ - reg_val = CGU_PLL1_CTRL; - // clang-format off - reg_val &= ~( CGU_PLL1_CTRL_CLK_SEL_MASK | - CGU_PLL1_CTRL_PD_MASK | - CGU_PLL1_CTRL_FBSEL_MASK | - CGU_PLL1_CTRL_BYPASS_MASK | - CGU_PLL1_CTRL_DIRECT_MASK | - CGU_PLL1_CTRL_PSEL_MASK | - CGU_PLL1_CTRL_MSEL_MASK | - CGU_PLL1_CTRL_NSEL_MASK ); - /* Set PLL1 up to 12MHz * 17 = 204MHz. - * Direct mode: FCLKOUT = FCCO = M*(FCLKIN/N) */ - reg_val |= CGU_PLL1_CTRL_CLK_SEL(CGU_SRC_XTAL) | - CGU_PLL1_CTRL_PSEL(0) | - CGU_PLL1_CTRL_NSEL(0) | - CGU_PLL1_CTRL_MSEL(16) | - CGU_PLL1_CTRL_FBSEL(0) | - CGU_PLL1_CTRL_DIRECT(1); - // clang-format on - CGU_PLL1_CTRL = reg_val; - - /* 6. Wait for PLL1 to lock. */ - while (!(CGU_PLL1_STAT & CGU_PLL1_STAT_LOCK_MASK)) {} - - /* 7. Set the PLL1 P-divider to divide by 2 (DIRECT=0, PSEL=0). */ - CGU_PLL1_CTRL &= ~CGU_PLL1_CTRL_DIRECT_MASK; - - /* 8. Select PLL1 as BASE_M4_CLK source. */ - reg_val = CGU_BASE_M4_CLK; - reg_val &= ~CGU_BASE_M4_CLK_CLK_SEL_MASK; - reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_PLL1); - CGU_BASE_M4_CLK = reg_val; - - /* 9. Wait 50us. */ - delay_us_at_mhz(50, 102); - - /* 10. Set the PLL1 P-divider to direct output mode (DIRECT=1). */ - CGU_PLL1_CTRL |= CGU_PLL1_CTRL_DIRECT_MASK; -} - -/* clock startup for LPC4320 configure PLL1 to max speed (204MHz). -Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1. */ -void cpu_clock_init(void) -{ - /* use IRC as clock source for APB1 (including I2C0) */ - CGU_BASE_APB1_CLK = CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_IRC); - - /* use IRC as clock source for APB3 */ - CGU_BASE_APB3_CLK = CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_IRC); - - //FIXME disable I2C - /* Kick I2C0 down to 400kHz when we switch over to APB1 clock = 204MHz */ - i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); - - /* - * 12MHz clock is entering LPC XTAL1/OSC input now. - * On HackRF One and Jawbreaker, there is a 12 MHz crystal at the LPC. - * Set up PLL1 to run from XTAL1 input. - */ - - //FIXME a lot of the details here should be in a CGU driver - - /* set xtal oscillator to low frequency mode */ - CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_HF_MASK; - - cpu_clock_pll1_max_speed(); - - /* use XTAL_OSC as clock source for APB1 */ - CGU_BASE_APB1_CLK = - CGU_BASE_APB1_CLK_AUTOBLOCK(1) | CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_XTAL); - - /* use XTAL_OSC as clock source for APB3 */ - CGU_BASE_APB3_CLK = - CGU_BASE_APB3_CLK_AUTOBLOCK(1) | CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_XTAL); - - /* use XTAL_OSC as clock source for PLL0USB */ - CGU_PLL0USB_CTRL = CGU_PLL0USB_CTRL_PD(1) | CGU_PLL0USB_CTRL_AUTOBLOCK(1) | - CGU_PLL0USB_CTRL_CLK_SEL(CGU_SRC_XTAL); - while (CGU_PLL0USB_STAT & CGU_PLL0USB_STAT_LOCK_MASK) {} - - /* configure PLL0USB to produce 480 MHz clock from 12 MHz XTAL_OSC */ - /* Values from User Manual v1.4 Table 94, for 12MHz oscillator. */ - CGU_PLL0USB_MDIV = 0x06167FFA; - CGU_PLL0USB_NP_DIV = 0x00302062; - CGU_PLL0USB_CTRL |= - (CGU_PLL0USB_CTRL_PD(1) | CGU_PLL0USB_CTRL_DIRECTI(1) | - CGU_PLL0USB_CTRL_DIRECTO(1) | CGU_PLL0USB_CTRL_CLKEN(1)); - - /* power on PLL0USB and wait until stable */ - CGU_PLL0USB_CTRL &= ~CGU_PLL0USB_CTRL_PD_MASK; - while (!(CGU_PLL0USB_STAT & CGU_PLL0USB_STAT_LOCK_MASK)) {} - - /* use PLL0USB as clock source for USB0 */ - CGU_BASE_USB0_CLK = CGU_BASE_USB0_CLK_AUTOBLOCK(1) | - CGU_BASE_USB0_CLK_CLK_SEL(CGU_SRC_PLL0USB); - - /* Switch peripheral clock over to use PLL1 (204MHz) */ - CGU_BASE_PERIPH_CLK = CGU_BASE_PERIPH_CLK_AUTOBLOCK(1) | - CGU_BASE_PERIPH_CLK_CLK_SEL(CGU_SRC_PLL1); - - /* Switch APB1 clock over to use PLL1 (204MHz) */ - CGU_BASE_APB1_CLK = - CGU_BASE_APB1_CLK_AUTOBLOCK(1) | CGU_BASE_APB1_CLK_CLK_SEL(CGU_SRC_PLL1); - - /* Switch APB3 clock over to use PLL1 (204MHz) */ - CGU_BASE_APB3_CLK = - CGU_BASE_APB3_CLK_AUTOBLOCK(1) | CGU_BASE_APB3_CLK_CLK_SEL(CGU_SRC_PLL1); - - CGU_BASE_SSP0_CLK = - CGU_BASE_SSP0_CLK_AUTOBLOCK(1) | CGU_BASE_SSP0_CLK_CLK_SEL(CGU_SRC_PLL1); - - CGU_BASE_SSP1_CLK = - CGU_BASE_SSP1_CLK_AUTOBLOCK(1) | CGU_BASE_SSP1_CLK_CLK_SEL(CGU_SRC_PLL1); - -#ifdef IS_NOT_RAD1O - if (IS_NOT_RAD1O) { - /* Disable unused clocks */ - /* Start with PLLs */ - CGU_PLL0AUDIO_CTRL = CGU_PLL0AUDIO_CTRL_PD(1); - - /* Dividers */ - CGU_IDIVA_CTRL = CGU_IDIVA_CTRL_PD(1); - CGU_IDIVB_CTRL = CGU_IDIVB_CTRL_PD(1); - CGU_IDIVC_CTRL = CGU_IDIVC_CTRL_PD(1); - CGU_IDIVD_CTRL = CGU_IDIVD_CTRL_PD(1); - CGU_IDIVE_CTRL = CGU_IDIVE_CTRL_PD(1); - - /* Base clocks */ - CGU_BASE_SPIFI_CLK = - CGU_BASE_SPIFI_CLK_PD(1); /* SPIFI is only used at boot */ - CGU_BASE_USB1_CLK = - CGU_BASE_USB1_CLK_PD(1); /* USB1 is not exposed on HackRF */ - CGU_BASE_PHY_RX_CLK = CGU_BASE_PHY_RX_CLK_PD(1); - CGU_BASE_PHY_TX_CLK = CGU_BASE_PHY_TX_CLK_PD(1); - CGU_BASE_LCD_CLK = CGU_BASE_LCD_CLK_PD(1); - CGU_BASE_VADC_CLK = CGU_BASE_VADC_CLK_PD(1); - CGU_BASE_SDIO_CLK = CGU_BASE_SDIO_CLK_PD(1); - CGU_BASE_UART0_CLK = CGU_BASE_UART0_CLK_PD(1); - CGU_BASE_UART1_CLK = CGU_BASE_UART1_CLK_PD(1); - CGU_BASE_UART2_CLK = CGU_BASE_UART2_CLK_PD(1); - CGU_BASE_UART3_CLK = CGU_BASE_UART3_CLK_PD(1); - CGU_BASE_OUT_CLK = CGU_BASE_OUT_CLK_PD(1); - CGU_BASE_AUDIO_CLK = CGU_BASE_AUDIO_CLK_PD(1); - CGU_BASE_CGU_OUT0_CLK = CGU_BASE_CGU_OUT0_CLK_PD(1); - CGU_BASE_CGU_OUT1_CLK = CGU_BASE_CGU_OUT1_CLK_PD(1); - - /* Disable unused peripheral clocks */ - CCU1_CLK_APB1_CAN1_CFG = 0; - CCU1_CLK_APB1_I2S_CFG = 0; - CCU1_CLK_APB1_MOTOCONPWM_CFG = 0; - //CCU1_CLK_APB3_ADC0_CFG = 0; - CCU1_CLK_APB3_ADC1_CFG = 0; - CCU1_CLK_APB3_CAN0_CFG = 0; - CCU1_CLK_APB3_DAC_CFG = 0; - //CCU1_CLK_M4_DMA_CFG = 0; - CCU1_CLK_M4_EMC_CFG = 0; - CCU1_CLK_M4_EMCDIV_CFG = 0; - CCU1_CLK_M4_ETHERNET_CFG = 0; - CCU1_CLK_M4_LCD_CFG = 0; - CCU1_CLK_M4_QEI_CFG = 0; - CCU1_CLK_M4_RITIMER_CFG = 0; - // CCU1_CLK_M4_SCT_CFG = 0; - CCU1_CLK_M4_SDIO_CFG = 0; - CCU1_CLK_M4_SPIFI_CFG = 0; - CCU1_CLK_M4_TIMER0_CFG = 0; - //CCU1_CLK_M4_TIMER1_CFG = 0; - //CCU1_CLK_M4_TIMER2_CFG = 0; - CCU1_CLK_M4_TIMER3_CFG = 0; - CCU1_CLK_M4_UART1_CFG = 0; - CCU1_CLK_M4_USART0_CFG = 0; - CCU1_CLK_M4_USART2_CFG = 0; - CCU1_CLK_M4_USART3_CFG = 0; - CCU1_CLK_M4_USB1_CFG = 0; - CCU1_CLK_M4_VADC_CFG = 0; - // CCU1_CLK_SPIFI_CFG = 0; - // CCU1_CLK_USB1_CFG = 0; - // CCU1_CLK_VADC_CFG = 0; - // CCU2_CLK_APB0_UART1_CFG = 0; - // CCU2_CLK_APB0_USART0_CFG = 0; - // CCU2_CLK_APB2_USART2_CFG = 0; - // CCU2_CLK_APB2_USART3_CFG = 0; - // CCU2_CLK_APLL_CFG = 0; - // CCU2_CLK_SDIO_CFG = 0; - } -#endif -} - void clock_gen_init(void) { i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 56221f6b..334d74a0 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -33,6 +33,7 @@ extern "C" { #include "cpld_jtag.h" #include "fixed_point.h" #include "i2c_bus.h" +#include "i2c_lpc.h" #include "max283x.h" #include "max5864.h" #include "mixer.h" @@ -49,6 +50,7 @@ extern "C" { #endif /* TODO: Hide these configurations */ +extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; extern si5351c_driver_t clock_gen; extern ssp_config_t ssp_config_w25q80bv; @@ -67,7 +69,6 @@ extern rf_path_t rf_path; extern jtag_t jtag_cpld; extern i2c_bus_t i2c0; -void cpu_clock_init(void); void clock_gen_init(void); void clock_gen_shutdown(void); void ssp1_set_mode_max283x(void); diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 9a349b1e..a2823405 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -168,6 +168,7 @@ endmacro() macro(DeclareTargets) SET(SRC_M4 ${SRC_M4} + ${PATH_HACKRF_FIRMWARE_COMMON}/cpu_clock.c ${PATH_HACKRF_FIRMWARE_COMMON}/delay.c ${PATH_HACKRF_FIRMWARE_COMMON}/hackrf_core.c ${PATH_HACKRF_FIRMWARE_COMMON}/sgpio.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index b79dcdec..dba9a39e 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -29,6 +29,7 @@ #include #include +#include #include #include #include From b8cd1d51a7e3d605b238a68f6051c74c5e1bc8ca Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 19 Mar 2026 11:04:47 +0200 Subject: [PATCH 059/161] firmware: move clock generator logic to clock_gen.h|c --- firmware/common/clock_gen.c | 452 ++++++++++++++++++++++ firmware/common/clock_gen.h | 76 ++++ firmware/common/fpga_selftest.c | 1 + firmware/common/hackrf_core.c | 416 +------------------- firmware/common/hackrf_core.h | 27 -- firmware/common/hackrf_ui.h | 2 +- firmware/common/radio.h | 7 - firmware/common/ui_portapack.c | 2 +- firmware/common/ui_rad1o.c | 5 +- firmware/hackrf-common.cmake | 1 + firmware/hackrf_usb/hackrf_usb.c | 1 + firmware/hackrf_usb/usb_api_board_info.c | 1 + firmware/hackrf_usb/usb_api_praline.c | 1 + firmware/hackrf_usb/usb_api_transceiver.c | 1 + 14 files changed, 541 insertions(+), 452 deletions(-) create mode 100644 firmware/common/clock_gen.c create mode 100644 firmware/common/clock_gen.h diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c new file mode 100644 index 00000000..e29e5a91 --- /dev/null +++ b/firmware/common/clock_gen.c @@ -0,0 +1,452 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "clock_gen.h" + +#include + +#include "hackrf_core.h" +#include "hackrf_ui.h" +#include "i2c_bus.h" +#include "platform_detect.h" +#include "sgpio.h" +#include "si5351c.h" +#if defined(IS_HACKRF_ONE) || defined(IS_PRALINE) + #include "delay.h" + #include "portapack.h" +#endif +#if defined(IS_PRALINE) + #include "gpio.h" + #include "platform_gpio.h" +#endif + +void clock_gen_init(void) +{ + i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); + + si5351c_init(&clock_gen); + si5351c_disable_all_outputs(&clock_gen); + si5351c_disable_oeb_pin_control(&clock_gen); + si5351c_power_down_all_clocks(&clock_gen); + si5351c_set_crystal_configuration(&clock_gen); + si5351c_enable_xo_and_ms_fanout(&clock_gen); + + /* + * Clocks on HackRF One r9: + * CLK0 -> MAX5864/CPLD/SGPIO (sample clocks) + * CLK1 -> RFFC5072/MAX2839 + * CLK2 -> External Clock Output/LPC43xx (power down at boot) + * + * Clocks on other platforms: + * CLK0 -> MAX5864/CPLD + * CLK1 -> CPLD + * CLK2 -> SGPIO + * CLK3 -> External Clock Output (power down at boot) + * CLK4 -> RFFC5072 (MAX2837 on rad1o) + * CLK5 -> MAX2837 (MAX2871 on rad1o) + * CLK6 -> none + * CLK7 -> LPC43xx (uses a 12MHz crystal by default) + * + * Clocks on Praline: + * CLK0 -> AFE_CLK (MAX5864/FPGA) + * CLK1 -> SCT_CLK + * CLK2 -> MCU_CLK (uses a 12MHz crystal by default) + * CLK3 -> External Clock Output (power down at boot) + * CLK4 -> XCVR_CLK (MAX2837) + * CLK5 -> MIX_CLK (RFFC5072) + * CLK6 -> AUX_CLK1 + * CLK7 -> AUX_CLK2 + */ + +#ifdef IS_H1_R9 + if (IS_H1_R9) { + /* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */ + si5351c_configure_multisynth( + &clock_gen, + 0, + 20 * 128 - 512, + 0, + 1, + 0); /* 800/20 = 40MHz */ + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { + /* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */ + si5351c_configure_multisynth( + &clock_gen, + 4, + 20 * 128 - 512, + 0, + 1, + 0); /* 800/20 = 40MHz */ + /* MS5/CLK5 is the source for the MAX2837 clock input (MAX2871 on rad1o). */ + si5351c_configure_multisynth( + &clock_gen, + 5, + 20 * 128 - 512, + 0, + 1, + 0); /* 800/20 = 40MHz */ + } +#endif + + /* MS6/CLK6 is unused. */ + /* MS7/CLK7 is unused. */ + + /* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */ + sample_rate_set(SR_FP_MHZ(10), true); + + si5351c_configure_clock_control(&clock_gen); + si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL); + // soft reset + si5351c_reset_pll(&clock_gen, SI5351C_PLL_BOTH); + si5351c_enable_clock_outputs(&clock_gen); +} + +void clock_gen_shutdown(void) +{ + i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); + si5351c_disable_all_outputs(&clock_gen); + si5351c_disable_oeb_pin_control(&clock_gen); + si5351c_power_down_all_clocks(&clock_gen); +} + +clock_source_t activate_best_clock_source(void) +{ +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { + /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ + if (portapack_reference_oscillator && portapack()) { + portapack_reference_oscillator(false); + } + } +#endif + + clock_source_t source = CLOCK_SOURCE_HACKRF; + + /* Check for external clock input. */ + if (si5351c_clkin_signal_valid(&clock_gen)) { + source = CLOCK_SOURCE_EXTERNAL; + } else { +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { + /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ + if (portapack_reference_oscillator && portapack()) { + portapack_reference_oscillator(true); + delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */ + if (si5351c_clkin_signal_valid(&clock_gen)) { + source = CLOCK_SOURCE_PORTAPACK; + } else { + portapack_reference_oscillator(false); + } + } + } +#endif + /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ + } + + si5351c_set_clock_source( + &clock_gen, + (source == CLOCK_SOURCE_HACKRF) ? PLL_SOURCE_XTAL : PLL_SOURCE_CLKIN); + hackrf_ui()->set_clock_source(source); + + return source; +} + +/* + * Closest fraction to m/d with denominator <= max_den. + * Returns result in *r / *s with gcd(*r, *s) == 1 and 0 < *s <= max_den. + * Straight port from CPython's fractions, and better documented there. + */ +void limit_denominator( + uint64_t m, + uint64_t d, + const uint64_t max_den, + uint64_t* r, + uint64_t* s) +{ + if (d <= max_den) { + *r = m; + *s = d; + return; + } + + uint64_t p0 = 0, q0 = 1, p1 = 1, q1 = 0; + uint64_t n = m, orig_d = d; + uint64_t tmp; + + while (1) { + uint64_t a = n / d; + uint64_t q2 = q0 + a * q1; + + if (q2 > max_den) + break; + + tmp = p0 + a * p1; + p0 = p1; + q0 = q1; + p1 = tmp; + q1 = q2; + + tmp = n - a * d; + n = d; + d = tmp; + if (d == 0) + break; + } + + uint64_t k = (max_den - q0) / q1; + + /* Return closer candidate. */ + if (2 * d * (q0 + k * q1) <= orig_d) { + *r = p1; + *s = q1; + } else { + *r = p0 + k * p1; + *s = q0 + k * q1; + } +} + +/* + * Configure clock generator to produce sample clock in units of 1/(2**36) Hz. + * Can be called with program=false for a dry run that returns the resultant + * frequency without actually configuring the clock generator. + * + * The clock generator output frequency is: + * + * fs = 128 * vco / (512 + p1 + p2/p3)) + * + * where p1, p2, and p3 are register values. + * + * For more information see: + * https://www.pa3fwm.nl/technotes/tn42a-si5351-programming.html + */ +fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) +{ + const uint64_t vco_hz = 800 * 1000ULL * 1000ULL; + uint64_t p1, p2, p3; + uint64_t n, d, q1, q2, q3, r1, r2; + fp_28_36_t resultant_rate; + + /* + * First double the sample rate so that we can produce a clock at twice + * the intended sample rate. The 2x clock is sometimes used directly, + * and it is divided by two in an output divider to produce the actual + * AFE clock. + */ + fp_28_36_t rate = sample_rate * 2; + + /* + * Computes p1 = (N << 36) / rate - 512, where N = 128 * vco_hz. + * + * Full numerator (N << 36) is 73 bits, so we split the division: + * + * (N << 36) / rate = ((N << 27) / rate) << 9 + * + (((N << 27) % rate) << 9) / rate + * + * IMPORTANT: Assumes sample rate is in [200e3 << 36, 43.6e6 << 36]. + */ + const uint64_t A = (128 * vco_hz) << 27; + + q1 = A / rate; + r1 = A % rate; + + // Remaining 9 bits with long division. + q2 = 0; + r2 = r1; + for (int j = 0; j < 9; j++) { + uint64_t msb = r2 >> 63; + r2 <<= 1; + q2 <<= 1; + if (msb || r2 >= rate) { + r2 -= rate; + q2 |= 1; + } + } + + p1 = (q1 << 9) + q2 - 512; + + if (r2) { + /* Use the remainder for the fractional part. */ + n = r2; + d = rate; + + /* Reduce fraction. */ + const uint64_t p3_max = 0xfffff; + limit_denominator(n, d, p3_max, &p2, &p3); + + /* Roll over to next p1 to enable integer mode. */ + if (p2 >= p3) { + p1++; + p2 = 0; + } + } else { + p2 = 0; + } + + /* Maximum: (128 * 2048) - 512 */ + if (p1 > 0x3fe00) { + p1 = 0x3fe00; + p2 = 0; + } + + if (p2 == 0) { + /* Use unity denominator for integer mode. */ + p3 = 1; + n = (128 * vco_hz) << 18; + d = (p1 + 512); + q1 = n / d; + r1 = n % d; + q2 = ((r1 << 18) + (d / 2)) / d; + resultant_rate = (q1 << 18) + q2; + } else { + n = p3 * vco_hz * 128; + d = p3 * (p1 + 512) + p2; + q1 = n / d; + r1 = n % d; + q2 = (r1 << 18) / d; + r2 = (r1 << 18) % d; + q3 = ((r2 << 18) + (d / 2)) / d; + resultant_rate = (q1 << 36) + (q2 << 18) + q3; + } + + /* Return MCU sample rate, not AFE clock rate. */ + resultant_rate = (resultant_rate + 1) / 2; + + if (!program) { + return resultant_rate; + } + + bool streaming = sgpio_cpld_stream_is_enabled(&sgpio_config); + + if (streaming) { + sgpio_cpld_stream_disable(&sgpio_config); + } + +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + /* Integer mode can be enabled if p1 is even and p2 is zero. */ + if (p1 & 0x1 || p2) { + si5351c_set_int_mode(&clock_gen, 0, 0); + } else { + si5351c_set_int_mode(&clock_gen, 0, 1); + } + + #ifdef IS_H1_R9 + if (IS_H1_R9) { + /* + * On HackRF One r9 all sample clocks are externally derived + * from MS1/CLK1 operating at twice the sample rate. + */ + si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { + /* + * On other platforms the clock generator produces three + * different sample clocks, all derived from multisynth 0. + */ + /* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */ + si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); + + /* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */ + si5351c_configure_multisynth( + &clock_gen, + 1, + 0, + 0, + 0, + 0); //p1 doesn't matter + + /* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */ + si5351c_configure_multisynth( + &clock_gen, + 2, + 0, + 0, + 0, + 0); //p1 doesn't matter + } + #endif + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { + /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ + si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); + + /* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */ + si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 1); + + /* Delay FPGA_CLK relative to AFE_CLK. */ + uint8_t phase_offset = 0; + if (p1 < 2100) { + phase_offset = (p1 >> 4) - 6; + } + si5351c_set_phase(&clock_gen, 1, phase_offset); + + if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { + /* + * On older boards FPGA_CLK is on CLK2 while SCT_CLK is on + * CLK1. We configure both so that behavior is consistent with + * newer boards that use CLK1 for both FPGA_CLK and SCT_CLK. + */ + si5351c_configure_multisynth(&clock_gen, 2, p1, p2, p3, 1); + si5351c_set_phase(&clock_gen, 2, phase_offset); + } + + /* Reset PLL to synchronize output clock phase. */ + si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); + } +#endif + + if (streaming) { + sgpio_cpld_stream_enable(&sgpio_config); + } + + return resultant_rate; +} + +#ifdef IS_PRALINE +void p1_ctrl_set(const p1_ctrl_signal_t signal) +{ + const platform_gpio_t* gpio = platform_gpio(); + + gpio_write(gpio->p1_ctrl0, signal & 1); + gpio_write(gpio->p1_ctrl1, (signal >> 1) & 1); + gpio_write(gpio->p1_ctrl2, (signal >> 2) & 1); +} + +void p2_ctrl_set(const p2_ctrl_signal_t signal) +{ + const platform_gpio_t* gpio = platform_gpio(); + + gpio_write(gpio->p2_ctrl0, signal & 1); + gpio_write(gpio->p2_ctrl1, (signal >> 1) & 1); +} + +void pps_out_set(const uint8_t value) +{ + gpio_write(platform_gpio()->pps_out, value & 1); +} +#endif diff --git a/firmware/common/clock_gen.h b/firmware/common/clock_gen.h new file mode 100644 index 00000000..48fb6f20 --- /dev/null +++ b/firmware/common/clock_gen.h @@ -0,0 +1,76 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "platform_detect.h" // IWYU pragma: keep + +#include +#ifdef IS_PRALINE + #include +#endif + +#include "fixed_point.h" + +typedef enum { + CLOCK_SOURCE_HACKRF = 0, + CLOCK_SOURCE_EXTERNAL = 1, + CLOCK_SOURCE_PORTAPACK = 2, +} clock_source_t; + +void clock_gen_init(void); +void clock_gen_shutdown(void); + +clock_source_t activate_best_clock_source(void); + +fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program); + +#ifdef IS_PRALINE +typedef enum { + P1_SIGNAL_TRIGGER_IN = 0, + P1_SIGNAL_AUX_CLK1 = 1, + P1_SIGNAL_CLKIN = 2, + P1_SIGNAL_TRIGGER_OUT = 3, + P1_SIGNAL_P22_CLKIN = 4, + P1_SIGNAL_P2_5 = 5, + P1_SIGNAL_NC = 6, + P1_SIGNAL_AUX_CLK2 = 7, +} p1_ctrl_signal_t; + +typedef enum { + P2_SIGNAL_CLK3 = 0, + P2_SIGNAL_TRIGGER_IN = 2, + P2_SIGNAL_TRIGGER_OUT = 3, +} p2_ctrl_signal_t; + +void p1_ctrl_set(const p1_ctrl_signal_t signal); +void p2_ctrl_set(const p2_ctrl_signal_t signal); + +void pps_out_set(const uint8_t value); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/firmware/common/fpga_selftest.c b/firmware/common/fpga_selftest.c index 270d3ff4..fe0a96f9 100644 --- a/firmware/common/fpga_selftest.c +++ b/firmware/common/fpga_selftest.c @@ -23,6 +23,7 @@ #include #include +#include "clock_gen.h" #include "delay.h" #include "fixed_point.h" #include "fpga.h" diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index e9bc4c2e..c8b12842 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -27,10 +27,10 @@ #include "platform_detect.h" +#include "clock_gen.h" #include "delay.h" #include "gpio.h" #include "hackrf_core.h" -#include "hackrf_ui.h" #include "i2c_lpc.h" #include "max283x.h" #include "max5864_target.h" @@ -38,9 +38,6 @@ #include "platform_scu.h" #include "spi_bus.h" #include "w25q80bv_target.h" -#ifdef IS_EXPANSION_COMPATIBLE - #include "portapack.h" -#endif #ifdef IS_PRALINE #include "ice40_spi.h" #endif @@ -166,395 +163,6 @@ jtag_t jtag_cpld = { .gpio = &jtag_gpio_cpld, }; -/* - * Closest fraction to m/d with denominator <= max_den. - * Returns result in *r / *s with gcd(*r, *s) == 1 and 0 < *s <= max_den. - * Straight port from CPython's fractions, and better documented there. - */ -void limit_denominator( - uint64_t m, - uint64_t d, - const uint64_t max_den, - uint64_t* r, - uint64_t* s) -{ - if (d <= max_den) { - *r = m; - *s = d; - return; - } - - uint64_t p0 = 0, q0 = 1, p1 = 1, q1 = 0; - uint64_t n = m, orig_d = d; - uint64_t tmp; - - while (1) { - uint64_t a = n / d; - uint64_t q2 = q0 + a * q1; - - if (q2 > max_den) - break; - - tmp = p0 + a * p1; - p0 = p1; - q0 = q1; - p1 = tmp; - q1 = q2; - - tmp = n - a * d; - n = d; - d = tmp; - if (d == 0) - break; - } - - uint64_t k = (max_den - q0) / q1; - - /* Return closer candidate. */ - if (2 * d * (q0 + k * q1) <= orig_d) { - *r = p1; - *s = q1; - } else { - *r = p0 + k * p1; - *s = q0 + k * q1; - } -} - -/* - * Configure clock generator to produce sample clock in units of 1/(2**36) Hz. - * Can be called with program=false for a dry run that returns the resultant - * frequency without actually configuring the clock generator. - * - * The clock generator output frequency is: - * - * fs = 128 * vco / (512 + p1 + p2/p3)) - * - * where p1, p2, and p3 are register values. - * - * For more information see: - * https://www.pa3fwm.nl/technotes/tn42a-si5351-programming.html - */ -fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) -{ - const uint64_t vco_hz = 800 * 1000ULL * 1000ULL; - uint64_t p1, p2, p3; - uint64_t n, d, q1, q2, q3, r1, r2; - fp_28_36_t resultant_rate; - - /* - * First double the sample rate so that we can produce a clock at twice - * the intended sample rate. The 2x clock is sometimes used directly, - * and it is divided by two in an output divider to produce the actual - * AFE clock. - */ - fp_28_36_t rate = sample_rate * 2; - - /* - * Computes p1 = (N << 36) / rate - 512, where N = 128 * vco_hz. - * - * Full numerator (N << 36) is 73 bits, so we split the division: - * - * (N << 36) / rate = ((N << 27) / rate) << 9 - * + (((N << 27) % rate) << 9) / rate - * - * IMPORTANT: Assumes sample rate is in [200e3 << 36, 43.6e6 << 36]. - */ - const uint64_t A = (128 * vco_hz) << 27; - - q1 = A / rate; - r1 = A % rate; - - // Remaining 9 bits with long division. - q2 = 0; - r2 = r1; - for (int j = 0; j < 9; j++) { - uint64_t msb = r2 >> 63; - r2 <<= 1; - q2 <<= 1; - if (msb || r2 >= rate) { - r2 -= rate; - q2 |= 1; - } - } - - p1 = (q1 << 9) + q2 - 512; - - if (r2) { - /* Use the remainder for the fractional part. */ - n = r2; - d = rate; - - /* Reduce fraction. */ - const uint64_t p3_max = 0xfffff; - limit_denominator(n, d, p3_max, &p2, &p3); - - /* Roll over to next p1 to enable integer mode. */ - if (p2 >= p3) { - p1++; - p2 = 0; - } - } else { - p2 = 0; - } - - /* Maximum: (128 * 2048) - 512 */ - if (p1 > 0x3fe00) { - p1 = 0x3fe00; - p2 = 0; - } - - if (p2 == 0) { - /* Use unity denominator for integer mode. */ - p3 = 1; - n = (128 * vco_hz) << 18; - d = (p1 + 512); - q1 = n / d; - r1 = n % d; - q2 = ((r1 << 18) + (d / 2)) / d; - resultant_rate = (q1 << 18) + q2; - } else { - n = p3 * vco_hz * 128; - d = p3 * (p1 + 512) + p2; - q1 = n / d; - r1 = n % d; - q2 = (r1 << 18) / d; - r2 = (r1 << 18) % d; - q3 = ((r2 << 18) + (d / 2)) / d; - resultant_rate = (q1 << 36) + (q2 << 18) + q3; - } - - /* Return MCU sample rate, not AFE clock rate. */ - resultant_rate = (resultant_rate + 1) / 2; - - if (!program) { - return resultant_rate; - } - - bool streaming = sgpio_cpld_stream_is_enabled(&sgpio_config); - - if (streaming) { - sgpio_cpld_stream_disable(&sgpio_config); - } - -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - /* Integer mode can be enabled if p1 is even and p2 is zero. */ - if (p1 & 0x1 || p2) { - si5351c_set_int_mode(&clock_gen, 0, 0); - } else { - si5351c_set_int_mode(&clock_gen, 0, 1); - } - - #ifdef IS_H1_R9 - if (IS_H1_R9) { - /* - * On HackRF One r9 all sample clocks are externally derived - * from MS1/CLK1 operating at twice the sample rate. - */ - si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); - } - #endif - #ifdef IS_NOT_H1_R9 - if (IS_NOT_H1_R9) { - /* - * On other platforms the clock generator produces three - * different sample clocks, all derived from multisynth 0. - */ - /* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */ - si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); - - /* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */ - si5351c_configure_multisynth( - &clock_gen, - 1, - 0, - 0, - 0, - 0); //p1 doesn't matter - - /* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */ - si5351c_configure_multisynth( - &clock_gen, - 2, - 0, - 0, - 0, - 0); //p1 doesn't matter - } - #endif - } -#endif -#ifdef IS_PRALINE - if (IS_PRALINE) { - /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ - si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); - - /* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */ - si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 1); - - /* Delay FPGA_CLK relative to AFE_CLK. */ - uint8_t phase_offset = 0; - if (p1 < 2100) { - phase_offset = (p1 >> 4) - 6; - } - si5351c_set_phase(&clock_gen, 1, phase_offset); - - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* - * On older boards FPGA_CLK is on CLK2 while SCT_CLK is on - * CLK1. We configure both so that behavior is consistent with - * newer boards that use CLK1 for both FPGA_CLK and SCT_CLK. - */ - si5351c_configure_multisynth(&clock_gen, 2, p1, p2, p3, 1); - si5351c_set_phase(&clock_gen, 2, phase_offset); - } - - /* Reset PLL to synchronize output clock phase. */ - si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); - } -#endif - - if (streaming) { - sgpio_cpld_stream_enable(&sgpio_config); - } - - return resultant_rate; -} - -void clock_gen_init(void) -{ - i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); - - si5351c_init(&clock_gen); - si5351c_disable_all_outputs(&clock_gen); - si5351c_disable_oeb_pin_control(&clock_gen); - si5351c_power_down_all_clocks(&clock_gen); - si5351c_set_crystal_configuration(&clock_gen); - si5351c_enable_xo_and_ms_fanout(&clock_gen); - - /* - * Clocks on HackRF One r9: - * CLK0 -> MAX5864/CPLD/SGPIO (sample clocks) - * CLK1 -> RFFC5072/MAX2839 - * CLK2 -> External Clock Output/LPC43xx (power down at boot) - * - * Clocks on other platforms: - * CLK0 -> MAX5864/CPLD - * CLK1 -> CPLD - * CLK2 -> SGPIO - * CLK3 -> External Clock Output (power down at boot) - * CLK4 -> RFFC5072 (MAX2837 on rad1o) - * CLK5 -> MAX2837 (MAX2871 on rad1o) - * CLK6 -> none - * CLK7 -> LPC43xx (uses a 12MHz crystal by default) - * - * Clocks on Praline: - * CLK0 -> AFE_CLK (MAX5864/FPGA) - * CLK1 -> SCT_CLK - * CLK2 -> MCU_CLK (uses a 12MHz crystal by default) - * CLK3 -> External Clock Output (power down at boot) - * CLK4 -> XCVR_CLK (MAX2837) - * CLK5 -> MIX_CLK (RFFC5072) - * CLK6 -> AUX_CLK1 - * CLK7 -> AUX_CLK2 - */ - -#ifdef IS_H1_R9 - if (IS_H1_R9) { - /* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */ - si5351c_configure_multisynth( - &clock_gen, - 0, - 20 * 128 - 512, - 0, - 1, - 0); /* 800/20 = 40MHz */ - } -#endif -#ifdef IS_NOT_H1_R9 - if (IS_NOT_H1_R9) { - /* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */ - si5351c_configure_multisynth( - &clock_gen, - 4, - 20 * 128 - 512, - 0, - 1, - 0); /* 800/20 = 40MHz */ - /* MS5/CLK5 is the source for the MAX2837 clock input (MAX2871 on rad1o). */ - si5351c_configure_multisynth( - &clock_gen, - 5, - 20 * 128 - 512, - 0, - 1, - 0); /* 800/20 = 40MHz */ - } -#endif - - /* MS6/CLK6 is unused. */ - /* MS7/CLK7 is unused. */ - - /* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */ - sample_rate_set(SR_FP_MHZ(10), true); - - si5351c_configure_clock_control(&clock_gen); - si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL); - // soft reset - si5351c_reset_pll(&clock_gen, SI5351C_PLL_BOTH); - si5351c_enable_clock_outputs(&clock_gen); -} - -void clock_gen_shutdown(void) -{ - i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); - si5351c_disable_all_outputs(&clock_gen); - si5351c_disable_oeb_pin_control(&clock_gen); - si5351c_power_down_all_clocks(&clock_gen); -} - -clock_source_t activate_best_clock_source(void) -{ -#ifdef IS_EXPANSION_COMPATIBLE - if (IS_EXPANSION_COMPATIBLE) { - /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ - if (portapack_reference_oscillator && portapack()) { - portapack_reference_oscillator(false); - } - } -#endif - - clock_source_t source = CLOCK_SOURCE_HACKRF; - - /* Check for external clock input. */ - if (si5351c_clkin_signal_valid(&clock_gen)) { - source = CLOCK_SOURCE_EXTERNAL; - } else { -#ifdef IS_EXPANSION_COMPATIBLE - if (IS_EXPANSION_COMPATIBLE) { - /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ - if (portapack_reference_oscillator && portapack()) { - portapack_reference_oscillator(true); - delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */ - if (si5351c_clkin_signal_valid(&clock_gen)) { - source = CLOCK_SOURCE_PORTAPACK; - } else { - portapack_reference_oscillator(false); - } - } - } -#endif - /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ - } - - si5351c_set_clock_source( - &clock_gen, - (source == CLOCK_SOURCE_HACKRF) ? PLL_SOURCE_XTAL : PLL_SOURCE_CLKIN); - hackrf_ui()->set_clock_source(source); - - return source; -} - void ssp1_set_mode_max283x(void) { spi_bus_start(&spi_bus_ssp1, &ssp_config_max283x); @@ -1151,33 +759,11 @@ void halt_and_flash(const uint32_t duration) } #ifdef IS_PRALINE -void p1_ctrl_set(const p1_ctrl_signal_t signal) -{ - const platform_gpio_t* gpio = platform_gpio(); - - gpio_write(gpio->p1_ctrl0, signal & 1); - gpio_write(gpio->p1_ctrl1, (signal >> 1) & 1); - gpio_write(gpio->p1_ctrl2, (signal >> 2) & 1); -} - -void p2_ctrl_set(const p2_ctrl_signal_t signal) -{ - const platform_gpio_t* gpio = platform_gpio(); - - gpio_write(gpio->p2_ctrl0, signal & 1); - gpio_write(gpio->p2_ctrl1, (signal >> 1) & 1); -} - void clkin_ctrl_set(const clkin_signal_t signal) { gpio_write(platform_gpio()->clkin_ctrl, signal & 1); } -void pps_out_set(const uint8_t value) -{ - gpio_write(platform_gpio()->pps_out, value & 1); -} - void narrowband_filter_set(const uint8_t value) { gpio_write(platform_gpio()->aa_en, value & 1); diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 334d74a0..d38ce387 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -31,7 +31,6 @@ extern "C" { #include #include "cpld_jtag.h" -#include "fixed_point.h" #include "i2c_bus.h" #include "i2c_lpc.h" #include "max283x.h" @@ -69,8 +68,6 @@ extern rf_path_t rf_path; extern jtag_t jtag_cpld; extern i2c_bus_t i2c0; -void clock_gen_init(void); -void clock_gen_shutdown(void); void ssp1_set_mode_max283x(void); void ssp1_set_mode_max5864(void); #ifdef IS_PRALINE @@ -89,10 +86,6 @@ void disable_3v3aux_power(void); void enable_1v8_power(void); void disable_1v8_power(void); -fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program); - -clock_source_t activate_best_clock_source(void); - #if defined(IS_RAD1O) || defined(IS_HACKRF_ONE) || defined(IS_PRALINE) void enable_rf_power(void); void disable_rf_power(void); @@ -115,33 +108,13 @@ void trigger_enable(const bool enable); void halt_and_flash(const uint32_t duration); #ifdef IS_PRALINE -typedef enum { - P1_SIGNAL_TRIGGER_IN = 0, - P1_SIGNAL_AUX_CLK1 = 1, - P1_SIGNAL_CLKIN = 2, - P1_SIGNAL_TRIGGER_OUT = 3, - P1_SIGNAL_P22_CLKIN = 4, - P1_SIGNAL_P2_5 = 5, - P1_SIGNAL_NC = 6, - P1_SIGNAL_AUX_CLK2 = 7, -} p1_ctrl_signal_t; - -typedef enum { - P2_SIGNAL_CLK3 = 0, - P2_SIGNAL_TRIGGER_IN = 2, - P2_SIGNAL_TRIGGER_OUT = 3, -} p2_ctrl_signal_t; - typedef enum { CLKIN_SIGNAL_P1 = 0, CLKIN_SIGNAL_P22 = 1, } clkin_signal_t; -void p1_ctrl_set(const p1_ctrl_signal_t signal); -void p2_ctrl_set(const p2_ctrl_signal_t signal); void narrowband_filter_set(const uint8_t value); void clkin_ctrl_set(const clkin_signal_t value); -void pps_out_set(const uint8_t value); #endif #ifdef __cplusplus diff --git a/firmware/common/hackrf_ui.h b/firmware/common/hackrf_ui.h index 1193d1c5..7b7d67e4 100644 --- a/firmware/common/hackrf_ui.h +++ b/firmware/common/hackrf_ui.h @@ -25,7 +25,7 @@ #include #include -#include "radio.h" +#include "clock_gen.h" #include "rf_path.h" #include "transceiver_mode.h" diff --git a/firmware/common/radio.h b/firmware/common/radio.h index 83f72441..7ab46c80 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -54,13 +54,6 @@ typedef struct { uint32_t hz; } radio_sample_rate_t; -// legacy type, moved from hackrf_core -typedef enum { - CLOCK_SOURCE_HACKRF = 0, - CLOCK_SOURCE_EXTERNAL = 1, - CLOCK_SOURCE_PORTAPACK = 2, -} clock_source_t; - /** * Configurable registers stored as uint64_t. Any register may be set to * RADIO_UNSET. When not RADIO_UNSET, some registers are read as a specific type diff --git a/firmware/common/ui_portapack.c b/firmware/common/ui_portapack.c index 8912777f..85ffa705 100644 --- a/firmware/common/ui_portapack.c +++ b/firmware/common/ui_portapack.c @@ -26,8 +26,8 @@ #include #include +#include "clock_gen.h" #include "portapack.h" -#include "radio.h" #include "rf_path.h" #include "transceiver_mode.h" diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index ce894cdf..878e9e74 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -26,10 +26,13 @@ #include #include +#include "clock_gen.h" #include "hackrf_core.h" -#include "radio.h" #include "rf_path.h" #include "transceiver_mode.h" +#ifdef IS_NOT_RAD1O + #include "radio.h" +#endif #include "rad1o/display.h" #include "rad1o/draw.h" diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index a2823405..0183948f 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -174,6 +174,7 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/sgpio.c ${PATH_HACKRF_FIRMWARE_COMMON}/rf_path.c ${PATH_HACKRF_FIRMWARE_COMMON}/si5351c.c + ${PATH_HACKRF_FIRMWARE_COMMON}/clock_gen.c ${PATH_HACKRF_FIRMWARE_COMMON}/max5864.c ${PATH_HACKRF_FIRMWARE_COMMON}/max5864_target.c ${PATH_HACKRF_FIRMWARE_COMMON}/mixer.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index dba9a39e..661d6791 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -29,6 +29,7 @@ #include #include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index 920ad749..c4634605 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -26,6 +26,7 @@ #include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_praline.c b/firmware/hackrf_usb/usb_api_praline.c index 2a41b839..47b59446 100644 --- a/firmware/hackrf_usb/usb_api_praline.c +++ b/firmware/hackrf_usb/usb_api_praline.c @@ -23,6 +23,7 @@ #include "usb_api_praline.h" +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index be16b1be..3d3ded45 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include From 92476260edde8dae7a1d9814c3a3acd10141501c Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Tue, 24 Mar 2026 11:00:56 +0200 Subject: [PATCH 060/161] firmware: move power logic to power.h|c --- firmware/blinky/blinky.c | 9 +- firmware/common/hackrf_core.c | 163 +------------------------ firmware/common/hackrf_core.h | 14 --- firmware/common/power.c | 197 +++++++++++++++++++++++++++++++ firmware/common/power.h | 46 ++++++++ firmware/hackrf-common.cmake | 1 + firmware/hackrf_usb/hackrf_usb.c | 1 + 7 files changed, 251 insertions(+), 180 deletions(-) create mode 100644 firmware/common/power.c create mode 100644 firmware/common/power.h diff --git a/firmware/blinky/blinky.c b/firmware/blinky/blinky.c index 6d071c97..e82f4007 100644 --- a/firmware/blinky/blinky.c +++ b/firmware/blinky/blinky.c @@ -19,9 +19,10 @@ * Boston, MA 02110-1301, USA. */ +#include "delay.h" #include "hackrf_core.h" #include "platform_detect.h" -#include "delay.h" +#include "power.h" int main(void) { @@ -37,18 +38,18 @@ int main(void) #endif /* Blink LED1/2/3 on the board. */ - while (1) + while (1) { led_on(LED1); led_on(LED2); led_on(LED3); delay(2000000); - + led_off(LED1); led_off(LED2); led_off(LED3); - + delay(2000000); } diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index c8b12842..b0b8a563 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -36,6 +36,7 @@ #include "max5864_target.h" #include "platform_gpio.h" #include "platform_scu.h" +#include "power.h" #include "spi_bus.h" #include "w25q80bv_target.h" #ifdef IS_PRALINE @@ -511,168 +512,6 @@ void pin_setup(void) sgpio_configure_pin_functions(&sgpio_config); } -#ifdef IS_PRALINE -void enable_1v2_power(void) -{ - if (IS_PRALINE) { - gpio_set(platform_gpio()->gpio_1v2_enable); - } -} - -void disable_1v2_power(void) -{ - if (IS_PRALINE) { - gpio_clear(platform_gpio()->gpio_1v2_enable); - } -} - -void enable_3v3aux_power(void) -{ - if (IS_PRALINE) { - gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); - } -} - -void disable_3v3aux_power(void) -{ - if (IS_PRALINE) { - gpio_set(platform_gpio()->gpio_3v3aux_enable_n); - } -} -#endif - -void enable_1v8_power(void) -{ -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - #ifdef IS_H1_R9 - if (IS_H1_R9) { - gpio_set(platform_gpio()->h1r9_1v8_enable); - } - #endif - #ifdef IS_NOT_H1_R9 - if (IS_NOT_H1_R9) { - gpio_set(platform_gpio()->gpio_1v8_enable); - } - #endif - } -#endif -} - -void disable_1v8_power(void) -{ -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - #ifdef IS_H1_R9 - if (IS_H1_R9) { - gpio_clear(platform_gpio()->h1r9_1v8_enable); - } - #endif - #ifdef IS_NOT_H1_R9 - if (IS_NOT_H1_R9) { - gpio_clear(platform_gpio()->gpio_1v8_enable); - } - #endif - } -#endif -} - -#ifdef IS_HACKRF_ONE -static inline void enable_rf_power_hackrf_one(void) -{ - const platform_gpio_t* gpio = platform_gpio(); - uint32_t i; - - /* many short pulses to avoid one big voltage glitch */ - for (i = 0; i < 1000; i++) { - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - gpio_set(gpio->h1r9_vaa_disable); - gpio_clear(gpio->h1r9_vaa_disable); - } else { - gpio_set(gpio->vaa_disable); - gpio_clear(gpio->vaa_disable); - } - } -} - -static inline void disable_rf_power_hackrf_one(void) -{ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - gpio_set(platform_gpio()->h1r9_vaa_disable); - } else { - gpio_set(platform_gpio()->vaa_disable); - } -} -#endif - -#ifdef IS_PRALINE -static inline void enable_rf_power_praline(void) -{ - gpio_clear(platform_gpio()->vaa_disable); - - /* Let the voltage stabilize */ - delay(1000000); -} - -static inline void disable_rf_power_praline(void) -{ - gpio_set(platform_gpio()->vaa_disable); -} -#endif - -#ifdef IS_RAD1O -static inline void enable_rf_power_rad1o(void) -{ - gpio_set(platform_gpio()->vaa_enable); - - /* Let the voltage stabilize */ - delay(1000000); -} - -static inline void disable_rf_power_rad1o(void) -{ - gpio_clear(platform_gpio()->vaa_enable); -} -#endif - -void enable_rf_power(void) -{ -#ifdef IS_HACKRF_ONE - if (IS_HACKRF_ONE) { - enable_rf_power_hackrf_one(); - } -#endif -#ifdef IS_PRALINE - if (IS_PRALINE) { - enable_rf_power_praline(); - } -#endif -#ifdef IS_RAD1O - if (IS_RAD1O) { - enable_rf_power_rad1o(); - } -#endif -} - -void disable_rf_power(void) -{ -#ifdef IS_HACKRF_ONE - if (IS_HACKRF_ONE) { - disable_rf_power_hackrf_one(); - } -#endif -#ifdef IS_PRALINE - if (IS_PRALINE) { - disable_rf_power_praline(); - } -#endif -#ifdef IS_RAD1O - if (IS_RAD1O) { - disable_rf_power_rad1o(); - } -#endif -} - void led_on(const led_t led) { #ifdef IS_PRALINE diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index d38ce387..1d783236 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -77,20 +77,6 @@ void ssp1_set_mode_ice40(void); void pin_shutdown(void); void pin_setup(void); -#ifdef IS_PRALINE -void enable_1v2_power(void); -void disable_1v2_power(void); -void enable_3v3aux_power(void); -void disable_3v3aux_power(void); -#endif -void enable_1v8_power(void); -void disable_1v8_power(void); - -#if defined(IS_RAD1O) || defined(IS_HACKRF_ONE) || defined(IS_PRALINE) -void enable_rf_power(void); -void disable_rf_power(void); -#endif - typedef enum { LED1 = 0, LED2 = 1, diff --git a/firmware/common/power.c b/firmware/common/power.c new file mode 100644 index 00000000..25424aab --- /dev/null +++ b/firmware/common/power.c @@ -0,0 +1,197 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "power.h" + +#if defined(IS_HACKRF_ONE) + #include +#endif +#if defined(IS_HACKRF_ONE) || defined(IS_RAD1O) || defined(IS_JAWBREAKER) + #include "platform_detect.h" +#endif +#if defined(IS_PRALINE) || defined(IS_RAD1O) + #include "delay.h" +#endif + +#include "gpio.h" +#include "platform_gpio.h" + +#ifdef IS_PRALINE +void enable_1v2_power(void) +{ + if (IS_PRALINE) { + gpio_set(platform_gpio()->gpio_1v2_enable); + } +} + +void disable_1v2_power(void) +{ + if (IS_PRALINE) { + gpio_clear(platform_gpio()->gpio_1v2_enable); + } +} + +void enable_3v3aux_power(void) +{ + if (IS_PRALINE) { + gpio_clear(platform_gpio()->gpio_3v3aux_enable_n); + } +} + +void disable_3v3aux_power(void) +{ + if (IS_PRALINE) { + gpio_set(platform_gpio()->gpio_3v3aux_enable_n); + } +} +#endif + +void enable_1v8_power(void) +{ +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + #ifdef IS_H1_R9 + if (IS_H1_R9) { + gpio_set(platform_gpio()->h1r9_1v8_enable); + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { + gpio_set(platform_gpio()->gpio_1v8_enable); + } + #endif + } +#endif +} + +void disable_1v8_power(void) +{ +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + #ifdef IS_H1_R9 + if (IS_H1_R9) { + gpio_clear(platform_gpio()->h1r9_1v8_enable); + } + #endif + #ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { + gpio_clear(platform_gpio()->gpio_1v8_enable); + } + #endif + } +#endif +} + +#ifdef IS_HACKRF_ONE +static inline void enable_rf_power_hackrf_one(void) +{ + const platform_gpio_t* gpio = platform_gpio(); + uint32_t i; + + /* many short pulses to avoid one big voltage glitch */ + for (i = 0; i < 1000; i++) { + if (detected_platform() == BOARD_ID_HACKRF1_R9) { + gpio_set(gpio->h1r9_vaa_disable); + gpio_clear(gpio->h1r9_vaa_disable); + } else { + gpio_set(gpio->vaa_disable); + gpio_clear(gpio->vaa_disable); + } + } +} + +static inline void disable_rf_power_hackrf_one(void) +{ + if (detected_platform() == BOARD_ID_HACKRF1_R9) { + gpio_set(platform_gpio()->h1r9_vaa_disable); + } else { + gpio_set(platform_gpio()->vaa_disable); + } +} +#endif + +#ifdef IS_PRALINE +static inline void enable_rf_power_praline(void) +{ + gpio_clear(platform_gpio()->vaa_disable); + + /* Let the voltage stabilize */ + delay(1000000); +} + +static inline void disable_rf_power_praline(void) +{ + gpio_set(platform_gpio()->vaa_disable); +} +#endif + +#ifdef IS_RAD1O +static inline void enable_rf_power_rad1o(void) +{ + gpio_set(platform_gpio()->vaa_enable); + + /* Let the voltage stabilize */ + delay(1000000); +} + +static inline void disable_rf_power_rad1o(void) +{ + gpio_clear(platform_gpio()->vaa_enable); +} +#endif + +void enable_rf_power(void) +{ +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { + enable_rf_power_hackrf_one(); + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { + enable_rf_power_praline(); + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { + enable_rf_power_rad1o(); + } +#endif +} + +void disable_rf_power(void) +{ +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { + disable_rf_power_hackrf_one(); + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { + disable_rf_power_praline(); + } +#endif +#ifdef IS_RAD1O + if (IS_RAD1O) { + disable_rf_power_rad1o(); + } +#endif +} diff --git a/firmware/common/power.h b/firmware/common/power.h new file mode 100644 index 00000000..22b2345e --- /dev/null +++ b/firmware/common/power.h @@ -0,0 +1,46 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "platform_detect.h" // IWYU pragma: keep + +#ifdef IS_PRALINE +void enable_1v2_power(void); +void disable_1v2_power(void); +void enable_3v3aux_power(void); +void disable_3v3aux_power(void); +#endif +void enable_1v8_power(void); +void disable_1v8_power(void); + +#if defined(IS_RAD1O) || defined(IS_HACKRF_ONE) || defined(IS_PRALINE) +void enable_rf_power(void); +void disable_rf_power(void); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 0183948f..1a55400f 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -168,6 +168,7 @@ endmacro() macro(DeclareTargets) SET(SRC_M4 ${SRC_M4} + ${PATH_HACKRF_FIRMWARE_COMMON}/power.c ${PATH_HACKRF_FIRMWARE_COMMON}/cpu_clock.c ${PATH_HACKRF_FIRMWARE_COMMON}/delay.c ${PATH_HACKRF_FIRMWARE_COMMON}/hackrf_core.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 661d6791..eff69298 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include From 799af35b5fd7f94221e0d37db477bd5b3e0bbc1b Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Tue, 24 Mar 2026 14:03:55 +0200 Subject: [PATCH 061/161] firmware: move led logic to leds.h|c --- firmware/blinky/blinky.c | 1 + firmware/common/hackrf_core.c | 73 +--------------- firmware/common/hackrf_core.h | 18 +--- firmware/common/leds.c | 100 ++++++++++++++++++++++ firmware/common/leds.h | 46 ++++++++++ firmware/common/platform_detect.c | 2 +- firmware/hackrf-common.cmake | 1 + firmware/hackrf_usb/hackrf_usb.c | 1 + firmware/hackrf_usb/usb_api_cpld.c | 1 + firmware/hackrf_usb/usb_api_register.c | 1 + firmware/hackrf_usb/usb_api_transceiver.c | 1 + 11 files changed, 157 insertions(+), 88 deletions(-) create mode 100644 firmware/common/leds.c create mode 100644 firmware/common/leds.h diff --git a/firmware/blinky/blinky.c b/firmware/blinky/blinky.c index e82f4007..0bf89bf8 100644 --- a/firmware/blinky/blinky.c +++ b/firmware/blinky/blinky.c @@ -21,6 +21,7 @@ #include "delay.h" #include "hackrf_core.h" +#include "leds.h" #include "platform_detect.h" #include "power.h" diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index b0b8a563..c6429f6d 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -28,10 +28,10 @@ #include "platform_detect.h" #include "clock_gen.h" -#include "delay.h" #include "gpio.h" #include "hackrf_core.h" #include "i2c_lpc.h" +#include "leds.h" #include "max283x.h" #include "max5864_target.h" #include "platform_gpio.h" @@ -512,62 +512,6 @@ void pin_setup(void) sgpio_configure_pin_functions(&sgpio_config); } -void led_on(const led_t led) -{ -#ifdef IS_PRALINE - if (IS_PRALINE) { - gpio_clear(platform_gpio()->led[led]); - } -#endif -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - gpio_set(platform_gpio()->led[led]); - } -#endif -} - -void led_off(const led_t led) -{ -#ifdef IS_PRALINE - if (IS_PRALINE) { - gpio_set(platform_gpio()->led[led]); - } -#endif -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - gpio_clear(platform_gpio()->led[led]); - } -#endif -} - -void led_toggle(const led_t led) -{ - gpio_toggle(platform_gpio()->led[led]); -} - -void set_leds(const uint8_t state) -{ - int num_leds = 3; -#ifdef IS_FOUR_LEDS - if (IS_FOUR_LEDS) { - num_leds = 4; - } -#endif - - for (int i = 0; i < num_leds; i++) { -#ifdef IS_PRALINE - if (IS_PRALINE) { - gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); - } -#endif -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); - } -#endif - } -} - void trigger_enable(const bool enable) { #ifdef IS_NOT_PRALINE @@ -582,21 +526,6 @@ void trigger_enable(const bool enable) #endif } -void halt_and_flash(const uint32_t duration) -{ - /* blink LED1, LED2, and LED3 */ - while (1) { - led_on(LED1); - led_on(LED2); - led_on(LED3); - delay(duration); - led_off(LED1); - led_off(LED2); - led_off(LED3); - delay(duration); - } -} - #ifdef IS_PRALINE void clkin_ctrl_set(const clkin_signal_t signal) { diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 1d783236..7b6e1336 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -28,7 +28,9 @@ extern "C" { #endif #include -#include +#ifdef IS_PRALINE + #include +#endif #include "cpld_jtag.h" #include "i2c_bus.h" @@ -77,22 +79,8 @@ void ssp1_set_mode_ice40(void); void pin_shutdown(void); void pin_setup(void); -typedef enum { - LED1 = 0, - LED2 = 1, - LED3 = 2, - LED4 = 3, -} led_t; - -void led_on(const led_t led); -void led_off(const led_t led); -void led_toggle(const led_t led); -void set_leds(const uint8_t state); - void trigger_enable(const bool enable); -void halt_and_flash(const uint32_t duration); - #ifdef IS_PRALINE typedef enum { CLKIN_SIGNAL_P1 = 0, diff --git a/firmware/common/leds.c b/firmware/common/leds.c new file mode 100644 index 00000000..ab9b56fb --- /dev/null +++ b/firmware/common/leds.c @@ -0,0 +1,100 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "leds.h" + +#include + +#include "delay.h" +#include "gpio.h" +#include "platform_detect.h" +#include "platform_gpio.h" + +void led_on(const led_t led) +{ +#ifdef IS_PRALINE + if (IS_PRALINE) { + gpio_clear(platform_gpio()->led[led]); + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + gpio_set(platform_gpio()->led[led]); + } +#endif +} + +void led_off(const led_t led) +{ +#ifdef IS_PRALINE + if (IS_PRALINE) { + gpio_set(platform_gpio()->led[led]); + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + gpio_clear(platform_gpio()->led[led]); + } +#endif +} + +void led_toggle(const led_t led) +{ + gpio_toggle(platform_gpio()->led[led]); +} + +void set_leds(const uint8_t state) +{ + int num_leds = 3; +#ifdef IS_FOUR_LEDS + if (IS_FOUR_LEDS) { + num_leds = 4; + } +#endif + + for (int i = 0; i < num_leds; i++) { +#ifdef IS_PRALINE + if (IS_PRALINE) { + gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 0); + } +#endif +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + gpio_write(platform_gpio()->led[i], ((state >> i) & 1) == 1); + } +#endif + } +} + +void halt_and_flash(const uint32_t duration) +{ + /* blink LED1, LED2, and LED3 */ + while (1) { + led_on(LED1); + led_on(LED2); + led_on(LED3); + delay(duration); + led_off(LED1); + led_off(LED2); + led_off(LED3); + delay(duration); + } +} diff --git a/firmware/common/leds.h b/firmware/common/leds.h new file mode 100644 index 00000000..a552dca6 --- /dev/null +++ b/firmware/common/leds.h @@ -0,0 +1,46 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef enum { + LED1 = 0, + LED2 = 1, + LED3 = 2, + LED4 = 3, +} led_t; + +void led_on(const led_t led); +void led_off(const led_t led); +void led_toggle(const led_t led); +void set_leds(const uint8_t state); + +void halt_and_flash(const uint32_t duration); + +#ifdef __cplusplus +} +#endif diff --git a/firmware/common/platform_detect.c b/firmware/common/platform_detect.c index b3903035..7c9fb53d 100644 --- a/firmware/common/platform_detect.c +++ b/firmware/common/platform_detect.c @@ -26,7 +26,7 @@ #include "firmware_info.h" #include "gpio.h" #include "gpio_lpc.h" -#include "hackrf_core.h" +#include "leds.h" #include "platform_detect.h" #include "platform_scu.h" diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 1a55400f..92a905f4 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -172,6 +172,7 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/cpu_clock.c ${PATH_HACKRF_FIRMWARE_COMMON}/delay.c ${PATH_HACKRF_FIRMWARE_COMMON}/hackrf_core.c + ${PATH_HACKRF_FIRMWARE_COMMON}/leds.c ${PATH_HACKRF_FIRMWARE_COMMON}/sgpio.c ${PATH_HACKRF_FIRMWARE_COMMON}/rf_path.c ${PATH_HACKRF_FIRMWARE_COMMON}/si5351c.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index eff69298..4cd68852 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_cpld.c b/firmware/hackrf_usb/usb_api_cpld.c index 05636a0b..7381601d 100644 --- a/firmware/hackrf_usb/usb_api_cpld.c +++ b/firmware/hackrf_usb/usb_api_cpld.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index b9498dab..632ce0e5 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 3d3ded45..153b760b 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include From 634bd605b92770defb63959a5736682951ec8d05 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Wed, 25 Mar 2026 14:53:00 +0200 Subject: [PATCH 062/161] firmware: move clkin_ctrl_set to clkin.h|c --- firmware/common/clkin.c | 13 ++++++++++++- firmware/common/clkin.h | 11 +++++++++++ firmware/common/hackrf_core.c | 6 +----- firmware/common/hackrf_core.h | 6 ------ firmware/hackrf_usb/usb_api_praline.c | 1 + 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/firmware/common/clkin.c b/firmware/common/clkin.c index 9f63681c..f5af1723 100644 --- a/firmware/common/clkin.c +++ b/firmware/common/clkin.c @@ -30,6 +30,10 @@ #include #include "gpdma.h" +#ifdef IS_PRALINE + #include "gpio.h" + #include "platform_gpio.h" +#endif #define CLOCK_CYCLES_1_MS (204000) #define MEASUREMENT_WINDOW_MS (50) @@ -114,4 +118,11 @@ void clkin_detect_init(void) uint32_t clkin_frequency(void) { return TIMER2_CR3 * (1000 / MEASUREMENT_WINDOW_MS); -}; +} + +#ifdef IS_PRALINE +void clkin_ctrl_set(const clkin_signal_t signal) +{ + gpio_write(platform_gpio()->clkin_ctrl, signal & 1); +} +#endif diff --git a/firmware/common/clkin.h b/firmware/common/clkin.h index 2cef67cf..53379013 100644 --- a/firmware/common/clkin.h +++ b/firmware/common/clkin.h @@ -23,5 +23,16 @@ #include +#include "platform_detect.h" // IWYU pragma: keep + void clkin_detect_init(void); uint32_t clkin_frequency(void); + +#ifdef IS_PRALINE +typedef enum { + CLKIN_SIGNAL_P1 = 0, + CLKIN_SIGNAL_P22 = 1, +} clkin_signal_t; + +void clkin_ctrl_set(const clkin_signal_t value); +#endif diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index c6429f6d..29ca3820 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -40,6 +40,7 @@ #include "spi_bus.h" #include "w25q80bv_target.h" #ifdef IS_PRALINE + #include "clkin.h" #include "ice40_spi.h" #endif @@ -527,11 +528,6 @@ void trigger_enable(const bool enable) } #ifdef IS_PRALINE -void clkin_ctrl_set(const clkin_signal_t signal) -{ - gpio_write(platform_gpio()->clkin_ctrl, signal & 1); -} - void narrowband_filter_set(const uint8_t value) { gpio_write(platform_gpio()->aa_en, value & 1); diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 7b6e1336..e0253458 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -82,13 +82,7 @@ void pin_setup(void); void trigger_enable(const bool enable); #ifdef IS_PRALINE -typedef enum { - CLKIN_SIGNAL_P1 = 0, - CLKIN_SIGNAL_P22 = 1, -} clkin_signal_t; - void narrowband_filter_set(const uint8_t value); -void clkin_ctrl_set(const clkin_signal_t value); #endif #ifdef __cplusplus diff --git a/firmware/hackrf_usb/usb_api_praline.c b/firmware/hackrf_usb/usb_api_praline.c index 47b59446..434418a5 100644 --- a/firmware/hackrf_usb/usb_api_praline.c +++ b/firmware/hackrf_usb/usb_api_praline.c @@ -23,6 +23,7 @@ #include "usb_api_praline.h" +#include #include #include #include From e2741090ad36b9aac6243fa25abad5eed9404ddd Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Fri, 27 Mar 2026 14:46:12 +0200 Subject: [PATCH 063/161] firmware: move narrowband_filter_set to rf_path.h|c --- firmware/common/fpga.h | 2 ++ firmware/common/hackrf_core.c | 7 ------- firmware/common/hackrf_core.h | 7 ------- firmware/common/rf_path.c | 10 ++++++++++ firmware/common/rf_path.h | 4 ++++ firmware/hackrf_usb/usb_api_praline.c | 4 ++-- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/firmware/common/fpga.h b/firmware/common/fpga.h index 4aa54239..65826b21 100644 --- a/firmware/common/fpga.h +++ b/firmware/common/fpga.h @@ -90,3 +90,5 @@ bool fpga_image_load(struct fpga_loader_t* loader, unsigned int index); bool fpga_spi_selftest(void); bool fpga_sgpio_selftest(void); bool fpga_if_xcvr_selftest(void); + +//void narrowband_filter_set(const uint8_t value); diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 29ca3820..58499442 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -526,10 +526,3 @@ void trigger_enable(const bool enable) } #endif } - -#ifdef IS_PRALINE -void narrowband_filter_set(const uint8_t value) -{ - gpio_write(platform_gpio()->aa_en, value & 1); -} -#endif diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index e0253458..3755ee2e 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -28,9 +28,6 @@ extern "C" { #endif #include -#ifdef IS_PRALINE - #include -#endif #include "cpld_jtag.h" #include "i2c_bus.h" @@ -81,10 +78,6 @@ void pin_setup(void); void trigger_enable(const bool enable); -#ifdef IS_PRALINE -void narrowband_filter_set(const uint8_t value); -#endif - #ifdef __cplusplus } #endif diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 6a8e3caf..6cadea1c 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -32,6 +32,9 @@ #include #include "platform_scu.h" #endif +#ifdef IS_PRALINE + #include "platform_gpio.h" +#endif /* * RF switches on Jawbreaker are controlled by General Purpose Outputs (GPO) on @@ -615,3 +618,10 @@ void rf_path_set_antenna(rf_path_t* const rf_path, const uint_fast8_t enable) switchctrl_set(rf_path, rf_path->switchctrl); } + +#ifdef IS_PRALINE +void narrowband_filter_set(const uint8_t value) +{ + gpio_write(platform_gpio()->aa_en, value & 1); +} +#endif diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index 42dd91cb..12f83b53 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -102,3 +102,7 @@ void rf_path_set_filter(rf_path_t* const rf_path, const rf_path_filter_t filter) void rf_path_set_lna(rf_path_t* const rf_path, const uint_fast8_t enable); void rf_path_set_antenna(rf_path_t* const rf_path, const uint_fast8_t enable); + +#ifdef IS_PRALINE +void narrowband_filter_set(const uint8_t value); +#endif diff --git a/firmware/hackrf_usb/usb_api_praline.c b/firmware/hackrf_usb/usb_api_praline.c index 434418a5..262e66df 100644 --- a/firmware/hackrf_usb/usb_api_praline.c +++ b/firmware/hackrf_usb/usb_api_praline.c @@ -25,12 +25,12 @@ #include #include -#include #include +#include #include #include #include -#if !(defined(DFU_MODE) || defined(RAM_MODE)) +#if !defined(DFU_MODE) && !defined(RAM_MODE) #include #endif From 5672be9d00a67a5ad8c2488580325d686b8365ef Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Tue, 31 Mar 2026 11:25:14 +0200 Subject: [PATCH 064/161] firmware: move clock-associated gpio functions to clkin.x and rename to clock_io.h|c --- firmware/common/clock_gen.c | 28 --------------- firmware/common/clock_gen.h | 27 --------------- firmware/common/{clkin.c => clock_io.c} | 45 +++++++++++++++++++++++-- firmware/common/{clkin.h => clock_io.h} | 26 +++++++++++++- firmware/common/hackrf_core.c | 18 ++-------- firmware/common/hackrf_core.h | 4 --- firmware/common/radio.c | 1 + firmware/common/sgpio.c | 2 +- firmware/common/si5351c.c | 2 +- firmware/hackrf-common.cmake | 2 +- firmware/hackrf_usb/hackrf_usb.c | 2 +- firmware/hackrf_usb/usb_api_praline.c | 3 +- 12 files changed, 76 insertions(+), 84 deletions(-) rename firmware/common/{clkin.c => clock_io.c} (81%) rename firmware/common/{clkin.h => clock_io.h} (64%) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index e29e5a91..3080c436 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -33,10 +33,6 @@ #include "delay.h" #include "portapack.h" #endif -#if defined(IS_PRALINE) - #include "gpio.h" - #include "platform_gpio.h" -#endif void clock_gen_init(void) { @@ -426,27 +422,3 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) return resultant_rate; } - -#ifdef IS_PRALINE -void p1_ctrl_set(const p1_ctrl_signal_t signal) -{ - const platform_gpio_t* gpio = platform_gpio(); - - gpio_write(gpio->p1_ctrl0, signal & 1); - gpio_write(gpio->p1_ctrl1, (signal >> 1) & 1); - gpio_write(gpio->p1_ctrl2, (signal >> 2) & 1); -} - -void p2_ctrl_set(const p2_ctrl_signal_t signal) -{ - const platform_gpio_t* gpio = platform_gpio(); - - gpio_write(gpio->p2_ctrl0, signal & 1); - gpio_write(gpio->p2_ctrl1, (signal >> 1) & 1); -} - -void pps_out_set(const uint8_t value) -{ - gpio_write(platform_gpio()->pps_out, value & 1); -} -#endif diff --git a/firmware/common/clock_gen.h b/firmware/common/clock_gen.h index 48fb6f20..0bc6439b 100644 --- a/firmware/common/clock_gen.h +++ b/firmware/common/clock_gen.h @@ -28,9 +28,6 @@ extern "C" { #include "platform_detect.h" // IWYU pragma: keep #include -#ifdef IS_PRALINE - #include -#endif #include "fixed_point.h" @@ -47,30 +44,6 @@ clock_source_t activate_best_clock_source(void); fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program); -#ifdef IS_PRALINE -typedef enum { - P1_SIGNAL_TRIGGER_IN = 0, - P1_SIGNAL_AUX_CLK1 = 1, - P1_SIGNAL_CLKIN = 2, - P1_SIGNAL_TRIGGER_OUT = 3, - P1_SIGNAL_P22_CLKIN = 4, - P1_SIGNAL_P2_5 = 5, - P1_SIGNAL_NC = 6, - P1_SIGNAL_AUX_CLK2 = 7, -} p1_ctrl_signal_t; - -typedef enum { - P2_SIGNAL_CLK3 = 0, - P2_SIGNAL_TRIGGER_IN = 2, - P2_SIGNAL_TRIGGER_OUT = 3, -} p2_ctrl_signal_t; - -void p1_ctrl_set(const p1_ctrl_signal_t signal); -void p2_ctrl_set(const p2_ctrl_signal_t signal); - -void pps_out_set(const uint8_t value); -#endif - #ifdef __cplusplus } #endif diff --git a/firmware/common/clkin.c b/firmware/common/clock_io.c similarity index 81% rename from firmware/common/clkin.c rename to firmware/common/clock_io.c index f5af1723..0a8b48d9 100644 --- a/firmware/common/clkin.c +++ b/firmware/common/clock_io.c @@ -1,5 +1,5 @@ /* - * Copyright 2022 Great Scott Gadgets + * Copyright 2022-2026 Great Scott Gadgets * * This file is part of HackRF. * @@ -19,8 +19,9 @@ * Boston, MA 02110-1301, USA. */ -#include "clkin.h" +#include "clock_io.h" +#include #include #include @@ -30,8 +31,10 @@ #include #include "gpdma.h" +#include "gpio.h" +#include "hackrf_core.h" #ifdef IS_PRALINE - #include "gpio.h" + #include "fpga.h" #include "platform_gpio.h" #endif @@ -120,9 +123,45 @@ uint32_t clkin_frequency(void) return TIMER2_CR3 * (1000 / MEASUREMENT_WINDOW_MS); } +void trigger_enable(const bool enable) +{ +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + gpio_write(sgpio_config.gpio_trigger_enable, enable); + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { + fpga_set_trigger_enable(&fpga, enable); + } +#endif +} + #ifdef IS_PRALINE void clkin_ctrl_set(const clkin_signal_t signal) { gpio_write(platform_gpio()->clkin_ctrl, signal & 1); } + +void p1_ctrl_set(const p1_ctrl_signal_t signal) +{ + const platform_gpio_t* gpio = platform_gpio(); + + gpio_write(gpio->p1_ctrl0, signal & 1); + gpio_write(gpio->p1_ctrl1, (signal >> 1) & 1); + gpio_write(gpio->p1_ctrl2, (signal >> 2) & 1); +} + +void p2_ctrl_set(const p2_ctrl_signal_t signal) +{ + const platform_gpio_t* gpio = platform_gpio(); + + gpio_write(gpio->p2_ctrl0, signal & 1); + gpio_write(gpio->p2_ctrl1, (signal >> 1) & 1); +} + +void pps_out_set(const uint8_t value) +{ + gpio_write(platform_gpio()->pps_out, value & 1); +} #endif diff --git a/firmware/common/clkin.h b/firmware/common/clock_io.h similarity index 64% rename from firmware/common/clkin.h rename to firmware/common/clock_io.h index 53379013..df540022 100644 --- a/firmware/common/clkin.h +++ b/firmware/common/clock_io.h @@ -1,5 +1,5 @@ /* - * Copyright 2022 Great Scott Gadgets + * Copyright 2022-2026 Great Scott Gadgets * * This file is part of HackRF. * @@ -21,6 +21,7 @@ #pragma once +#include #include #include "platform_detect.h" // IWYU pragma: keep @@ -28,11 +29,34 @@ void clkin_detect_init(void); uint32_t clkin_frequency(void); +void trigger_enable(const bool enable); + #ifdef IS_PRALINE typedef enum { CLKIN_SIGNAL_P1 = 0, CLKIN_SIGNAL_P22 = 1, } clkin_signal_t; +typedef enum { + P1_SIGNAL_TRIGGER_IN = 0, + P1_SIGNAL_AUX_CLK1 = 1, + P1_SIGNAL_CLKIN = 2, + P1_SIGNAL_TRIGGER_OUT = 3, + P1_SIGNAL_P22_CLKIN = 4, + P1_SIGNAL_P2_5 = 5, + P1_SIGNAL_NC = 6, + P1_SIGNAL_AUX_CLK2 = 7, +} p1_ctrl_signal_t; + +typedef enum { + P2_SIGNAL_CLK3 = 0, + P2_SIGNAL_TRIGGER_IN = 2, + P2_SIGNAL_TRIGGER_OUT = 3, +} p2_ctrl_signal_t; + void clkin_ctrl_set(const clkin_signal_t value); +void p1_ctrl_set(const p1_ctrl_signal_t signal); +void p2_ctrl_set(const p2_ctrl_signal_t signal); + +void pps_out_set(const uint8_t value); #endif diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 58499442..086e3961 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -21,6 +21,8 @@ * Boston, MA 02110-1301, USA. */ +#include + #include #include #include @@ -40,7 +42,7 @@ #include "spi_bus.h" #include "w25q80bv_target.h" #ifdef IS_PRALINE - #include "clkin.h" + #include "clock_io.h" #include "ice40_spi.h" #endif @@ -512,17 +514,3 @@ void pin_setup(void) sgpio_configure_pin_functions(&sgpio_config); } - -void trigger_enable(const bool enable) -{ -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - gpio_write(sgpio_config.gpio_trigger_enable, enable); - } -#endif -#ifdef IS_PRALINE - if (IS_PRALINE) { - fpga_set_trigger_enable(&fpga, enable); - } -#endif -} diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 3755ee2e..6e0c1cc0 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -27,8 +27,6 @@ extern "C" { #endif -#include - #include "cpld_jtag.h" #include "i2c_bus.h" #include "i2c_lpc.h" @@ -76,8 +74,6 @@ void ssp1_set_mode_ice40(void); void pin_shutdown(void); void pin_setup(void); -void trigger_enable(const bool enable); - #ifdef __cplusplus } #endif diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 760351ac..7191f1ff 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -25,6 +25,7 @@ #include +#include "clock_io.h" #include "fixed_point.h" #include "hackrf_core.h" #include "mixer.h" diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index 9d03924a..568f1208 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -32,7 +32,7 @@ #include "platform_scu.h" #include "sgpio.h" #ifdef IS_NOT_PRALINE - #include "hackrf_core.h" + #include "clock_io.h" #endif static void update_q_invert(sgpio_config_t* const config); diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 0acff63e..c9767405 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -23,7 +23,7 @@ #include #include -#include "clkin.h" +#include "clock_io.h" #include "delay.h" #include "platform_detect.h" #include "selftest.h" diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 92a905f4..6384acdc 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -192,7 +192,7 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/platform_gpio.c ${PATH_HACKRF_FIRMWARE_COMMON}/platform_scu.c ${PATH_HACKRF_FIRMWARE_COMMON}/firmware_info.c - ${PATH_HACKRF_FIRMWARE_COMMON}/clkin.c + ${PATH_HACKRF_FIRMWARE_COMMON}/clock_io.c ${PATH_HACKRF_FIRMWARE_COMMON}/gpdma.c ${PATH_HACKRF_FIRMWARE_COMMON}/radio.c ${PATH_HACKRF_FIRMWARE_COMMON}/selftest.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 4cd68852..bd2c5964 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -28,8 +28,8 @@ #include #include -#include #include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_praline.c b/firmware/hackrf_usb/usb_api_praline.c index 262e66df..d889f5da 100644 --- a/firmware/hackrf_usb/usb_api_praline.c +++ b/firmware/hackrf_usb/usb_api_praline.c @@ -23,8 +23,7 @@ #include "usb_api_praline.h" -#include -#include +#include #include #include #include From adf83cbac2396c568145e28bad48549b4e89561a Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Tue, 31 Mar 2026 10:59:18 +0200 Subject: [PATCH 065/161] firmware: move hackrf_core drivers & configurations to drivers.h|c --- firmware/common/clock_gen.c | 2 +- firmware/common/clock_io.c | 2 +- firmware/common/cpu_clock.c | 2 +- firmware/common/da7219.c | 2 +- firmware/common/drivers.c | 178 ++++++++++++++++++++++ firmware/common/drivers.h | 77 ++++++++++ firmware/common/fpga.c | 4 +- firmware/common/fpga_image.c | 2 +- firmware/common/fpga_selftest.c | 2 +- firmware/common/hackrf_core.c | 158 +------------------ firmware/common/hackrf_core.h | 44 ------ firmware/common/operacake.c | 2 +- firmware/common/portapack.c | 2 +- firmware/common/radio.c | 4 +- firmware/common/rf_path.c | 2 +- firmware/common/rom_iap.c | 2 +- firmware/common/ui_rad1o.c | 2 +- firmware/hackrf-common.cmake | 1 + firmware/hackrf_usb/hackrf_usb.c | 1 + firmware/hackrf_usb/usb_api_cpld.c | 2 +- firmware/hackrf_usb/usb_api_register.c | 2 +- firmware/hackrf_usb/usb_api_spiflash.c | 2 +- firmware/hackrf_usb/usb_api_sweep.c | 2 +- firmware/hackrf_usb/usb_api_transceiver.c | 2 +- 24 files changed, 284 insertions(+), 215 deletions(-) create mode 100644 firmware/common/drivers.c create mode 100644 firmware/common/drivers.h diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index 3080c436..b62308ce 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -23,7 +23,7 @@ #include -#include "hackrf_core.h" +#include "drivers.h" #include "hackrf_ui.h" #include "i2c_bus.h" #include "platform_detect.h" diff --git a/firmware/common/clock_io.c b/firmware/common/clock_io.c index 0a8b48d9..279eb8c5 100644 --- a/firmware/common/clock_io.c +++ b/firmware/common/clock_io.c @@ -30,9 +30,9 @@ #include #include +#include "drivers.h" #include "gpdma.h" #include "gpio.h" -#include "hackrf_core.h" #ifdef IS_PRALINE #include "fpga.h" #include "platform_gpio.h" diff --git a/firmware/common/cpu_clock.c b/firmware/common/cpu_clock.c index 1bbd9571..b69f0773 100644 --- a/firmware/common/cpu_clock.c +++ b/firmware/common/cpu_clock.c @@ -29,7 +29,7 @@ #endif #include "delay.h" -#include "hackrf_core.h" +#include "drivers.h" #include "i2c_bus.h" #ifdef IS_NOT_RAD1O #include "platform_detect.h" diff --git a/firmware/common/da7219.c b/firmware/common/da7219.c index 376e04a5..5af022d2 100644 --- a/firmware/common/da7219.c +++ b/firmware/common/da7219.c @@ -22,7 +22,7 @@ #include #include "da7219.h" -#include "hackrf_core.h" +#include "drivers.h" #include "i2c_bus.h" #define DA7219_REG_CHIP_ID1 0x81 diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c new file mode 100644 index 00000000..a6bd536a --- /dev/null +++ b/firmware/common/drivers.c @@ -0,0 +1,178 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "drivers.h" + +#include + +#include +#include + +#include "clock_gen.h" +#include "i2c_lpc.h" +#include "max283x.h" +#include "max5864_target.h" +#include "spi_bus.h" +#include "w25q80bv_target.h" +#if defined(IS_PRALINE) + #include "fpga.h" + #include "ice40_spi.h" +#endif + +i2c_bus_t i2c0 = { + .obj = (void*) I2C0_BASE, + .start = i2c_lpc_start, + .stop = i2c_lpc_stop, + .transfer = i2c_lpc_transfer, +}; + +i2c_bus_t i2c1 = { + .obj = (void*) I2C1_BASE, + .start = i2c_lpc_start, + .stop = i2c_lpc_stop, + .transfer = i2c_lpc_transfer, +}; + +// const i2c_lpc_config_t i2c_config_si5351c_slow_clock = { +// .duty_cycle_count = 15, +// }; + +const i2c_lpc_config_t i2c_config_si5351c_fast_clock = { + .duty_cycle_count = 255, +}; + +si5351c_driver_t clock_gen = { + .bus = &i2c0, + .i2c_address = 0x60, +}; + +ssp_config_t ssp_config_max283x = { + /* FIXME speed up once everything is working reliably */ + /* + // Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz + const uint8_t serial_clock_rate = 32; + const uint8_t clock_prescale_rate = 128; + */ + // Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz + .serial_clock_rate = 21, + .clock_prescale_rate = 2, +}; + +max283x_driver_t max283x = {}; + +ssp_config_t ssp_config_max5864 = { + /* FIXME speed up once everything is working reliably */ + /* + // Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz + const uint8_t serial_clock_rate = 32; + const uint8_t clock_prescale_rate = 128; + */ + // Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz + .data_bits = SSP_DATA_8BITS, + .serial_clock_rate = 21, + .clock_prescale_rate = 2, +}; + +spi_bus_t spi_bus_ssp1 = { + .obj = (void*) SSP1_BASE, + .config = &ssp_config_max5864, + .start = spi_ssp_start, + .stop = spi_ssp_stop, + .transfer = spi_ssp_transfer, + .transfer_gather = spi_ssp_transfer_gather, +}; + +max5864_driver_t max5864 = { + .bus = &spi_bus_ssp1, + .target_init = max5864_target_init, +}; + +ssp_config_t ssp_config_w25q80bv = { + .data_bits = SSP_DATA_8BITS, + .serial_clock_rate = 2, + .clock_prescale_rate = 2, +}; + +spi_bus_t spi_bus_ssp0 = { + .obj = (void*) SSP0_BASE, + .config = &ssp_config_w25q80bv, + .start = spi_ssp_start, + .stop = spi_ssp_stop, + .transfer = spi_ssp_transfer, + .transfer_gather = spi_ssp_transfer_gather, +}; + +w25q80bv_driver_t spi_flash = { + .bus = &spi_bus_ssp0, + .target_init = w25q80bv_target_init, +}; + +sgpio_config_t sgpio_config = { + .slice_mode_multislice = true, +}; + +#ifdef IS_PRALINE +ssp_config_t ssp_config_ice40_fpga = { + .data_bits = SSP_DATA_8BITS, + .spi_mode = SSP_CPOL_1_CPHA_1, + .serial_clock_rate = 21, + .clock_prescale_rate = 2, +}; + +ice40_spi_driver_t ice40 = { + .bus = &spi_bus_ssp1, +}; + +fpga_driver_t fpga = { + .bus = &ice40, +}; +#endif + +radio_t radio = { + .sample_rate_cb = sample_rate_set, +}; + +rf_path_t rf_path = { + .switchctrl = 0, +}; + +jtag_gpio_t jtag_gpio_cpld = {}; + +jtag_t jtag_cpld = { + .gpio = &jtag_gpio_cpld, +}; + +void ssp1_set_mode_max283x(void) +{ + spi_bus_start(&spi_bus_ssp1, &ssp_config_max283x); +} + +void ssp1_set_mode_max5864(void) +{ + spi_bus_start(max5864.bus, &ssp_config_max5864); +} + +#ifdef IS_PRALINE +void ssp1_set_mode_ice40(void) +{ + spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); +} +#endif diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h new file mode 100644 index 00000000..6aa45f87 --- /dev/null +++ b/firmware/common/drivers.h @@ -0,0 +1,77 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "cpld_jtag.h" +#include "i2c_bus.h" +#include "i2c_lpc.h" +#include "max283x.h" +#include "max5864.h" +#include "mixer.h" +#include "platform_detect.h" // IWYU pragma: keep +#include "radio.h" +#include "rf_path.h" +#include "sgpio.h" +#include "si5351c.h" +#include "spi_ssp.h" +#include "w25q80bv.h" +#if defined(IS_PRALINE) + #include "fpga.h" + #include "ice40_spi.h" +#endif + +/* TODO: Hide these configurations */ +extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; +extern si5351c_driver_t clock_gen; +extern ssp_config_t ssp_config_max283x; +extern ssp_config_t ssp_config_max5864; +extern ssp_config_t ssp_config_w25q80bv; + +#if defined(IS_PRALINE) +extern ssp_config_t ssp_config_ice40_fpga; +extern ice40_spi_driver_t ice40; +extern fpga_driver_t fpga; +#endif +extern max283x_driver_t max283x; +extern max5864_driver_t max5864; +extern mixer_driver_t mixer; +extern w25q80bv_driver_t spi_flash; +extern sgpio_config_t sgpio_config; +extern radio_t radio; +extern rf_path_t rf_path; +extern jtag_gpio_t jtag_gpio_cpld; +extern jtag_t jtag_cpld; +extern i2c_bus_t i2c0; + +void ssp1_set_mode_max283x(void); +void ssp1_set_mode_max5864(void); +#if defined(IS_PRALINE) +void ssp1_set_mode_ice40(void); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/firmware/common/fpga.c b/firmware/common/fpga.c index 9c4d55f2..172810a8 100644 --- a/firmware/common/fpga.c +++ b/firmware/common/fpga.c @@ -20,11 +20,11 @@ */ #include "fpga.h" +#include "fpga_regs.def" #include -#include "fpga_regs.def" -#include "hackrf_core.h" +#include "drivers.h" #include "ice40_spi.h" /* Set up all registers according to the loaded bitstream's defaults. */ diff --git a/firmware/common/fpga_image.c b/firmware/common/fpga_image.c index dc88556b..989d16b2 100644 --- a/firmware/common/fpga_image.c +++ b/firmware/common/fpga_image.c @@ -23,8 +23,8 @@ #include #include +#include "drivers.h" #include "fpga.h" -#include "hackrf_core.h" #include "ice40_spi.h" #include "lz4_blk.h" #include "selftest.h" diff --git a/firmware/common/fpga_selftest.c b/firmware/common/fpga_selftest.c index fe0a96f9..30e70ea2 100644 --- a/firmware/common/fpga_selftest.c +++ b/firmware/common/fpga_selftest.c @@ -25,9 +25,9 @@ #include "clock_gen.h" #include "delay.h" +#include "drivers.h" #include "fixed_point.h" #include "fpga.h" -#include "hackrf_core.h" #include "ice40_spi.h" #include "m0_state.h" #include "max283x.h" diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 086e3961..2a7bc376 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -21,167 +21,23 @@ * Boston, MA 02110-1301, USA. */ -#include +#include "hackrf_core.h" -#include #include #include -#include "platform_detect.h" - -#include "clock_gen.h" +#include "drivers.h" #include "gpio.h" -#include "hackrf_core.h" -#include "i2c_lpc.h" #include "leds.h" -#include "max283x.h" -#include "max5864_target.h" +#include "mixer.h" +#include "platform_detect.h" #include "platform_gpio.h" #include "platform_scu.h" #include "power.h" -#include "spi_bus.h" -#include "w25q80bv_target.h" -#ifdef IS_PRALINE +#include "rf_path.h" +#include "sgpio.h" +#if defined(IS_PRALINE) #include "clock_io.h" - #include "ice40_spi.h" -#endif - -i2c_bus_t i2c0 = { - .obj = (void*) I2C0_BASE, - .start = i2c_lpc_start, - .stop = i2c_lpc_stop, - .transfer = i2c_lpc_transfer, -}; - -i2c_bus_t i2c1 = { - .obj = (void*) I2C1_BASE, - .start = i2c_lpc_start, - .stop = i2c_lpc_stop, - .transfer = i2c_lpc_transfer, -}; - -// const i2c_lpc_config_t i2c_config_si5351c_slow_clock = { -// .duty_cycle_count = 15, -// }; - -const i2c_lpc_config_t i2c_config_si5351c_fast_clock = { - .duty_cycle_count = 255, -}; - -si5351c_driver_t clock_gen = { - .bus = &i2c0, - .i2c_address = 0x60, -}; - -static ssp_config_t ssp_config_max283x = { - /* FIXME speed up once everything is working reliably */ - /* - // Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz - const uint8_t serial_clock_rate = 32; - const uint8_t clock_prescale_rate = 128; - */ - // Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz - .serial_clock_rate = 21, - .clock_prescale_rate = 2, -}; - -max283x_driver_t max283x = {}; - -static ssp_config_t ssp_config_max5864 = { - /* FIXME speed up once everything is working reliably */ - /* - // Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz - const uint8_t serial_clock_rate = 32; - const uint8_t clock_prescale_rate = 128; - */ - // Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz - .data_bits = SSP_DATA_8BITS, - .serial_clock_rate = 21, - .clock_prescale_rate = 2, -}; - -spi_bus_t spi_bus_ssp1 = { - .obj = (void*) SSP1_BASE, - .config = &ssp_config_max5864, - .start = spi_ssp_start, - .stop = spi_ssp_stop, - .transfer = spi_ssp_transfer, - .transfer_gather = spi_ssp_transfer_gather, -}; - -max5864_driver_t max5864 = { - .bus = &spi_bus_ssp1, - .target_init = max5864_target_init, -}; - -ssp_config_t ssp_config_w25q80bv = { - .data_bits = SSP_DATA_8BITS, - .serial_clock_rate = 2, - .clock_prescale_rate = 2, -}; - -static spi_bus_t spi_bus_ssp0 = { - .obj = (void*) SSP0_BASE, - .config = &ssp_config_w25q80bv, - .start = spi_ssp_start, - .stop = spi_ssp_stop, - .transfer = spi_ssp_transfer, - .transfer_gather = spi_ssp_transfer_gather, -}; - -w25q80bv_driver_t spi_flash = { - .bus = &spi_bus_ssp0, - .target_init = w25q80bv_target_init, -}; - -sgpio_config_t sgpio_config = { - .slice_mode_multislice = true, -}; - -#ifdef IS_PRALINE -static ssp_config_t ssp_config_ice40_fpga = { - .data_bits = SSP_DATA_8BITS, - .spi_mode = SSP_CPOL_1_CPHA_1, - .serial_clock_rate = 21, - .clock_prescale_rate = 2, -}; - -ice40_spi_driver_t ice40 = { - .bus = &spi_bus_ssp1, -}; - -fpga_driver_t fpga = { - .bus = &ice40, -}; -#endif - -radio_t radio = { - .sample_rate_cb = sample_rate_set, -}; - -rf_path_t rf_path; - -jtag_gpio_t jtag_gpio_cpld; - -jtag_t jtag_cpld = { - .gpio = &jtag_gpio_cpld, -}; - -void ssp1_set_mode_max283x(void) -{ - spi_bus_start(&spi_bus_ssp1, &ssp_config_max283x); -} - -void ssp1_set_mode_max5864(void) -{ - spi_bus_start(max5864.bus, &ssp_config_max5864); -} - -#ifdef IS_PRALINE -void ssp1_set_mode_ice40(void) -{ - spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); -} #endif void pin_shutdown(void) diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index 6e0c1cc0..20ae76f0 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -27,50 +27,6 @@ extern "C" { #endif -#include "cpld_jtag.h" -#include "i2c_bus.h" -#include "i2c_lpc.h" -#include "max283x.h" -#include "max5864.h" -#include "mixer.h" -#include "platform_detect.h" // IWYU pragma: keep -#include "radio.h" -#include "rf_path.h" -#include "sgpio.h" -#include "si5351c.h" -#include "spi_ssp.h" -#include "w25q80bv.h" -#ifdef IS_PRALINE - #include "fpga.h" - #include "ice40_spi.h" -#endif - -/* TODO: Hide these configurations */ -extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; -extern si5351c_driver_t clock_gen; -extern ssp_config_t ssp_config_w25q80bv; - -extern max283x_driver_t max283x; -#ifdef IS_PRALINE -extern ice40_spi_driver_t ice40; -extern fpga_driver_t fpga; -#endif -extern max283x_driver_t max283x; -extern max5864_driver_t max5864; -extern mixer_driver_t mixer; -extern w25q80bv_driver_t spi_flash; -extern sgpio_config_t sgpio_config; -extern radio_t radio; -extern rf_path_t rf_path; -extern jtag_t jtag_cpld; -extern i2c_bus_t i2c0; - -void ssp1_set_mode_max283x(void); -void ssp1_set_mode_max5864(void); -#ifdef IS_PRALINE -void ssp1_set_mode_ice40(void); -#endif - void pin_shutdown(void); void pin_setup(void); diff --git a/firmware/common/operacake.c b/firmware/common/operacake.c index 61d78512..24ca6541 100644 --- a/firmware/common/operacake.c +++ b/firmware/common/operacake.c @@ -26,9 +26,9 @@ #include +#include "drivers.h" #include "gpio.h" #include "gpio_lpc.h" -#include "hackrf_core.h" #include "i2c_bus.h" #include "operacake_sctimer.h" #include "platform_scu.h" diff --git a/firmware/common/portapack.c b/firmware/common/portapack.c index 455df871..f25a4a6e 100644 --- a/firmware/common/portapack.c +++ b/firmware/common/portapack.c @@ -26,9 +26,9 @@ #include "cpld_jtag.h" #include "delay.h" +#include "drivers.h" #include "gpio.h" #include "gpio_lpc.h" -#include "hackrf_core.h" #include "platform_gpio.h" #include "platform_scu.h" diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 65412c4b..2d3ce8c3 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -26,10 +26,10 @@ #include #include "clock_io.h" +#include "drivers.h" #include "fixed_point.h" -#include "hackrf_core.h" -#include "mixer.h" #include "max283x.h" +#include "mixer.h" #include "platform_detect.h" #include "rf_path.h" #include "transceiver_mode.h" diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 6cadea1c..0506a3bf 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -23,7 +23,7 @@ #include "rf_path.h" -#include "hackrf_core.h" +#include "drivers.h" #include "max283x.h" #include "max5864.h" #include "mixer.h" diff --git a/firmware/common/rom_iap.c b/firmware/common/rom_iap.c index a8f185ad..09da0b7c 100644 --- a/firmware/common/rom_iap.c +++ b/firmware/common/rom_iap.c @@ -22,7 +22,7 @@ #include -#include "hackrf_core.h" +#include "drivers.h" #include "rom_iap.h" #include "spi_bus.h" #include "w25q80bv.h" diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index 878e9e74..757e11f1 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -27,7 +27,7 @@ #include #include "clock_gen.h" -#include "hackrf_core.h" +#include "drivers.h" #include "rf_path.h" #include "transceiver_mode.h" #ifdef IS_NOT_RAD1O diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 6384acdc..664f23f1 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -168,6 +168,7 @@ endmacro() macro(DeclareTargets) SET(SRC_M4 ${SRC_M4} + ${PATH_HACKRF_FIRMWARE_COMMON}/drivers.c ${PATH_HACKRF_FIRMWARE_COMMON}/power.c ${PATH_HACKRF_FIRMWARE_COMMON}/cpu_clock.c ${PATH_HACKRF_FIRMWARE_COMMON}/delay.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index bd2c5964..982a84bf 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_cpld.c b/firmware/hackrf_usb/usb_api_cpld.c index 7381601d..ed2beeda 100644 --- a/firmware/hackrf_usb/usb_api_cpld.c +++ b/firmware/hackrf_usb/usb_api_cpld.c @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index 632ce0e5..58c1684c 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -27,7 +27,7 @@ #include #include -#include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_spiflash.c b/firmware/hackrf_usb/usb_api_spiflash.c index d422836e..2c944a10 100644 --- a/firmware/hackrf_usb/usb_api_spiflash.c +++ b/firmware/hackrf_usb/usb_api_spiflash.c @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index 4f7d8e57..ae3b399d 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -25,8 +25,8 @@ #include +#include #include -#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 153b760b..b6c6782b 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -27,8 +27,8 @@ #include #include +#include #include -#include #include #include #include From 679179eb63976260389b98d958550608a52471fa Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Fri, 24 Apr 2026 09:03:47 +0200 Subject: [PATCH 066/161] firmware: rename hackrf_core.x to pins.x --- firmware/blinky/blinky.c | 4 ++-- firmware/common/{hackrf_core.c => pins.c} | 8 ++++---- firmware/common/{hackrf_core.h => pins.h} | 6 ++++-- firmware/common/rf_path.c | 2 +- firmware/hackrf-common.cmake | 2 +- firmware/hackrf_usb/hackrf_usb.c | 6 +++--- firmware/hackrf_usb/usb_api_board_info.c | 4 ++-- 7 files changed, 17 insertions(+), 15 deletions(-) rename firmware/common/{hackrf_core.c => pins.c} (98%) rename firmware/common/{hackrf_core.h => pins.h} (91%) diff --git a/firmware/blinky/blinky.c b/firmware/blinky/blinky.c index 0bf89bf8..fdaf3ef7 100644 --- a/firmware/blinky/blinky.c +++ b/firmware/blinky/blinky.c @@ -20,15 +20,15 @@ */ #include "delay.h" -#include "hackrf_core.h" #include "leds.h" +#include "pins.h" #include "platform_detect.h" #include "power.h" int main(void) { detect_hardware_platform(); - pin_setup(); + pins_setup(); #ifndef PRALINE /* enable 1V8 power supply so that the 1V8 LED lights up */ diff --git a/firmware/common/hackrf_core.c b/firmware/common/pins.c similarity index 98% rename from firmware/common/hackrf_core.c rename to firmware/common/pins.c index 2a7bc376..35b77838 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/pins.c @@ -21,7 +21,7 @@ * Boston, MA 02110-1301, USA. */ -#include "hackrf_core.h" +#include "pins.h" #include #include @@ -40,7 +40,7 @@ #include "clock_io.h" #endif -void pin_shutdown(void) +void pins_shutdown(void) { /* Configure all GPIO as Input (safe state) */ gpio_init(); @@ -209,8 +209,8 @@ void pin_shutdown(void) SCU_SFSI2C0 = SCU_I2C0_NOMINAL; } -/* Run after pin_shutdown() and prior to enabling power supplies. */ -void pin_setup(void) +/* Run after pins_shutdown() and prior to enabling power supplies. */ +void pins_setup(void) { /* Detect Platform */ const platform_gpio_t* gpio = platform_gpio(); diff --git a/firmware/common/hackrf_core.h b/firmware/common/pins.h similarity index 91% rename from firmware/common/hackrf_core.h rename to firmware/common/pins.h index 20ae76f0..b9454d18 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/pins.h @@ -27,8 +27,10 @@ extern "C" { #endif -void pin_shutdown(void); -void pin_setup(void); +#include "platform_detect.h" // IWYU pragma: keep + +void pins_shutdown(void); +void pins_setup(void); #ifdef __cplusplus } diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 0506a3bf..ea1c0d45 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -319,7 +319,7 @@ static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) void rf_path_pin_setup(rf_path_t* const rf_path) { -#ifdef JAWBREAKER +#ifdef IS_JAWBREAKER (void) rf_path; #else const platform_scu_t* scu = platform_scu(); diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 664f23f1..04d8b1a3 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -172,7 +172,7 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/power.c ${PATH_HACKRF_FIRMWARE_COMMON}/cpu_clock.c ${PATH_HACKRF_FIRMWARE_COMMON}/delay.c - ${PATH_HACKRF_FIRMWARE_COMMON}/hackrf_core.c + ${PATH_HACKRF_FIRMWARE_COMMON}/pins.c ${PATH_HACKRF_FIRMWARE_COMMON}/leds.c ${PATH_HACKRF_FIRMWARE_COMMON}/sgpio.c ${PATH_HACKRF_FIRMWARE_COMMON}/rf_path.c diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 982a84bf..54431ff3 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -35,10 +35,10 @@ #include #include #include -#include #include #include #include +#include #include #include #include @@ -444,12 +444,12 @@ int main(void) detect_hardware_platform(); board_id_t board_id = detected_platform(); - pin_shutdown(); + pins_shutdown(); if (board_id != BOARD_ID_RAD1O) { clock_gen_shutdown(); } delay_us_at_mhz(10000, 96); - pin_setup(); + pins_setup(); #ifdef IS_PRALINE if (IS_PRALINE) { enable_3v3aux_power(); diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index c4634605..2d017378 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -28,7 +28,7 @@ #include #include -#include +#include #include #include #include @@ -129,7 +129,7 @@ usb_request_status_t usb_vendor_request_reset( const usb_transfer_stage_t stage) { if (stage == USB_TRANSFER_STAGE_SETUP) { - pin_shutdown(); + pins_shutdown(); clock_gen_shutdown(); #ifdef IS_H1_R9 if (IS_H1_R9) { From db376ad1946b6f14b8c00bb5856d18869192f231 Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Tue, 17 Mar 2026 17:08:49 -0400 Subject: [PATCH 067/161] Change Praline CLKIN default to P1_CLKIN Previously the CLKIN switch defaulted to P22_CLKIN, enabling usage of the clock input on pin header P22 but not on SMA port P1. This change enables use of clock input on P1 and disables P22_CLKIN by default. --- ci-scripts/hackrf_pro_test.py | 3 --- firmware/common/pins.c | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ci-scripts/hackrf_pro_test.py b/ci-scripts/hackrf_pro_test.py index 3d0b1c63..44b6eb56 100644 --- a/ci-scripts/hackrf_pro_test.py +++ b/ci-scripts/hackrf_pro_test.py @@ -663,9 +663,6 @@ class HackRF: self.rf_test_cases = praline_r110_rf_test_cases def clkin(self): - if self.name == "EUT" and self.id == "5": - debug = subprocess.run([self.bin_dir + "/hackrf_debug", "-d", self.serial, - "-C", "0"]) command = subprocess.run([self.bin_dir + "/hackrf_clock", "-i", "-d", self.serial], capture_output=True, encoding="utf-8", timeout=TIMEOUT) diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 35b77838..6f8123cb 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -185,7 +185,7 @@ void pins_shutdown(void) p2_ctrl_set(P2_SIGNAL_CLK3); p1_ctrl_set(P1_SIGNAL_CLKIN); narrowband_filter_set(0); - clkin_ctrl_set(CLKIN_SIGNAL_P22); + clkin_ctrl_set(CLKIN_SIGNAL_P1); gpio_output(gpio->p2_ctrl0); gpio_output(gpio->p2_ctrl1); From 3e1542261a64b333d246b5e162ed88fe15caa28c Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Tue, 21 Apr 2026 10:21:40 -0400 Subject: [PATCH 068/161] Add CLKIN control option to hackrf_clock --- host/hackrf-tools/src/hackrf_clock.c | 37 ++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/host/hackrf-tools/src/hackrf_clock.c b/host/hackrf-tools/src/hackrf_clock.c index 36f066c1..01c2c800 100644 --- a/host/hackrf-tools/src/hackrf_clock.c +++ b/host/hackrf-tools/src/hackrf_clock.c @@ -103,6 +103,19 @@ int parse_p2_ctrl_signal(char* s, enum p2_ctrl_signal* const signal) return HACKRF_SUCCESS; } +int parse_clkin_ctrl_signal(char* s, enum clkin_ctrl_signal* const signal) +{ + if (strcasecmp("p1", s) == 0) { + *signal = CLKIN_SIGNAL_P1; + } else if (strcasecmp("p22", s) == 0) { + *signal = CLKIN_SIGNAL_P22; + } else { + fprintf(stderr, "Invalid signal '%s'\n", s); + return HACKRF_ERROR_INVALID_PARAM; + } + return HACKRF_SUCCESS; +} + int si5351c_read_register(hackrf_device* device, const uint16_t register_number) { uint16_t register_value; @@ -295,6 +308,8 @@ static void usage() printf("\tone of: clkin, trigger_in, trigger_out, p22_clkin, pps_out, aux_clk1, aux_clk2, off\n"); printf("\t-2, --p2 : select the signal for the HackRF Pro P2 SMA connector (default: clkout)\n"); printf("\tone of: clkout, trigger_in, trigger_out\n"); + printf("\t-c, --clkin-ctrl : select the HackRF Pro CLKIN signal (default: p1)\n"); + printf("\tone of: p1, p22\n"); printf("\t-d, --device : Serial number of desired HackRF.\n"); printf("\nExamples:\n"); printf("\thackrf_clock -r 3 : prints settings for CLKOUT\n"); @@ -308,6 +323,7 @@ static struct option long_options[] = { {"clkout", required_argument, 0, 'o'}, {"p1", required_argument, 0, '1'}, {"p2", required_argument, 0, '2'}, + {"clkin-ctrl", required_argument, 0, 'c'}, {"device", required_argument, 0, 'd'}, {0, 0, 0, 0}, }; @@ -324,8 +340,10 @@ int main(int argc, char** argv) uint8_t clkin_status; bool p1_ctrl = false; bool p2_ctrl = false; + bool clkin_ctrl = false; enum p1_ctrl_signal p1_signal = P1_SIGNAL_CLKIN; enum p2_ctrl_signal p2_signal = P2_SIGNAL_CLK3; + enum clkin_ctrl_signal clkin_signal = CLKIN_SIGNAL_P1; const char* serial_number = NULL; int result = hackrf_init(); @@ -339,7 +357,7 @@ int main(int argc, char** argv) while ((opt = getopt_long( argc, argv, - "r:aio:1:2:d:h?", + "r:aio:1:2:c:d:h?", long_options, &option_index)) != EOF) { switch (opt) { @@ -371,6 +389,11 @@ int main(int argc, char** argv) result = parse_p2_ctrl_signal(optarg, &p2_signal); break; + case 'c': + clkin_ctrl = true; + result = parse_clkin_ctrl_signal(optarg, &clkin_signal); + break; + case 'd': serial_number = optarg; break; @@ -394,7 +417,7 @@ int main(int argc, char** argv) } } - if (!clkin && !clkout && !read && !p1_ctrl && !p2_ctrl) { + if (!clkin && !clkout && !read && !p1_ctrl && !p2_ctrl && !clkin_ctrl) { fprintf(stderr, "An operation must be specified.\n"); usage(); return EXIT_FAILURE; @@ -460,6 +483,16 @@ int main(int argc, char** argv) } } + if (clkin_ctrl) { + result = hackrf_set_clkin_ctrl(device, clkin_signal); + if (result != HACKRF_SUCCESS) { + printf("hackrf_set_clkin_ctrl() failed: %s (%d)\n", + hackrf_error_name(result), + result); + return EXIT_FAILURE; + } + } + result = hackrf_close(device); if (result) { printf("hackrf_close() failed: %s (%d)\n", From fd0c7cc5f1f416f8097ede8e03790845086a1dfa Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Tue, 21 Apr 2026 13:01:58 -0400 Subject: [PATCH 069/161] Document Praline P1, P2, and CLKIN control --- docs/source/external_clock_interface.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/source/external_clock_interface.rst b/docs/source/external_clock_interface.rst index fc3f3b1d..9c09ca66 100644 --- a/docs/source/external_clock_interface.rst +++ b/docs/source/external_clock_interface.rst @@ -9,12 +9,18 @@ HackRF Pro HackRF Pro has two configurable SMA ports, P1 and P2. By default, P1 is configured as CLKIN and P2 as CLKOUT. The default behaviour of these signals is as described for HackRF One below. +A second CLKIN signal is available on header P22 pin 2. Unlike HackRF One, HackRF Pro's P22_CLKIN is a separate signal from P1_CLKIN. To enable P22_CLKIN instead of P1_CLKIN use ``hackrf_clock -c p22``. + +Various internal signals can be connected to P1 or P2 instead of the default CLKIN and CLKOUT signals. Use ``hackrf_clock -1`` or ``hackrf_clock -2`` to select a different signal. + HackRF One ~~~~~~~~~~ -HackRF One produces a 10 MHz clock signal on CLKOUT. The signal is a 3.3 V, 10 MHz square wave intended for a high impedance load. +HackRF One produces a 10 MHz clock signal on the CLKOUT SMA port. The signal is a 3.3 V, 10 MHz square wave intended for a high impedance load. -The CLKIN port on HackRF One is a high impedance input that expects 3.3 V square wave at 10 MHz. Do not exceed 3.3 V or drop below 0 V on this input. Do not connect a clock signal at a frequency other than 10 MHz (unless you modify the firmware to support this). You may directly connect the CLKOUT port of one HackRF One to the CLKIN port of another HackRF One. +The CLKIN SMA port on HackRF One is a high impedance input that expects 3.3 V square wave at 10 MHz. Do not exceed 3.3 V or drop below 0 V on this input. Do not connect a clock signal at a frequency other than 10 MHz (unless you modify the firmware to support this). You may directly connect the CLKOUT port of one HackRF One to the CLKIN port of another HackRF. + +The CLKIN signal is also connected to header P22 pin 2. Unlike HackRF Pro, HackRF One has only one CLKIN signal shared between P22 pin 2 and the CLKIN port. Do not connect input signals to both CLKIN and P22 pin 2 simultaneously. HackRF One uses CLKIN instead of the internal crystal when a clock signal is detected on CLKIN. The switch to or from CLKIN only happens when a transmit or receive operation begins. From ba5fe7f7daa9ee487df8950134896a8df35a54cb Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 18 Oct 2025 23:08:12 +0100 Subject: [PATCH 070/161] Rename usb_bulk_buffer to usb_samp_buffer. --- firmware/common/LPC43xx_M4_memory.ld | 4 ++-- firmware/common/fpga_selftest.c | 20 +++++++++---------- firmware/hackrf_usb/usb_api_sweep.c | 4 ++-- firmware/hackrf_usb/usb_api_transceiver.c | 10 +++++----- .../{usb_bulk_buffer.h => usb_buffer.h} | 8 ++++---- 5 files changed, 23 insertions(+), 23 deletions(-) rename firmware/hackrf_usb/{usb_bulk_buffer.h => usb_buffer.h} (85%) diff --git a/firmware/common/LPC43xx_M4_memory.ld b/firmware/common/LPC43xx_M4_memory.ld index 933fa854..d37542f9 100644 --- a/firmware/common/LPC43xx_M4_memory.ld +++ b/firmware/common/LPC43xx_M4_memory.ld @@ -35,11 +35,11 @@ MEMORY * to get performance benefit of having two USB buffers addressable * simultaneously (on two different buses of the AHB multilayer matrix) */ - ram_usb (rwx) : ORIGIN = 0x20008000, LENGTH = 32K + ram_samp (rwx) : ORIGIN = 0x20008000, LENGTH = 32K } -usb_bulk_buffer = ORIGIN(ram_usb); lz4_in_buf = ORIGIN(ram_lz4_in); lz4_out_buf = ORIGIN(ram_lz4_out); +usb_samp_buffer = ORIGIN(ram_samp); m0_state = ORIGIN(ram_shared); PROVIDE(__ram_m0_start__ = ORIGIN(ram_m0)); diff --git a/firmware/common/fpga_selftest.c b/firmware/common/fpga_selftest.c index 30e70ea2..b23be525 100644 --- a/firmware/common/fpga_selftest.c +++ b/firmware/common/fpga_selftest.c @@ -37,8 +37,8 @@ #include "streaming.h" // USB buffer used during selftests. -#define USB_BULK_BUFFER_SIZE 0x8000 -extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE]; +#define USB_SAMP_BUFFER_SIZE 0x8000 +extern uint8_t usb_samp_buffer[USB_SAMP_BUFFER_SIZE]; static int rx_samples(const unsigned int num_samples, uint32_t max_cycles) { @@ -115,10 +115,10 @@ bool fpga_sgpio_selftest(void) fpga_set_prbs_enable(&fpga, false); // Generate sequence from first value and compare. - bool seq_in_sync = (usb_bulk_buffer[0] != 0); - uint8_t seq = lfsr_advance(usb_bulk_buffer[0]); + bool seq_in_sync = (usb_samp_buffer[0] != 0); + uint8_t seq = lfsr_advance(usb_samp_buffer[0]); for (int i = 1; i < 512; ++i) { - if (usb_bulk_buffer[i] != seq) { + if (usb_samp_buffer[i] != seq) { seq_in_sync = false; break; } @@ -189,7 +189,7 @@ bool fpga_if_xcvr_selftest(void) return false; } - const size_t num_samples = USB_BULK_BUFFER_SIZE / 2; + const size_t num_samples = USB_SAMP_BUFFER_SIZE / 2; // Set common RX path and gateware settings for the measurements. fpga_set_tx_nco_pstep(&fpga, 64); // NCO phase increment @@ -206,7 +206,7 @@ bool fpga_if_xcvr_selftest(void) timeout = true; } measure_tone( - (int8_t*) usb_bulk_buffer, + (int8_t*) usb_samp_buffer, num_samples, &selftest.xcvr_measurements[0]); @@ -217,7 +217,7 @@ bool fpga_if_xcvr_selftest(void) timeout = true; } measure_tone( - (int8_t*) usb_bulk_buffer, + (int8_t*) usb_samp_buffer, num_samples, &selftest.xcvr_measurements[1]); @@ -230,7 +230,7 @@ bool fpga_if_xcvr_selftest(void) timeout = true; } measure_tone( - (int8_t*) usb_bulk_buffer, + (int8_t*) usb_samp_buffer, num_samples, &selftest.xcvr_measurements[2]); @@ -241,7 +241,7 @@ bool fpga_if_xcvr_selftest(void) timeout = true; } measure_tone( - (int8_t*) usb_bulk_buffer, + (int8_t*) usb_samp_buffer, num_samples, &selftest.xcvr_measurements[3]); diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index ae3b399d..eee74228 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -40,7 +40,7 @@ #include "usb_api_sweep.h" #include "usb_api_transceiver.h" -#include "usb_bulk_buffer.h" +#include "usb_buffer.h" #include "usb_endpoint.h" #define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -207,7 +207,7 @@ void sweep_mode(uint32_t seq) m0_state.next_mode = M0_MODE_RX; // Write metadata to buffer. - buffer = &usb_bulk_buffer[phase * 0x4000]; + buffer = &usb_samp_buffer[phase * 0x4000]; *buffer = 0x7f; *(buffer + 1) = 0x7f; *(buffer + 2) = sweep_freq & 0xff; diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index b6c6782b..f272dac5 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -41,7 +41,7 @@ #include #include -#include "usb_bulk_buffer.h" +#include "usb_buffer.h" #include "usb_endpoint.h" #define USB_TRANSFER_SIZE 0x4000 @@ -442,7 +442,7 @@ void rx_mode(uint32_t seq) if ((m0_state.m0_count - usb_count) >= USB_TRANSFER_SIZE) { usb_transfer_schedule_block( &usb_endpoint_bulk_in, - &usb_bulk_buffer[usb_count & USB_BULK_BUFFER_MASK], + &usb_samp_buffer[usb_count & USB_SAMP_BUFFER_MASK], USB_TRANSFER_SIZE, transceiver_bulk_transfer_complete, NULL); @@ -464,14 +464,14 @@ void tx_mode(uint32_t seq) // Set up OUT transfer of buffer 0. usb_transfer_schedule_block( &usb_endpoint_bulk_out, - &usb_bulk_buffer[0x0000], + &usb_samp_buffer[0x0000], USB_TRANSFER_SIZE, transceiver_bulk_transfer_complete, NULL); usb_count += USB_TRANSFER_SIZE; while (transceiver_request.seq == seq) { - if (!started && (m0_state.m4_count == USB_BULK_BUFFER_SIZE)) { + if (!started && (m0_state.m4_count == USB_SAMP_BUFFER_SIZE)) { // Buffer is now full, start streaming. baseband_streaming_enable(&sgpio_config); started = true; @@ -479,7 +479,7 @@ void tx_mode(uint32_t seq) if ((usb_count - m0_state.m0_count) <= USB_TRANSFER_SIZE) { usb_transfer_schedule_block( &usb_endpoint_bulk_out, - &usb_bulk_buffer[usb_count & USB_BULK_BUFFER_MASK], + &usb_samp_buffer[usb_count & USB_SAMP_BUFFER_MASK], USB_TRANSFER_SIZE, transceiver_bulk_transfer_complete, NULL); diff --git a/firmware/hackrf_usb/usb_bulk_buffer.h b/firmware/hackrf_usb/usb_buffer.h similarity index 85% rename from firmware/hackrf_usb/usb_bulk_buffer.h rename to firmware/hackrf_usb/usb_buffer.h index e21a84ff..5aecdb67 100644 --- a/firmware/hackrf_usb/usb_bulk_buffer.h +++ b/firmware/hackrf_usb/usb_buffer.h @@ -25,11 +25,11 @@ #include -#define USB_BULK_BUFFER_SIZE 0x8000 -#define USB_BULK_BUFFER_MASK 0x7FFF +#define USB_SAMP_BUFFER_SIZE 0x8000 +#define USB_SAMP_BUFFER_MASK 0x7FFF -/* Address of usb_bulk_buffer is set in ldscripts. If you change the name of this +/* Address of usb_samp_buffer is set in ldscripts. If you change the name of this * variable, it won't be where it needs to be in the processor's address space, * unless you also adjust the ldscripts. */ -extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE]; +extern uint8_t usb_samp_buffer[USB_SAMP_BUFFER_SIZE]; From d222e0188e3bf0ecdcde15f30cc6fb0251ba8978 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 19 Oct 2025 22:39:10 +0100 Subject: [PATCH 071/161] Add new usb_bulk_buffer. --- firmware/common/LPC4320_M4_memory.ld | 3 ++- firmware/common/LPC4330_M4_memory.ld | 3 ++- firmware/common/LPC43xx_M4_memory.ld | 1 + firmware/hackrf_usb/usb_buffer.h | 10 +++++++--- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/firmware/common/LPC4320_M4_memory.ld b/firmware/common/LPC4320_M4_memory.ld index a5997f32..48be94da 100644 --- a/firmware/common/LPC4320_M4_memory.ld +++ b/firmware/common/LPC4320_M4_memory.ld @@ -26,7 +26,8 @@ MEMORY { /* rom is really the shadow region that points to SPI flash or elsewhere */ rom (rx) : ORIGIN = 0x00000000, LENGTH = 1M - ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 96K + ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 64K + ram_usb (rw) : ORIGIN = 0x10010000, LENGTH = 32K ram_local2 (rwx) : ORIGIN = 0x10080000, LENGTH = 32K ram_sleep (rwx) : ORIGIN = 0x10088000, LENGTH = 8K } diff --git a/firmware/common/LPC4330_M4_memory.ld b/firmware/common/LPC4330_M4_memory.ld index 1e886d3a..1d42ebee 100644 --- a/firmware/common/LPC4330_M4_memory.ld +++ b/firmware/common/LPC4330_M4_memory.ld @@ -26,7 +26,8 @@ MEMORY { /* rom is really the shadow region that points to SPI flash or elsewhere */ rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K - ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 128K + ram_local1 (rwx) : ORIGIN = 0x10000000, LENGTH = 96K + ram_usb(rw) : ORIGIN = 0x10018000, LENGTH = 32K ram_local2 (rwx) : ORIGIN = 0x10080000, LENGTH = 64K ram_sleep (rwx) : ORIGIN = 0x10090000, LENGTH = 8K } diff --git a/firmware/common/LPC43xx_M4_memory.ld b/firmware/common/LPC43xx_M4_memory.ld index d37542f9..fc7dcee7 100644 --- a/firmware/common/LPC43xx_M4_memory.ld +++ b/firmware/common/LPC43xx_M4_memory.ld @@ -41,5 +41,6 @@ MEMORY lz4_in_buf = ORIGIN(ram_lz4_in); lz4_out_buf = ORIGIN(ram_lz4_out); usb_samp_buffer = ORIGIN(ram_samp); +usb_bulk_buffer = ORIGIN(ram_usb); m0_state = ORIGIN(ram_shared); PROVIDE(__ram_m0_start__ = ORIGIN(ram_m0)); diff --git a/firmware/hackrf_usb/usb_buffer.h b/firmware/hackrf_usb/usb_buffer.h index 5aecdb67..07543025 100644 --- a/firmware/hackrf_usb/usb_buffer.h +++ b/firmware/hackrf_usb/usb_buffer.h @@ -28,8 +28,12 @@ #define USB_SAMP_BUFFER_SIZE 0x8000 #define USB_SAMP_BUFFER_MASK 0x7FFF -/* Address of usb_samp_buffer is set in ldscripts. If you change the name of this - * variable, it won't be where it needs to be in the processor's address space, - * unless you also adjust the ldscripts. +#define USB_BULK_BUFFER_SIZE 0x8000 +#define USB_BULK_BUFFER_MASK 0x7FFF + +/* Addresses of usb_samp_buffer and usb_bulk_buffer are set in ldscripts. If + * you change the name of these variables, they won't be where they need to + * be in the processor's address space, unless you also adjust the ldscripts. */ extern uint8_t usb_samp_buffer[USB_SAMP_BUFFER_SIZE]; +extern uint8_t usb_bulk_buffer[USB_BULK_BUFFER_SIZE]; From 40a8dc53dc9f0f7f858c911eac47186f16127ce8 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 19 Oct 2025 22:39:23 +0100 Subject: [PATCH 072/161] Use both buffers, with memcpy in place of DMA for now. --- firmware/hackrf_usb/usb_api_transceiver.c | 179 +++++++++++++++++----- 1 file changed, 142 insertions(+), 37 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index f272dac5..148741eb 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -45,6 +46,11 @@ #include "usb_endpoint.h" #define USB_TRANSFER_SIZE 0x4000 +#define DMA_TRANSFER_SIZE 0x4000 + +#define BUF_HALF_MASK (USB_SAMP_BUFFER_SIZE >> 1) + +volatile uint32_t dma_started, dma_pending, usb_started, usb_completed; typedef struct { uint32_t freq_mhz; @@ -68,6 +74,8 @@ typedef struct { set_sample_r_params_t set_sample_r_params; +void dma_isr(void); + usb_request_status_t usb_vendor_request_set_baseband_filter_bandwidth( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage) @@ -340,6 +348,11 @@ void transceiver_shutdown(void) void transceiver_startup(const transceiver_mode_t mode) { + dma_started = 0; + dma_pending = 0; + usb_started = 0; + usb_completed = 0; + radio_switch_opmode(&radio, mode); switch (mode) { @@ -424,30 +437,115 @@ usb_request_status_t usb_vendor_request_set_rx_overrun_limit( return USB_REQUEST_STATUS_OK; } +void transceiver_start_dma(void* src, void* dest, size_t size) +{ + dma_pending = size; + memcpy(dest, src, size); + dma_isr(); +} + +void dma_isr(void) +{ + m0_state.m4_count += dma_pending; + dma_pending = 0; +} + void transceiver_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) { (void) user_data; - m0_state.m4_count += bytes_transferred; + usb_completed += bytes_transferred; +} + +typedef enum { + DIRECTION_RX, + DIRECTION_TX, +} direction_t; + +void start_dma_if_possible(direction_t direction, size_t size) +{ + if (dma_pending) { + return; + } + + uint32_t sampling_completed = m0_state.m0_count; + uint32_t dma_completed = m0_state.m4_count; + uint32_t samp_offset = dma_started & USB_SAMP_BUFFER_MASK; + uint32_t bulk_offset = dma_started & USB_BULK_BUFFER_MASK; + uint32_t data_available, space_in_use, space_available, samp_buf_margin; + uint8_t *dest, *src; + + if (direction == DIRECTION_RX) { + data_available = sampling_completed - dma_started; + space_in_use = usb_completed - dma_completed; + space_available = USB_BULK_BUFFER_SIZE - space_in_use; + samp_buf_margin = USB_SAMP_BUFFER_SIZE - data_available; + src = &usb_samp_buffer[samp_offset]; + dest = &usb_bulk_buffer[bulk_offset]; + } else { + data_available = usb_completed - dma_started; + space_in_use = dma_completed - sampling_completed; + space_available = USB_SAMP_BUFFER_SIZE - space_in_use; + samp_buf_margin = space_in_use; + src = &usb_bulk_buffer[bulk_offset]; + dest = &usb_samp_buffer[samp_offset]; + } + + if (data_available < size || size > space_available) { + return; + } + + uint32_t m0_buf_half = sampling_completed & BUF_HALF_MASK; + uint32_t dma_buf_half = dma_started & BUF_HALF_MASK; + bool same_buf_half = m0_buf_half == dma_buf_half; + + if (same_buf_half && samp_buf_margin >= (USB_SAMP_BUFFER_SIZE / 2)) { + return; + } + + transceiver_start_dma(src, dest, size); + + dma_started += size; +} + +void start_usb_if_possible(direction_t direction) +{ + uint32_t bulk_offset = usb_started & USB_BULK_BUFFER_MASK; + uint32_t dma_completed = m0_state.m4_count; + uint32_t bytes_available; + usb_endpoint_t* usb_endpoint; + + if (direction == DIRECTION_RX) { + bytes_available = dma_completed - usb_started; + usb_endpoint = &usb_endpoint_bulk_in; + } else { + uint32_t space_used = usb_started - dma_completed; + bytes_available = USB_BULK_BUFFER_SIZE - space_used; + usb_endpoint = &usb_endpoint_bulk_out; + } + + if (bytes_available < USB_TRANSFER_SIZE) { + return; + } + + usb_transfer_schedule_block( + usb_endpoint, + &usb_bulk_buffer[bulk_offset], + USB_TRANSFER_SIZE, + transceiver_bulk_transfer_complete, + NULL); + + usb_started += USB_TRANSFER_SIZE; } void rx_mode(uint32_t seq) { - uint32_t usb_count = 0; - transceiver_startup(TRANSCEIVER_MODE_RX); baseband_streaming_enable(&sgpio_config); while (transceiver_request.seq == seq) { - if ((m0_state.m0_count - usb_count) >= USB_TRANSFER_SIZE) { - usb_transfer_schedule_block( - &usb_endpoint_bulk_in, - &usb_samp_buffer[usb_count & USB_SAMP_BUFFER_MASK], - USB_TRANSFER_SIZE, - transceiver_bulk_transfer_complete, - NULL); - usb_count += USB_TRANSFER_SIZE; - } + start_dma_if_possible(DIRECTION_RX, DMA_TRANSFER_SIZE); + start_usb_if_possible(DIRECTION_RX); radio_update(&radio); } @@ -456,35 +554,42 @@ void rx_mode(uint32_t seq) void tx_mode(uint32_t seq) { - unsigned int usb_count = 0; - bool started = false; - transceiver_startup(TRANSCEIVER_MODE_TX); - // Set up OUT transfer of buffer 0. - usb_transfer_schedule_block( - &usb_endpoint_bulk_out, - &usb_samp_buffer[0x0000], - USB_TRANSFER_SIZE, - transceiver_bulk_transfer_complete, - NULL); - usb_count += USB_TRANSFER_SIZE; + // First, make transfers directly into the sample buffer to fill it. + for (int i = 0; i < (USB_SAMP_BUFFER_SIZE / USB_TRANSFER_SIZE); i++) { + // Set up transfer. + usb_transfer_schedule_block( + &usb_endpoint_bulk_out, + &usb_samp_buffer[usb_started], + USB_TRANSFER_SIZE, + transceiver_bulk_transfer_complete, + NULL); + usb_started += USB_TRANSFER_SIZE; + // Wait for the transfer to complete. + while (usb_completed < usb_started) { + // Handle the host switching modes before filling the buffer. + if (transceiver_request.seq != seq) { + transceiver_shutdown(); + return; + } + + radio_update(&radio); + } + } + + // Sample buffer is now full. Update DMA counters accordingly. + dma_started = USB_SAMP_BUFFER_SIZE; + m0_state.m4_count = USB_SAMP_BUFFER_SIZE; + + // Start transmitting samples. + baseband_streaming_enable(&sgpio_config); + + // Continue feeding samples to the sample buffer. while (transceiver_request.seq == seq) { - if (!started && (m0_state.m4_count == USB_SAMP_BUFFER_SIZE)) { - // Buffer is now full, start streaming. - baseband_streaming_enable(&sgpio_config); - started = true; - } - if ((usb_count - m0_state.m0_count) <= USB_TRANSFER_SIZE) { - usb_transfer_schedule_block( - &usb_endpoint_bulk_out, - &usb_samp_buffer[usb_count & USB_SAMP_BUFFER_MASK], - USB_TRANSFER_SIZE, - transceiver_bulk_transfer_complete, - NULL); - usb_count += USB_TRANSFER_SIZE; - } + start_dma_if_possible(DIRECTION_TX, DMA_TRANSFER_SIZE); + start_usb_if_possible(DIRECTION_TX); radio_update(&radio); } From 0fb7b1c8c0ecb7854fa2d8a5b45bda05b8a84040 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 19 Oct 2025 19:40:28 +0100 Subject: [PATCH 073/161] Reduce memory transfer size to 8KB. --- firmware/hackrf_usb/usb_api_transceiver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 148741eb..32efbc71 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -46,7 +46,7 @@ #include "usb_endpoint.h" #define USB_TRANSFER_SIZE 0x4000 -#define DMA_TRANSFER_SIZE 0x4000 +#define DMA_TRANSFER_SIZE 0x2000 #define BUF_HALF_MASK (USB_SAMP_BUFFER_SIZE >> 1) From cebf0c7ce1ef4d2c1699408fc7723d883f2886e1 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 1 Dec 2025 23:24:34 +0000 Subject: [PATCH 074/161] Flush both buffers before leaving TX mode. --- firmware/common/usb.h | 2 + firmware/hackrf_usb/usb_api_transceiver.c | 58 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/firmware/common/usb.h b/firmware/common/usb.h index 9445728a..2b8c6c8c 100644 --- a/firmware/common/usb.h +++ b/firmware/common/usb.h @@ -31,6 +31,8 @@ #include "usb_type.h" +usb_queue_head_t* usb_queue_head(const uint_fast8_t endpoint_address); + void usb_peripheral_reset(void); void usb_phy_enable(void); diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 32efbc71..2112fe07 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -27,6 +27,8 @@ #include #include +#include + #include #include #include @@ -593,6 +595,62 @@ void tx_mode(uint32_t seq) radio_update(&radio); } + // Host has now requested to stop TX. We won't initiate any further USB + // transfers into the bulk buffer. However, we should make sure all + // data currently in the USB bulk buffer reaches the sample buffer. + + if ((usb_started - usb_completed) > 0) { + // We were part way through a 16KB firmware-side transfer when + // the transceiver mode change request to stop TX was received. + // + // We want to include the contents of that partial transfer in + // the data we move to the sample buffer. + // + // The transfer was already stopped by usb_endpoint_flush(), + // which was called from request_transceiver_mode(). + // + // We will not have had a callback, and the transfer descriptor + // (dTD) will not have been updated, since the transfer did not + // complete. + // + // However, as long as we haven't started a new transfer, we + // can retrieve the partial byte count from the transfer + // overlay in the endpoint queue head (dQH) (UM10503 25.9.1). + + usb_queue_head_t* const qh = + usb_queue_head(usb_endpoint_bulk_out.address); + unsigned int bytes_remaining = + (qh->total_bytes & USB_TD_DTD_TOKEN_TOTAL_BYTES_MASK) >> + USB_TD_DTD_TOKEN_TOTAL_BYTES_SHIFT; + unsigned int bytes_transferred = USB_TRANSFER_SIZE - bytes_remaining; + usb_completed += bytes_transferred; + } + + // Feed the remaining data from the bulk buffer to the sample buffer. + // At this point, we also need to handle the case where there is less data + // to be transferred to the sample buffer than a full-sized DMA transfer. + + // Any remainder of less than 4 bytes will be ignored; this is the chunk + // size of our DMA transfers. + while ((usb_completed - m0_state.m4_count) >= 4) { + uint32_t data_available = usb_completed - dma_started; + if (data_available > DMA_TRANSFER_SIZE) { + start_dma_if_possible(DIRECTION_TX, DMA_TRANSFER_SIZE); + } else { + start_dma_if_possible(DIRECTION_TX, data_available); + } + radio_update(&radio); + } + + // Wait for the data in the sample buffer to be transmitted. + + // Any remainder of less than 32 bytes will be ignored; this is + // the chunk size used by the M0 core to transfer samples to SGPIO. + while ((m0_state.m4_count - m0_state.m0_count) >= 32) { + radio_update(&radio); + } + + // All data received from the host has now been transmitted. transceiver_shutdown(); } From ebe5a5acdffbe8e2ec8a3a273bf4b7e87317ee25 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 8 Dec 2025 16:05:09 +0000 Subject: [PATCH 075/161] Read USB API version in hackrf_open() and cache it. --- host/libhackrf/src/hackrf.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 48a25868..14c34ab2 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -143,6 +143,7 @@ typedef enum { struct hackrf_device { libusb_device_handle* usb_device; + uint16_t usb_api_version; struct libusb_transfer** transfers; hackrf_sample_block_cb_fn callback; volatile bool @@ -187,10 +188,8 @@ static const max2837_ft_t max2837_ft[] = { {28000000}, {0}}; -#define USB_API_REQUIRED(device, version) \ - uint16_t usb_version = 0; \ - hackrf_usb_api_version_read(device, &usb_version); \ - if (usb_version < version) \ +#define USB_API_REQUIRED(device, version) \ + if (device->usb_api_version < version) \ return HACKRF_ERROR_USB_API_VERSION; static const uint16_t hackrf_usb_vid = 0x1d50; @@ -713,6 +712,13 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d { int result; hackrf_device* lib_device; + struct libusb_device_descriptor device_descriptor; + libusb_device* dev = libusb_get_device(usb_device); + result = libusb_get_device_descriptor(dev, &device_descriptor); + if (result < 0) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } //int speed = libusb_get_device_speed(usb_device); // TODO: Error or warning if not high speed USB? @@ -746,6 +752,7 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d #endif lib_device->usb_device = usb_device; + lib_device->usb_api_version = device_descriptor.bcdDevice; lib_device->transfers = NULL; lib_device->callback = NULL; lib_device->transfer_thread_started = false; @@ -1700,17 +1707,7 @@ extern ADDAPI int ADDCALL hackrf_usb_api_version_read( hackrf_device* device, uint16_t* version) { - int result; - libusb_device* dev; - struct libusb_device_descriptor desc; - dev = libusb_get_device(device->usb_device); - result = libusb_get_device_descriptor(dev, &desc); - if (result < 0) { - last_libusb_error = result; - return HACKRF_ERROR_LIBUSB; - } - - *version = desc.bcdDevice; + *version = device->usb_api_version; return HACKRF_SUCCESS; } From 3517d0adfc3bdb34f806a997a862d2d75e3508ff Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 9 Dec 2025 16:32:14 +0000 Subject: [PATCH 076/161] NAK the request that takes us out of TX mode until buffer flushed. --- firmware/hackrf_usb/usb_api_transceiver.c | 14 ++++++++++++++ firmware/hackrf_usb/usb_api_transceiver.h | 2 ++ firmware/hackrf_usb/usb_endpoint.c | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 2112fe07..cad75cd3 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -324,6 +324,17 @@ volatile transceiver_request_t transceiver_request = { .seq = 0, }; +void transceiver_usb_setup_complete(usb_endpoint_t* const endpoint) +{ + if (transceiver_request.mode == TRANSCEIVER_MODE_TX && + endpoint->setup.request == 1) { + // This is a request to leave TX mode. Do so but NAK for now. + request_transceiver_mode(endpoint->setup.value); + } else { + usb_setup_complete(endpoint); + } +} + // Must be called from an atomic context (normally USB ISR) void request_transceiver_mode(transceiver_mode_t mode) { @@ -651,6 +662,9 @@ void tx_mode(uint32_t seq) } // All data received from the host has now been transmitted. + // Now we can ACK the control request that took us out of TX mode. + usb_transfer_schedule_ack(usb_endpoint_control_in.in); + transceiver_shutdown(); } diff --git a/firmware/hackrf_usb/usb_api_transceiver.h b/firmware/hackrf_usb/usb_api_transceiver.h index df981c1e..383be18c 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.h +++ b/firmware/hackrf_usb/usb_api_transceiver.h @@ -36,6 +36,8 @@ typedef struct { extern volatile transceiver_request_t transceiver_request; +void transceiver_usb_setup_complete(usb_endpoint_t* const endpoint); + usb_request_status_t usb_vendor_request_set_transceiver_mode( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); diff --git a/firmware/hackrf_usb/usb_endpoint.c b/firmware/hackrf_usb/usb_endpoint.c index a8a35f0d..81251f1e 100644 --- a/firmware/hackrf_usb/usb_endpoint.c +++ b/firmware/hackrf_usb/usb_endpoint.c @@ -28,13 +28,14 @@ #include #include "usb_device.h" +#include "usb_api_transceiver.h" usb_endpoint_t usb_endpoint_control_out = { .address = 0x00, .device = &usb_device, .in = &usb_endpoint_control_in, .out = &usb_endpoint_control_out, - .setup_complete = usb_setup_complete, + .setup_complete = transceiver_usb_setup_complete, .transfer_complete = usb_control_out_complete, }; USB_DEFINE_QUEUE(usb_endpoint_control_out, 4); From 9584416b49ecd5e3b792288d2ffd5a64f7cfc60f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 18 Dec 2025 07:21:58 +0000 Subject: [PATCH 077/161] Add vendor request to retrieve buffer size. --- firmware/hackrf_usb/hackrf_usb.c | 1 + firmware/hackrf_usb/usb_api_transceiver.c | 22 ++++++++++++++++++++++ firmware/hackrf_usb/usb_api_transceiver.h | 3 +++ firmware/hackrf_usb/usb_descriptor.c | 2 +- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 54431ff3..cabf0d11 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -183,6 +183,7 @@ static usb_request_handler_fn vendor_request_handler[] = { usb_vendor_request_test_rtc_osc, usb_vendor_request_write_radio_reg, usb_vendor_request_read_radio_reg, + usb_vendor_request_get_buffer_size, }; static const uint32_t vendor_request_handler_count = diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index cad75cd3..1aca654e 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -450,6 +450,28 @@ usb_request_status_t usb_vendor_request_set_rx_overrun_limit( return USB_REQUEST_STATUS_OK; } +usb_request_status_t usb_vendor_request_get_buffer_size( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage) +{ + if (stage == USB_TRANSFER_STAGE_SETUP) { + uint32_t value = USB_SAMP_BUFFER_SIZE + USB_BULK_BUFFER_SIZE; + endpoint->buffer[0] = value & 0xff; + endpoint->buffer[1] = (value & 0xff00) >> 8; + endpoint->buffer[2] = (value & 0xff0000) >> 16; + endpoint->buffer[3] = (value & 0xff000000) >> 24; + usb_transfer_schedule_block( + endpoint->in, + &endpoint->buffer, + 4, + NULL, + NULL); + usb_transfer_schedule_ack(endpoint->out); + return USB_REQUEST_STATUS_OK; + } + return USB_REQUEST_STATUS_OK; +} + void transceiver_start_dma(void* src, void* dest, size_t size) { dma_pending = size; diff --git a/firmware/hackrf_usb/usb_api_transceiver.h b/firmware/hackrf_usb/usb_api_transceiver.h index 383be18c..a29dec11 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.h +++ b/firmware/hackrf_usb/usb_api_transceiver.h @@ -77,6 +77,9 @@ usb_request_status_t usb_vendor_request_set_tx_underrun_limit( usb_request_status_t usb_vendor_request_set_rx_overrun_limit( usb_endpoint_t* const endpoint, const usb_transfer_stage_t stage); +usb_request_status_t usb_vendor_request_get_buffer_size( + usb_endpoint_t* const endpoint, + const usb_transfer_stage_t stage); void request_transceiver_mode(transceiver_mode_t mode); void transceiver_startup(transceiver_mode_t mode); diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index 36531ebc..067317b3 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -30,7 +30,7 @@ #define USB_VENDOR_ID (0x1D50) -#define USB_API_VERSION (0x0111) +#define USB_API_VERSION (0x0112) #define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF) From 90625818adc367759a94e90a3007beecd91e64aa Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 18 Dec 2025 07:36:58 +0000 Subject: [PATCH 078/161] For API >= 1.12, get buffer size when opening device. --- host/libhackrf/src/hackrf.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 14c34ab2..da3c0f15 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -115,6 +115,7 @@ typedef enum { HACKRF_VENDOR_REQUEST_TEST_RTC_OSC = 58, HACKRF_VENDOR_REQUEST_RADIO_WRITE_REG = 59, HACKRF_VENDOR_REQUEST_RADIO_READ_REG = 60, + HACKRF_VENDOR_REQUEST_GET_BUFFER_SIZE = 61, } hackrf_vendor_request; #define USB_CONFIG_STANDARD 0x1 @@ -138,7 +139,6 @@ typedef enum { #define TRANSFER_COUNT 4 #define TRANSFER_BUFFER_SIZE 262144 -#define DEVICE_BUFFER_SIZE 32768 #define USB_MAX_SERIAL_LENGTH 32 struct hackrf_device { @@ -163,6 +163,7 @@ struct hackrf_device { hackrf_flush_cb_fn flush_callback; hackrf_tx_block_complete_cb_fn tx_completion_callback; void* flush_ctx; + uint32_t buffer_size; }; typedef struct { @@ -712,6 +713,7 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d { int result; hackrf_device* lib_device; + uint32_t buffer_size; struct libusb_device_descriptor device_descriptor; libusb_device* dev = libusb_get_device(usb_device); result = libusb_get_device_descriptor(dev, &device_descriptor); @@ -765,6 +767,30 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d lib_device->flush_ctx = NULL; lib_device->tx_completion_callback = NULL; + if (lib_device->usb_api_version >= 0x0112) { + // Fetch buffer size from device so we know how many bytes to flush TX with. + result = libusb_control_transfer( + lib_device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_DEVICE, + HACKRF_VENDOR_REQUEST_GET_BUFFER_SIZE, + 0, + 0, + (unsigned char*) &buffer_size, + 4, + 0); + + if (result < 4) { + last_libusb_error = result; + return HACKRF_ERROR_LIBUSB; + } + + lib_device->buffer_size = FROM_LE32(buffer_size); + } else { + // All older firmware uses a fixed 32KB buffer size. + lib_device->buffer_size = 32768; + } + result = pthread_mutex_init(&lib_device->transfer_lock, NULL); if (result != 0) { free(lib_device); @@ -2371,8 +2397,8 @@ ADDAPI int ADDCALL hackrf_enable_tx_flush( device->flush_transfer, device->usb_device, TX_ENDPOINT_ADDRESS, - calloc(1, DEVICE_BUFFER_SIZE), - DEVICE_BUFFER_SIZE, + calloc(1, device->buffer_size), + device->buffer_size, hackrf_libusb_flush_callback, device, 0); From 5afdd6dfef9f7d83885e09750d3a46acab0e9e8a Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 18 Dec 2025 07:49:31 +0000 Subject: [PATCH 079/161] If the host knows our buffer size, don't auto-flush. --- firmware/hackrf_usb/usb_api_transceiver.c | 26 +++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index 1aca654e..ca9445d1 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -52,6 +52,11 @@ #define BUF_HALF_MASK (USB_SAMP_BUFFER_SIZE >> 1) +// Unless we know the host knows our buffer size, we'll avoid leaving TX +// until we've transmitted all bytes sent by the host. This flag is cleared +// when the host requests our buffer size. +bool auto_tx_flush = true; + volatile uint32_t dma_started, dma_pending, usb_started, usb_completed; typedef struct { @@ -327,7 +332,7 @@ volatile transceiver_request_t transceiver_request = { void transceiver_usb_setup_complete(usb_endpoint_t* const endpoint) { if (transceiver_request.mode == TRANSCEIVER_MODE_TX && - endpoint->setup.request == 1) { + endpoint->setup.request == 1 && auto_tx_flush) { // This is a request to leave TX mode. Do so but NAK for now. request_transceiver_mode(endpoint->setup.value); } else { @@ -467,6 +472,11 @@ usb_request_status_t usb_vendor_request_get_buffer_size( NULL, NULL); usb_transfer_schedule_ack(endpoint->out); + + // We now know the host is aware of our buffer size, so it + // can make its own decisions about flushing the buffer. + auto_tx_flush = false; + return USB_REQUEST_STATUS_OK; } return USB_REQUEST_STATUS_OK; @@ -628,9 +638,17 @@ void tx_mode(uint32_t seq) radio_update(&radio); } - // Host has now requested to stop TX. We won't initiate any further USB - // transfers into the bulk buffer. However, we should make sure all - // data currently in the USB bulk buffer reaches the sample buffer. + // Host has now requested to stop TX. If we're not auto-flushing, we + // should now stop TX immediately. + + if (!auto_tx_flush) { + transceiver_shutdown(); + return; + } + + // Otherwise, we should now ensure all bytes sent by the host are + // transmitted before we leave TX. First, we should make sure all data + // currently in the USB bulk buffer reaches the sample buffer. if ((usb_started - usb_completed) > 0) { // We were part way through a 16KB firmware-side transfer when From 03119000a4a8543f9f39f5727cad42ce32df6a61 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 19 Oct 2025 16:41:34 +0100 Subject: [PATCH 080/161] Use DMA for transfers. --- firmware/hackrf_usb/usb_api_transceiver.c | 64 ++++++++++++++++++++--- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index ca9445d1..fe2b5405 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -25,13 +25,15 @@ #include #include -#include +#include +#include #include #include #include #include +#include #include #include #include @@ -81,7 +83,7 @@ typedef struct { set_sample_r_params_t set_sample_r_params; -void dma_isr(void); +void transceiver_dma_setup(void); usb_request_status_t usb_vendor_request_set_baseband_filter_bandwidth( usb_endpoint_t* const endpoint, @@ -371,6 +373,8 @@ void transceiver_startup(const transceiver_mode_t mode) usb_started = 0; usb_completed = 0; + transceiver_dma_setup(); + radio_switch_opmode(&radio, mode); switch (mode) { @@ -482,15 +486,63 @@ usb_request_status_t usb_vendor_request_get_buffer_size( return USB_REQUEST_STATUS_OK; } -void transceiver_start_dma(void* src, void* dest, size_t size) +/* clang-format off */ + +// Which GPDMA channel to use. +const uint32_t DMA_CHANNEL = 1; + +// GPDMA CCONFIG register setting. +const uint32_t DMA_CONFIG = + GPDMA_CCONFIG_FLOWCNTRL(0) // memory-to-memory + | GPDMA_CCONFIG_IE(0) // no error interrupt + | GPDMA_CCONFIG_ITC(1) // terminal count interrupt + | GPDMA_CCONFIG_L(0) // do not lock + | GPDMA_CCONFIG_H(0); // do not halt + +// GPDMA CCONTROL register setting (excluding TRANSFERSIZE field). +const uint32_t DMA_CONTROL = + GPDMA_CCONTROL_SBSIZE(7) // 256-transfer src bursts + | GPDMA_CCONTROL_DBSIZE(7) // 256-transfer dst bursts + | GPDMA_CCONTROL_SWIDTH(2) // 32-bit src transfers + | GPDMA_CCONTROL_DWIDTH(2) // 32-bit dst transfers + | GPDMA_CCONTROL_S(0) // AHB Master 0 + | GPDMA_CCONTROL_D(1) // AHB Master 1 + | GPDMA_CCONTROL_SI(1) // increment source + | GPDMA_CCONTROL_DI(1) // increment destination + | GPDMA_CCONTROL_PROT1(0) // user mode + | GPDMA_CCONTROL_PROT2(0) // not bufferable + | GPDMA_CCONTROL_PROT3(0) // not cacheable + | GPDMA_CCONTROL_I(1); // interrupt enabled + +/* clang-format on */ + +// Called before any sequence of DMA transfers. +void transceiver_dma_setup(void) { - dma_pending = size; - memcpy(dest, src, size); - dma_isr(); + gpdma_controller_enable(); + GPDMA_CCONFIG(DMA_CHANNEL) = DMA_CONFIG; + GPDMA_CCONTROL(DMA_CHANNEL) = DMA_CONTROL; + GPDMA_CLLI(DMA_CHANNEL) = 0; + GPDMA_INTTCCLEAR = (1 << DMA_CHANNEL); + nvic_enable_irq(NVIC_DMA_IRQ); } +// Called to start each DMA transfer. +void transceiver_start_dma(void* src, void* dest, size_t size) +{ + uint32_t num_transfers = size >> 2; + GPDMA_CCONTROL(DMA_CHANNEL) = DMA_CONTROL | num_transfers; + GPDMA_CSRCADDR(DMA_CHANNEL) = (uint32_t) src; + GPDMA_CDESTADDR(DMA_CHANNEL) = (uint32_t) dest; + dma_pending = size; + gpdma_channel_enable(DMA_CHANNEL); +} + +// Called when a DMA transfer completes. void dma_isr(void) { + gpdma_channel_disable(DMA_CHANNEL); + GPDMA_INTTCCLEAR = (1 << DMA_CHANNEL); m0_state.m4_count += dma_pending; dma_pending = 0; } From f1ece3769c30de6ef319ab640e1b0eaecd4c2208 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 09:48:52 +0200 Subject: [PATCH 081/161] firmware: move radio_t object instance to hackrf_usb binary --- firmware/common/drivers.c | 5 ----- firmware/common/drivers.h | 2 -- firmware/common/radio.h | 8 ++++++++ firmware/hackrf_usb/hackrf_usb.c | 4 ++++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index a6bd536a..a9f00509 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -26,7 +26,6 @@ #include #include -#include "clock_gen.h" #include "i2c_lpc.h" #include "max283x.h" #include "max5864_target.h" @@ -146,10 +145,6 @@ fpga_driver_t fpga = { }; #endif -radio_t radio = { - .sample_rate_cb = sample_rate_set, -}; - rf_path_t rf_path = { .switchctrl = 0, }; diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 6aa45f87..b5fea0a5 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -32,7 +32,6 @@ extern "C" { #include "max5864.h" #include "mixer.h" #include "platform_detect.h" // IWYU pragma: keep -#include "radio.h" #include "rf_path.h" #include "sgpio.h" #include "si5351c.h" @@ -60,7 +59,6 @@ extern max5864_driver_t max5864; extern mixer_driver_t mixer; extern w25q80bv_driver_t spi_flash; extern sgpio_config_t sgpio_config; -extern radio_t radio; extern rf_path_t rf_path; extern jtag_gpio_t jtag_gpio_cpld; extern jtag_t jtag_cpld; diff --git a/firmware/common/radio.h b/firmware/common/radio.h index 7ab46c80..b785c281 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -260,3 +260,11 @@ bool radio_update(radio_t* const radio); * the request bank for the new mode. */ void radio_switch_opmode(radio_t* const radio, const transceiver_mode_t mode); + +/** + * Driver instance. + * + * This needs to be configured and provided by the application + * binary. e.g. firmware/hackrf_usb/hackrf_usb.c + */ +extern radio_t radio; diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 54431ff3..d02ea279 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -98,6 +98,10 @@ extern uint32_t __m0_end__; extern uint32_t __ram_m0_start__; extern uint32_t _etext_ram, _text_ram, _etext_rom; +radio_t radio = { + .sample_rate_cb = sample_rate_set, +}; + static usb_request_handler_fn vendor_request_handler[] = { NULL, usb_vendor_request_set_transceiver_mode, From 1af5055720d454518b44ab262cb51526a69e5d27 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 14:35:46 +0200 Subject: [PATCH 082/161] firmware: move spi_bus_ssp0, spi_bus_ssp1 driver instances to spi_bus.x --- firmware/common/drivers.c | 18 ------------------ firmware/common/spi_bus.c | 22 ++++++++++++++++++++++ firmware/common/spi_bus.h | 4 ++++ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index a9f00509..a5b568af 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -90,15 +90,6 @@ ssp_config_t ssp_config_max5864 = { .clock_prescale_rate = 2, }; -spi_bus_t spi_bus_ssp1 = { - .obj = (void*) SSP1_BASE, - .config = &ssp_config_max5864, - .start = spi_ssp_start, - .stop = spi_ssp_stop, - .transfer = spi_ssp_transfer, - .transfer_gather = spi_ssp_transfer_gather, -}; - max5864_driver_t max5864 = { .bus = &spi_bus_ssp1, .target_init = max5864_target_init, @@ -110,15 +101,6 @@ ssp_config_t ssp_config_w25q80bv = { .clock_prescale_rate = 2, }; -spi_bus_t spi_bus_ssp0 = { - .obj = (void*) SSP0_BASE, - .config = &ssp_config_w25q80bv, - .start = spi_ssp_start, - .stop = spi_ssp_stop, - .transfer = spi_ssp_transfer, - .transfer_gather = spi_ssp_transfer_gather, -}; - w25q80bv_driver_t spi_flash = { .bus = &spi_bus_ssp0, .target_init = w25q80bv_target_init, diff --git a/firmware/common/spi_bus.c b/firmware/common/spi_bus.c index a26b4bab..a5332699 100644 --- a/firmware/common/spi_bus.c +++ b/firmware/common/spi_bus.c @@ -22,8 +22,30 @@ #include "spi_bus.h" +#include + +#include "spi_ssp.h" + +/* Driver instances. */ +spi_bus_t spi_bus_ssp0 = { + .obj = (void*) SSP0_BASE, + .start = spi_ssp_start, + .stop = spi_ssp_stop, + .transfer = spi_ssp_transfer, + .transfer_gather = spi_ssp_transfer_gather, +}; + +spi_bus_t spi_bus_ssp1 = { + .obj = (void*) SSP1_BASE, + .start = spi_ssp_start, + .stop = spi_ssp_stop, + .transfer = spi_ssp_transfer, + .transfer_gather = spi_ssp_transfer_gather, +}; + void spi_bus_start(spi_bus_t* const bus, const void* const config) { + bus->config = config; bus->start(bus, config); } diff --git a/firmware/common/spi_bus.h b/firmware/common/spi_bus.h index 07d5787d..23b3107b 100644 --- a/firmware/common/spi_bus.h +++ b/firmware/common/spi_bus.h @@ -51,3 +51,7 @@ void spi_bus_transfer_gather( spi_bus_t* const bus, const spi_transfer_t* const transfers, const size_t count); + +/* Driver instances. */ +extern spi_bus_t spi_bus_ssp0; +extern spi_bus_t spi_bus_ssp1; From 858ffd268a4cf3a6c4941fdacb6a252599adbd5e Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 14:40:37 +0200 Subject: [PATCH 083/161] firmware: move i2c_bus0, i2c_bus1 driver instances to i2c_bus.x --- firmware/common/da7219.c | 1 - firmware/common/drivers.c | 16 +--------------- firmware/common/drivers.h | 2 -- firmware/common/i2c_bus.c | 19 +++++++++++++++++++ firmware/common/i2c_bus.h | 4 ++++ firmware/common/operacake.c | 1 - 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/firmware/common/da7219.c b/firmware/common/da7219.c index 5af022d2..7ae916cb 100644 --- a/firmware/common/da7219.c +++ b/firmware/common/da7219.c @@ -22,7 +22,6 @@ #include #include "da7219.h" -#include "drivers.h" #include "i2c_bus.h" #define DA7219_REG_CHIP_ID1 0x81 diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index a5b568af..9d42000b 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -23,9 +23,9 @@ #include -#include #include +#include "i2c_bus.h" #include "i2c_lpc.h" #include "max283x.h" #include "max5864_target.h" @@ -36,20 +36,6 @@ #include "ice40_spi.h" #endif -i2c_bus_t i2c0 = { - .obj = (void*) I2C0_BASE, - .start = i2c_lpc_start, - .stop = i2c_lpc_stop, - .transfer = i2c_lpc_transfer, -}; - -i2c_bus_t i2c1 = { - .obj = (void*) I2C1_BASE, - .start = i2c_lpc_start, - .stop = i2c_lpc_stop, - .transfer = i2c_lpc_transfer, -}; - // const i2c_lpc_config_t i2c_config_si5351c_slow_clock = { // .duty_cycle_count = 15, // }; diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index b5fea0a5..111c7103 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -26,7 +26,6 @@ extern "C" { #endif #include "cpld_jtag.h" -#include "i2c_bus.h" #include "i2c_lpc.h" #include "max283x.h" #include "max5864.h" @@ -62,7 +61,6 @@ extern sgpio_config_t sgpio_config; extern rf_path_t rf_path; extern jtag_gpio_t jtag_gpio_cpld; extern jtag_t jtag_cpld; -extern i2c_bus_t i2c0; void ssp1_set_mode_max283x(void); void ssp1_set_mode_max5864(void); diff --git a/firmware/common/i2c_bus.c b/firmware/common/i2c_bus.c index 6d8dcd25..c0d5b79d 100644 --- a/firmware/common/i2c_bus.c +++ b/firmware/common/i2c_bus.c @@ -22,6 +22,25 @@ #include "i2c_bus.h" +#include + +#include "i2c_lpc.h" + +/* Driver instances. */ +i2c_bus_t i2c0 = { + .obj = (void*) I2C0_BASE, + .start = i2c_lpc_start, + .stop = i2c_lpc_stop, + .transfer = i2c_lpc_transfer, +}; + +i2c_bus_t i2c1 = { + .obj = (void*) I2C1_BASE, + .start = i2c_lpc_start, + .stop = i2c_lpc_stop, + .transfer = i2c_lpc_transfer, +}; + void i2c_bus_start(i2c_bus_t* const bus, const void* const config) { bus->start(bus, config); diff --git a/firmware/common/i2c_bus.h b/firmware/common/i2c_bus.h index 7519ba14..fa1211c1 100644 --- a/firmware/common/i2c_bus.h +++ b/firmware/common/i2c_bus.h @@ -47,3 +47,7 @@ void i2c_bus_transfer( const size_t tx_count, uint8_t* const rx, const size_t rx_count); + +/* Driver instances. */ +extern i2c_bus_t i2c0; +extern i2c_bus_t i2c1; diff --git a/firmware/common/operacake.c b/firmware/common/operacake.c index 24ca6541..f50c4f7e 100644 --- a/firmware/common/operacake.c +++ b/firmware/common/operacake.c @@ -26,7 +26,6 @@ #include -#include "drivers.h" #include "gpio.h" #include "gpio_lpc.h" #include "i2c_bus.h" From 4f44917f685337cdccbe5fd6b930883c2ac503c2 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 14:48:24 +0200 Subject: [PATCH 084/161] firmware: move w25q80vc driver instance to w25q80bv.x --- firmware/common/drivers.c | 12 ------------ firmware/common/drivers.h | 3 --- firmware/common/pins.c | 1 + firmware/common/rom_iap.c | 1 - firmware/common/w25q80bv.c | 18 +++++++++++++++++- firmware/common/w25q80bv.h | 5 +++++ firmware/hackrf_usb/usb_api_spiflash.c | 1 - 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index 9d42000b..b83a1a80 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -30,7 +30,6 @@ #include "max283x.h" #include "max5864_target.h" #include "spi_bus.h" -#include "w25q80bv_target.h" #if defined(IS_PRALINE) #include "fpga.h" #include "ice40_spi.h" @@ -81,17 +80,6 @@ max5864_driver_t max5864 = { .target_init = max5864_target_init, }; -ssp_config_t ssp_config_w25q80bv = { - .data_bits = SSP_DATA_8BITS, - .serial_clock_rate = 2, - .clock_prescale_rate = 2, -}; - -w25q80bv_driver_t spi_flash = { - .bus = &spi_bus_ssp0, - .target_init = w25q80bv_target_init, -}; - sgpio_config_t sgpio_config = { .slice_mode_multislice = true, }; diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 111c7103..5d903e5b 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -35,7 +35,6 @@ extern "C" { #include "sgpio.h" #include "si5351c.h" #include "spi_ssp.h" -#include "w25q80bv.h" #if defined(IS_PRALINE) #include "fpga.h" #include "ice40_spi.h" @@ -46,7 +45,6 @@ extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; extern si5351c_driver_t clock_gen; extern ssp_config_t ssp_config_max283x; extern ssp_config_t ssp_config_max5864; -extern ssp_config_t ssp_config_w25q80bv; #if defined(IS_PRALINE) extern ssp_config_t ssp_config_ice40_fpga; @@ -56,7 +54,6 @@ extern fpga_driver_t fpga; extern max283x_driver_t max283x; extern max5864_driver_t max5864; extern mixer_driver_t mixer; -extern w25q80bv_driver_t spi_flash; extern sgpio_config_t sgpio_config; extern rf_path_t rf_path; extern jtag_gpio_t jtag_gpio_cpld; diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 6f8123cb..425d6d74 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -36,6 +36,7 @@ #include "power.h" #include "rf_path.h" #include "sgpio.h" +#include "w25q80bv.h" #if defined(IS_PRALINE) #include "clock_io.h" #endif diff --git a/firmware/common/rom_iap.c b/firmware/common/rom_iap.c index 09da0b7c..25dfa269 100644 --- a/firmware/common/rom_iap.c +++ b/firmware/common/rom_iap.c @@ -22,7 +22,6 @@ #include -#include "drivers.h" #include "rom_iap.h" #include "spi_bus.h" #include "w25q80bv.h" diff --git a/firmware/common/w25q80bv.c b/firmware/common/w25q80bv.c index f569bedf..11c6d5c0 100644 --- a/firmware/common/w25q80bv.c +++ b/firmware/common/w25q80bv.c @@ -27,11 +27,15 @@ * programming the flash. */ +#include "w25q80bv.h" + #include #include +#include + #include "platform_detect.h" -#include "w25q80bv.h" +#include "w25q80bv_target.h" #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -51,6 +55,18 @@ #define W25Q80BV_DEVICE_ID_RES 0x13 /* Expected device_id for W25Q80BV */ +/* Driver instance. */ +ssp_config_t ssp_config_w25q80bv = { + .data_bits = SSP_DATA_8BITS, + .serial_clock_rate = 2, + .clock_prescale_rate = 2, +}; + +w25q80bv_driver_t spi_flash = { + .bus = &spi_bus_ssp0, + .target_init = w25q80bv_target_init, +}; + /* * Set up pins for GPIO and SPI control, configure SSP0 peripheral for SPI. * SSP0_CS is controlled by GPIO in order to handle various transfer lengths. diff --git a/firmware/common/w25q80bv.h b/firmware/common/w25q80bv.h index a43d6445..21d0814b 100644 --- a/firmware/common/w25q80bv.h +++ b/firmware/common/w25q80bv.h @@ -28,6 +28,7 @@ #include "gpio.h" #include "spi_bus.h" +#include "spi_ssp.h" #define W25Q80BV_DEVICE_ID_RES 0x13 /* Expected device_id for W25Q80BV */ #define W25Q16DV_DEVICE_ID_RES 0x14 /* Expected device_id for W25Q16DV */ @@ -65,3 +66,7 @@ void w25q80bv_read( uint32_t len, uint8_t* const data); void w25q80bv_clear_status(w25q80bv_driver_t* const drv); + +/* Driver instance. */ +extern ssp_config_t ssp_config_w25q80bv; +extern w25q80bv_driver_t spi_flash; diff --git a/firmware/hackrf_usb/usb_api_spiflash.c b/firmware/hackrf_usb/usb_api_spiflash.c index 2c944a10..d490eb0e 100644 --- a/firmware/hackrf_usb/usb_api_spiflash.c +++ b/firmware/hackrf_usb/usb_api_spiflash.c @@ -26,7 +26,6 @@ #include #include -#include #include #include #include From e5c8dc235012cc44ee4e6eab813a267b43680c02 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 14:54:22 +0200 Subject: [PATCH 085/161] firmware: move max283x driver instance to max283x.x --- firmware/common/drivers.c | 20 -------------------- firmware/common/drivers.h | 4 ---- firmware/common/fpga.c | 1 + firmware/common/fpga_image.c | 1 + firmware/common/max283x.c | 20 +++++++++++++++++++- firmware/common/max283x.h | 6 ++++++ firmware/common/pins.c | 1 + firmware/common/ui_rad1o.c | 2 +- 8 files changed, 29 insertions(+), 26 deletions(-) diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index b83a1a80..38f41a9d 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -27,7 +27,6 @@ #include "i2c_bus.h" #include "i2c_lpc.h" -#include "max283x.h" #include "max5864_target.h" #include "spi_bus.h" #if defined(IS_PRALINE) @@ -48,20 +47,6 @@ si5351c_driver_t clock_gen = { .i2c_address = 0x60, }; -ssp_config_t ssp_config_max283x = { - /* FIXME speed up once everything is working reliably */ - /* - // Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz - const uint8_t serial_clock_rate = 32; - const uint8_t clock_prescale_rate = 128; - */ - // Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz - .serial_clock_rate = 21, - .clock_prescale_rate = 2, -}; - -max283x_driver_t max283x = {}; - ssp_config_t ssp_config_max5864 = { /* FIXME speed up once everything is working reliably */ /* @@ -111,11 +96,6 @@ jtag_t jtag_cpld = { .gpio = &jtag_gpio_cpld, }; -void ssp1_set_mode_max283x(void) -{ - spi_bus_start(&spi_bus_ssp1, &ssp_config_max283x); -} - void ssp1_set_mode_max5864(void) { spi_bus_start(max5864.bus, &ssp_config_max5864); diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 5d903e5b..d4792d75 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -27,7 +27,6 @@ extern "C" { #include "cpld_jtag.h" #include "i2c_lpc.h" -#include "max283x.h" #include "max5864.h" #include "mixer.h" #include "platform_detect.h" // IWYU pragma: keep @@ -43,7 +42,6 @@ extern "C" { /* TODO: Hide these configurations */ extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; extern si5351c_driver_t clock_gen; -extern ssp_config_t ssp_config_max283x; extern ssp_config_t ssp_config_max5864; #if defined(IS_PRALINE) @@ -51,7 +49,6 @@ extern ssp_config_t ssp_config_ice40_fpga; extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif -extern max283x_driver_t max283x; extern max5864_driver_t max5864; extern mixer_driver_t mixer; extern sgpio_config_t sgpio_config; @@ -59,7 +56,6 @@ extern rf_path_t rf_path; extern jtag_gpio_t jtag_gpio_cpld; extern jtag_t jtag_cpld; -void ssp1_set_mode_max283x(void); void ssp1_set_mode_max5864(void); #if defined(IS_PRALINE) void ssp1_set_mode_ice40(void); diff --git a/firmware/common/fpga.c b/firmware/common/fpga.c index 172810a8..3bdd636e 100644 --- a/firmware/common/fpga.c +++ b/firmware/common/fpga.c @@ -26,6 +26,7 @@ #include "drivers.h" #include "ice40_spi.h" +#include "max283x.h" /* Set up all registers according to the loaded bitstream's defaults. */ void fpga_init(fpga_driver_t* const drv) diff --git a/firmware/common/fpga_image.c b/firmware/common/fpga_image.c index 989d16b2..a9f46aee 100644 --- a/firmware/common/fpga_image.c +++ b/firmware/common/fpga_image.c @@ -27,6 +27,7 @@ #include "fpga.h" #include "ice40_spi.h" #include "lz4_blk.h" +#include "max283x.h" #include "selftest.h" struct fpga_image_read_ctx { diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index 3b49ab6e..e7221bb0 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -41,7 +41,25 @@ #include "max2839_target.h" #endif -extern spi_bus_t spi_bus_ssp1; +/* Driver instance. */ +ssp_config_t ssp_config_max283x = { + /* FIXME speed up once everything is working reliably */ + /* + // Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz + const uint8_t serial_clock_rate = 32; + const uint8_t clock_prescale_rate = 128; + */ + // Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz + .serial_clock_rate = 21, + .clock_prescale_rate = 2, +}; + +max283x_driver_t max283x = {}; + +void ssp1_set_mode_max283x(void) +{ + spi_bus_start(&spi_bus_ssp1, &ssp_config_max283x); +} #ifdef IS_PRALINE max2831_driver_t max2831 = { diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index 01bb40b0..b4e44620 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -28,6 +28,7 @@ #include "fixed_point.h" #include "platform_detect.h" +#include "spi_ssp.h" #ifdef IS_PRALINE #include "max2831.h" @@ -139,3 +140,8 @@ void max283x_set_rx_hpf_frequency( /* Perform MAX2831 TX and RX calibration. */ void max283x_tx_calibration(max283x_driver_t* const drv); void max283x_rx_calibration(max283x_driver_t* const drv); + +/* Driver instance. */ +extern ssp_config_t ssp_config_max283x; +extern max283x_driver_t max283x; +void ssp1_set_mode_max283x(void); diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 425d6d74..bced06d4 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -29,6 +29,7 @@ #include "drivers.h" #include "gpio.h" #include "leds.h" +#include "max283x.h" #include "mixer.h" #include "platform_detect.h" #include "platform_gpio.h" diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index 757e11f1..378798b0 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -27,7 +27,7 @@ #include #include "clock_gen.h" -#include "drivers.h" +#include "max283x.h" #include "rf_path.h" #include "transceiver_mode.h" #ifdef IS_NOT_RAD1O From ac93a68a028d7921aa6e37626f1b8d18be23ac80 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 30 Apr 2026 12:45:15 +0100 Subject: [PATCH 086/161] ui_rad1o: remove unnecessary include. --- firmware/common/ui_rad1o.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index 757e11f1..4f0a3636 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -30,9 +30,6 @@ #include "drivers.h" #include "rf_path.h" #include "transceiver_mode.h" -#ifdef IS_NOT_RAD1O - #include "radio.h" -#endif #include "rad1o/display.h" #include "rad1o/draw.h" From f655bec11f3dad1ebf659bf2cd2e798fbf1f5202 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 30 Apr 2026 14:01:36 +0100 Subject: [PATCH 087/161] ui_rad1o: use a helper function for printing frequencies. --- firmware/common/ui_rad1o.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index 4f0a3636..c1041fad 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -58,20 +58,21 @@ static bool enabled = false; #define WHITE 0b11111111 #define GREY 0b01001101 +static void print_hz(char* tmp, char* fmt, uint64_t hz) +{ + uint32_t mhz, khz; + mhz = hz / 1000000; + khz = (hz - mhz * 1000000) / 1000; + sprintf(tmp, fmt, (unsigned int) mhz, (unsigned int) khz); + rad1o_lcdPrint(tmp); +} + static void draw_frequency(void) { char tmp[100]; - uint32_t mhz; - uint32_t khz; - - mhz = freq / 1000000; - khz = (freq - mhz * 1000000) / 1000; - rad1o_setTextColor(BLACK, GREEN); rad1o_setIntFont(&Font_Ubuntu18pt); - sprintf(tmp, "%4u.%03u", (unsigned int) mhz, (unsigned int) khz); - rad1o_lcdPrint(tmp); - + print_hz(tmp, "%4u.%03u", freq); rad1o_setIntFont(&Font_7x8); rad1o_lcdMoveCrsr(1, 18 - 7); rad1o_lcdPrint("MHz"); @@ -112,8 +113,6 @@ static void draw_tx_rx(void) static void ui_update(void) { char tmp[100]; - uint32_t mhz; - uint32_t khz; if (!enabled) { return; @@ -154,18 +153,12 @@ static void ui_update(void) rad1o_setTextColor(BLACK, WHITE); rad1o_lcdSetCrsr(2, 71); rad1o_lcdPrint("Rate: "); - mhz = sample_rate / 1000000; - khz = (sample_rate - mhz * 1000000) / 1000; - sprintf(tmp, "%2u.%03u MHz", (unsigned int) mhz, (unsigned int) khz); - rad1o_lcdPrint(tmp); + print_hz(tmp, "%2u.%03u MHz", sample_rate); rad1o_lcdNl(); rad1o_lcdMoveCrsr(2, 0); rad1o_lcdPrint("Filter: "); - mhz = filter_bw / 1000000; - khz = (filter_bw - mhz * 1000000) / 1000; - sprintf(tmp, "%2u.%03u MHz", (unsigned int) mhz, (unsigned int) khz); - rad1o_lcdPrint(tmp); + print_hz(tmp, "%2u.%03u MHz", filter_bw); rad1o_lcdNl(); rad1o_drawHLine(88, 0, RESX - 1, WHITE); From 5d7b6c33cf199dc57a138b44e490b3caf2e5cc88 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 30 Apr 2026 14:02:33 +0100 Subject: [PATCH 088/161] ui_rad1o: Round displayed frequencies to nearest kHz. --- firmware/common/ui_rad1o.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index c1041fad..8df630bd 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -58,11 +58,14 @@ static bool enabled = false; #define WHITE 0b11111111 #define GREY 0b01001101 +#define DIV_ROUND_CLOSEST(n, d) ((n + (d / 2)) / d) + static void print_hz(char* tmp, char* fmt, uint64_t hz) { uint32_t mhz, khz; - mhz = hz / 1000000; - khz = (hz - mhz * 1000000) / 1000; + mhz = DIV_ROUND_CLOSEST(hz, 1000000); + hz -= mhz * 1000000; + khz = DIV_ROUND_CLOSEST(hz, 1000); sprintf(tmp, fmt, (unsigned int) mhz, (unsigned int) khz); rad1o_lcdPrint(tmp); } From 01a3b345422c2102721c2c8e3e9a42beb55a92d9 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 15:07:58 +0200 Subject: [PATCH 089/161] firmware: move si5351c driver instance 'clock_gen' to si5351c.x --- firmware/common/cpu_clock.c | 2 +- firmware/common/drivers.c | 11 ----------- firmware/common/drivers.h | 4 ---- firmware/common/si5351c.c | 13 +++++++++++++ firmware/common/si5351c.h | 5 +++++ 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/firmware/common/cpu_clock.c b/firmware/common/cpu_clock.c index b69f0773..5a4ab4f6 100644 --- a/firmware/common/cpu_clock.c +++ b/firmware/common/cpu_clock.c @@ -29,8 +29,8 @@ #endif #include "delay.h" -#include "drivers.h" #include "i2c_bus.h" +#include "si5351c.h" #ifdef IS_NOT_RAD1O #include "platform_detect.h" #endif diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index 38f41a9d..bd601a1c 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -25,8 +25,6 @@ #include -#include "i2c_bus.h" -#include "i2c_lpc.h" #include "max5864_target.h" #include "spi_bus.h" #if defined(IS_PRALINE) @@ -38,15 +36,6 @@ // .duty_cycle_count = 15, // }; -const i2c_lpc_config_t i2c_config_si5351c_fast_clock = { - .duty_cycle_count = 255, -}; - -si5351c_driver_t clock_gen = { - .bus = &i2c0, - .i2c_address = 0x60, -}; - ssp_config_t ssp_config_max5864 = { /* FIXME speed up once everything is working reliably */ /* diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index d4792d75..2bb8db8e 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -26,13 +26,11 @@ extern "C" { #endif #include "cpld_jtag.h" -#include "i2c_lpc.h" #include "max5864.h" #include "mixer.h" #include "platform_detect.h" // IWYU pragma: keep #include "rf_path.h" #include "sgpio.h" -#include "si5351c.h" #include "spi_ssp.h" #if defined(IS_PRALINE) #include "fpga.h" @@ -40,8 +38,6 @@ extern "C" { #endif /* TODO: Hide these configurations */ -extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; -extern si5351c_driver_t clock_gen; extern ssp_config_t ssp_config_max5864; #if defined(IS_PRALINE) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index c9767405..53b025d8 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -25,6 +25,7 @@ #include "clock_io.h" #include "delay.h" +#include "i2c_lpc.h" #include "platform_detect.h" #include "selftest.h" #include "si5351c.h" @@ -35,6 +36,18 @@ #include "platform_scu.h" #endif +/* Driver instance. */ +// const i2c_lpc_config_t i2c_config_si5351c_slow_clock = { +// .duty_cycle_count = 15, +// }; +const i2c_lpc_config_t i2c_config_si5351c_fast_clock = { + .duty_cycle_count = 255, +}; +si5351c_driver_t clock_gen = { + .bus = &i2c0, + .i2c_address = 0x60, +}; + static enum pll_sources active_clock_source = PLL_SOURCE_UNINITIALIZED; /* External clock output default is deactivated as it creates noise */ static bool clkout_enabled = false; diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index 9e6b9ab8..f7a7da90 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -31,6 +31,7 @@ extern "C" { #include #include "i2c_bus.h" +#include "i2c_lpc.h" #define SI_INTDIV(x) (x * 128 - 512) @@ -115,6 +116,10 @@ void si5351c_set_phase( const uint8_t ms_number, const uint8_t offset); +/* Driver Instance. */ +extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; +extern si5351c_driver_t clock_gen; + #ifdef __cplusplus } #endif From 76252016b6a813b7c4ad661d6a8820521965a4d8 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 15:19:15 +0200 Subject: [PATCH 090/161] firmware: rename si5351c driver instance 'clock_gen' to 'si5351c' --- firmware/common/clock_gen.c | 66 +++++++++++++------------- firmware/common/cpu_clock.c | 2 +- firmware/common/si5351c.c | 2 +- firmware/common/si5351c.h | 2 +- firmware/hackrf_usb/usb_api_register.c | 10 ++-- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index b62308ce..642fc61b 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -36,14 +36,14 @@ void clock_gen_init(void) { - i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); + i2c_bus_start(si5351c.bus, &i2c_config_si5351c_fast_clock); - si5351c_init(&clock_gen); - si5351c_disable_all_outputs(&clock_gen); - si5351c_disable_oeb_pin_control(&clock_gen); - si5351c_power_down_all_clocks(&clock_gen); - si5351c_set_crystal_configuration(&clock_gen); - si5351c_enable_xo_and_ms_fanout(&clock_gen); + si5351c_init(&si5351c); + si5351c_disable_all_outputs(&si5351c); + si5351c_disable_oeb_pin_control(&si5351c); + si5351c_power_down_all_clocks(&si5351c); + si5351c_set_crystal_configuration(&si5351c); + si5351c_enable_xo_and_ms_fanout(&si5351c); /* * Clocks on HackRF One r9: @@ -76,7 +76,7 @@ void clock_gen_init(void) if (IS_H1_R9) { /* MS0/CLK0 is the reference for both RFFC5071 and MAX2839. */ si5351c_configure_multisynth( - &clock_gen, + &si5351c, 0, 20 * 128 - 512, 0, @@ -88,7 +88,7 @@ void clock_gen_init(void) if (IS_NOT_H1_R9) { /* MS4/CLK4 is the source for the RFFC5071 mixer (MAX2837 on rad1o). */ si5351c_configure_multisynth( - &clock_gen, + &si5351c, 4, 20 * 128 - 512, 0, @@ -96,7 +96,7 @@ void clock_gen_init(void) 0); /* 800/20 = 40MHz */ /* MS5/CLK5 is the source for the MAX2837 clock input (MAX2871 on rad1o). */ si5351c_configure_multisynth( - &clock_gen, + &si5351c, 5, 20 * 128 - 512, 0, @@ -111,19 +111,19 @@ void clock_gen_init(void) /* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */ sample_rate_set(SR_FP_MHZ(10), true); - si5351c_configure_clock_control(&clock_gen); - si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL); + si5351c_configure_clock_control(&si5351c); + si5351c_set_clock_source(&si5351c, PLL_SOURCE_XTAL); // soft reset - si5351c_reset_pll(&clock_gen, SI5351C_PLL_BOTH); - si5351c_enable_clock_outputs(&clock_gen); + si5351c_reset_pll(&si5351c, SI5351C_PLL_BOTH); + si5351c_enable_clock_outputs(&si5351c); } void clock_gen_shutdown(void) { - i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); - si5351c_disable_all_outputs(&clock_gen); - si5351c_disable_oeb_pin_control(&clock_gen); - si5351c_power_down_all_clocks(&clock_gen); + i2c_bus_start(si5351c.bus, &i2c_config_si5351c_fast_clock); + si5351c_disable_all_outputs(&si5351c); + si5351c_disable_oeb_pin_control(&si5351c); + si5351c_power_down_all_clocks(&si5351c); } clock_source_t activate_best_clock_source(void) @@ -140,7 +140,7 @@ clock_source_t activate_best_clock_source(void) clock_source_t source = CLOCK_SOURCE_HACKRF; /* Check for external clock input. */ - if (si5351c_clkin_signal_valid(&clock_gen)) { + if (si5351c_clkin_signal_valid(&si5351c)) { source = CLOCK_SOURCE_EXTERNAL; } else { #ifdef IS_EXPANSION_COMPATIBLE @@ -149,7 +149,7 @@ clock_source_t activate_best_clock_source(void) if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(true); delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */ - if (si5351c_clkin_signal_valid(&clock_gen)) { + if (si5351c_clkin_signal_valid(&si5351c)) { source = CLOCK_SOURCE_PORTAPACK; } else { portapack_reference_oscillator(false); @@ -161,7 +161,7 @@ clock_source_t activate_best_clock_source(void) } si5351c_set_clock_source( - &clock_gen, + &si5351c, (source == CLOCK_SOURCE_HACKRF) ? PLL_SOURCE_XTAL : PLL_SOURCE_CLKIN); hackrf_ui()->set_clock_source(source); @@ -342,9 +342,9 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) if (IS_NOT_PRALINE) { /* Integer mode can be enabled if p1 is even and p2 is zero. */ if (p1 & 0x1 || p2) { - si5351c_set_int_mode(&clock_gen, 0, 0); + si5351c_set_int_mode(&si5351c, 0, 0); } else { - si5351c_set_int_mode(&clock_gen, 0, 1); + si5351c_set_int_mode(&si5351c, 0, 1); } #ifdef IS_H1_R9 @@ -353,7 +353,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) * On HackRF One r9 all sample clocks are externally derived * from MS1/CLK1 operating at twice the sample rate. */ - si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0); + si5351c_configure_multisynth(&si5351c, 1, p1, p2, p3, 0); } #endif #ifdef IS_NOT_H1_R9 @@ -363,11 +363,11 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) * different sample clocks, all derived from multisynth 0. */ /* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */ - si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); + si5351c_configure_multisynth(&si5351c, 0, p1, p2, p3, 1); /* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */ si5351c_configure_multisynth( - &clock_gen, + &si5351c, 1, 0, 0, @@ -376,7 +376,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) /* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */ si5351c_configure_multisynth( - &clock_gen, + &si5351c, 2, 0, 0, @@ -389,17 +389,17 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) #ifdef IS_PRALINE if (IS_PRALINE) { /* MS0/CLK0 is the source for the MAX5864 (AFE_CLK). */ - si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1); + si5351c_configure_multisynth(&si5351c, 0, p1, p2, p3, 1); /* MS1/CLK1 is the source for the FPGA (FPGA_CLK and SCT_CLK). */ - si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 1); + si5351c_configure_multisynth(&si5351c, 1, p1, p2, p3, 1); /* Delay FPGA_CLK relative to AFE_CLK. */ uint8_t phase_offset = 0; if (p1 < 2100) { phase_offset = (p1 >> 4) - 6; } - si5351c_set_phase(&clock_gen, 1, phase_offset); + si5351c_set_phase(&si5351c, 1, phase_offset); if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { /* @@ -407,12 +407,12 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) * CLK1. We configure both so that behavior is consistent with * newer boards that use CLK1 for both FPGA_CLK and SCT_CLK. */ - si5351c_configure_multisynth(&clock_gen, 2, p1, p2, p3, 1); - si5351c_set_phase(&clock_gen, 2, phase_offset); + si5351c_configure_multisynth(&si5351c, 2, p1, p2, p3, 1); + si5351c_set_phase(&si5351c, 2, phase_offset); } /* Reset PLL to synchronize output clock phase. */ - si5351c_reset_pll(&clock_gen, SI5351C_PLL_A); + si5351c_reset_pll(&si5351c, SI5351C_PLL_A); } #endif diff --git a/firmware/common/cpu_clock.c b/firmware/common/cpu_clock.c index 5a4ab4f6..aa4ac910 100644 --- a/firmware/common/cpu_clock.c +++ b/firmware/common/cpu_clock.c @@ -116,7 +116,7 @@ void cpu_clock_init(void) //FIXME disable I2C /* Kick I2C0 down to 400kHz when we switch over to APB1 clock = 204MHz */ - i2c_bus_start(clock_gen.bus, &i2c_config_si5351c_fast_clock); + i2c_bus_start(si5351c.bus, &i2c_config_si5351c_fast_clock); /* * 12MHz clock is entering LPC XTAL1/OSC input now. diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 53b025d8..a7b605f5 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -43,7 +43,7 @@ const i2c_lpc_config_t i2c_config_si5351c_fast_clock = { .duty_cycle_count = 255, }; -si5351c_driver_t clock_gen = { +si5351c_driver_t si5351c = { .bus = &i2c0, .i2c_address = 0x60, }; diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index f7a7da90..b38eac90 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -118,7 +118,7 @@ void si5351c_set_phase( /* Driver Instance. */ extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; -extern si5351c_driver_t clock_gen; +extern si5351c_driver_t si5351c; #ifdef __cplusplus } diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index 58c1684c..e88d0016 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -27,7 +27,6 @@ #include #include -#include #include #include #include @@ -40,6 +39,7 @@ #include #endif #ifdef IS_NOT_RAD1O + #include #include #include #endif @@ -101,7 +101,7 @@ usb_request_status_t usb_vendor_request_write_si5351c( if (endpoint->setup.index < 256) { if (endpoint->setup.value < 256) { si5351c_write_single( - &clock_gen, + &si5351c, endpoint->setup.index, endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -121,7 +121,7 @@ usb_request_status_t usb_vendor_request_read_si5351c( if (stage == USB_TRANSFER_STAGE_SETUP) { if (endpoint->setup.index < 256) { const uint8_t value = - si5351c_read_single(&clock_gen, endpoint->setup.index); + si5351c_read_single(&si5351c, endpoint->setup.index); endpoint->buffer[0] = value; usb_transfer_schedule_block( endpoint->in, @@ -211,7 +211,7 @@ usb_request_status_t usb_vendor_request_set_clkout_enable( const usb_transfer_stage_t stage) { if (stage == USB_TRANSFER_STAGE_SETUP) { - si5351c_clkout_enable(&clock_gen, endpoint->setup.value); + si5351c_clkout_enable(&si5351c, endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); } return USB_REQUEST_STATUS_OK; @@ -222,7 +222,7 @@ usb_request_status_t usb_vendor_request_get_clkin_status( const usb_transfer_stage_t stage) { if (stage == USB_TRANSFER_STAGE_SETUP) { - endpoint->buffer[0] = si5351c_clkin_signal_valid(&clock_gen); + endpoint->buffer[0] = si5351c_clkin_signal_valid(&si5351c); usb_transfer_schedule_block( endpoint->in, &endpoint->buffer, From 3efc8bdbb6a2ccee3bbccfb0f2f7443fd1b34b94 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 15:30:00 +0200 Subject: [PATCH 091/161] firmware: move max5864 driver instance to max5864.x --- firmware/common/drivers.c | 33 ++------------------------------- firmware/common/drivers.h | 8 +------- firmware/common/max5864.c | 29 +++++++++++++++++++++++++++++ firmware/common/max5864.h | 6 ++++++ firmware/common/pins.c | 1 + 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index bd601a1c..769013b0 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -23,37 +23,13 @@ #include -#include - -#include "max5864_target.h" -#include "spi_bus.h" #if defined(IS_PRALINE) + #include #include "fpga.h" #include "ice40_spi.h" + #include "spi_bus.h" #endif -// const i2c_lpc_config_t i2c_config_si5351c_slow_clock = { -// .duty_cycle_count = 15, -// }; - -ssp_config_t ssp_config_max5864 = { - /* FIXME speed up once everything is working reliably */ - /* - // Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz - const uint8_t serial_clock_rate = 32; - const uint8_t clock_prescale_rate = 128; - */ - // Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz - .data_bits = SSP_DATA_8BITS, - .serial_clock_rate = 21, - .clock_prescale_rate = 2, -}; - -max5864_driver_t max5864 = { - .bus = &spi_bus_ssp1, - .target_init = max5864_target_init, -}; - sgpio_config_t sgpio_config = { .slice_mode_multislice = true, }; @@ -85,11 +61,6 @@ jtag_t jtag_cpld = { .gpio = &jtag_gpio_cpld, }; -void ssp1_set_mode_max5864(void) -{ - spi_bus_start(max5864.bus, &ssp_config_max5864); -} - #ifdef IS_PRALINE void ssp1_set_mode_ice40(void) { diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 2bb8db8e..423e46a8 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -26,33 +26,27 @@ extern "C" { #endif #include "cpld_jtag.h" -#include "max5864.h" #include "mixer.h" #include "platform_detect.h" // IWYU pragma: keep #include "rf_path.h" #include "sgpio.h" -#include "spi_ssp.h" #if defined(IS_PRALINE) #include "fpga.h" #include "ice40_spi.h" + #include "spi_ssp.h" #endif -/* TODO: Hide these configurations */ -extern ssp_config_t ssp_config_max5864; - #if defined(IS_PRALINE) extern ssp_config_t ssp_config_ice40_fpga; extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif -extern max5864_driver_t max5864; extern mixer_driver_t mixer; extern sgpio_config_t sgpio_config; extern rf_path_t rf_path; extern jtag_gpio_t jtag_gpio_cpld; extern jtag_t jtag_cpld; -void ssp1_set_mode_max5864(void); #if defined(IS_PRALINE) void ssp1_set_mode_ice40(void); #endif diff --git a/firmware/common/max5864.c b/firmware/common/max5864.c index b91a1cd1..88d781b9 100644 --- a/firmware/common/max5864.c +++ b/firmware/common/max5864.c @@ -24,6 +24,35 @@ #include +#include + +#include "max5864_target.h" +#include "spi_bus.h" + +/* Driver instance. */ +ssp_config_t ssp_config_max5864 = { + /* FIXME speed up once everything is working reliably */ + /* + // Freq About 0.0498MHz / 49.8KHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz + const uint8_t serial_clock_rate = 32; + const uint8_t clock_prescale_rate = 128; + */ + // Freq About 4.857MHz => Freq = PCLK / (CPSDVSR * [SCR+1]) with PCLK=PLL1=204MHz + .data_bits = SSP_DATA_8BITS, + .serial_clock_rate = 21, + .clock_prescale_rate = 2, +}; + +max5864_driver_t max5864 = { + .bus = &spi_bus_ssp1, + .target_init = max5864_target_init, +}; + +void ssp1_set_mode_max5864(void) +{ + spi_bus_start(max5864.bus, &ssp_config_max5864); +} + static void max5864_write(max5864_driver_t* const drv, uint8_t value) { spi_bus_transfer(drv->bus, &value, 1); diff --git a/firmware/common/max5864.h b/firmware/common/max5864.h index 56d730cc..a617cdf0 100644 --- a/firmware/common/max5864.h +++ b/firmware/common/max5864.h @@ -23,6 +23,7 @@ #pragma once #include "spi_bus.h" +#include "spi_ssp.h" typedef struct _max5864_driver_t { spi_bus_t* const bus; @@ -37,3 +38,8 @@ void max5864_idle(max5864_driver_t* const drv); void max5864_rx(max5864_driver_t* const drv); void max5864_tx(max5864_driver_t* const drv); void max5864_xcvr(max5864_driver_t* const drv); + +/* Driver instance. */ +extern ssp_config_t ssp_config_max5864; +extern max5864_driver_t max5864; +void ssp1_set_mode_max5864(void); diff --git a/firmware/common/pins.c b/firmware/common/pins.c index bced06d4..24aeb033 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -30,6 +30,7 @@ #include "gpio.h" #include "leds.h" #include "max283x.h" +#include "max5864.h" #include "mixer.h" #include "platform_detect.h" #include "platform_gpio.h" From c8a0b02d85da760b7d87da0b382acc6ea2f90204 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 15:37:23 +0200 Subject: [PATCH 092/161] firmware: move mixer object's extern instance to mixer.h --- firmware/common/drivers.h | 2 -- firmware/common/mixer.h | 3 +++ firmware/common/rf_path.c | 1 - firmware/hackrf_usb/usb_api_register.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 423e46a8..461ada2c 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -26,7 +26,6 @@ extern "C" { #endif #include "cpld_jtag.h" -#include "mixer.h" #include "platform_detect.h" // IWYU pragma: keep #include "rf_path.h" #include "sgpio.h" @@ -41,7 +40,6 @@ extern ssp_config_t ssp_config_ice40_fpga; extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif -extern mixer_driver_t mixer; extern sgpio_config_t sgpio_config; extern rf_path_t rf_path; extern jtag_gpio_t jtag_gpio_cpld; diff --git a/firmware/common/mixer.h b/firmware/common/mixer.h index f7861725..35c0f047 100644 --- a/firmware/common/mixer.h +++ b/firmware/common/mixer.h @@ -65,3 +65,6 @@ extern fp_40_24_t mixer_set_frequency( extern void mixer_enable(mixer_driver_t* const mixer); extern void mixer_disable(mixer_driver_t* const mixer); extern void mixer_set_gpo(mixer_driver_t* const drv, uint8_t gpo); + +/* Mixer instance. */ +extern mixer_driver_t mixer; diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index ea1c0d45..721d647c 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -23,7 +23,6 @@ #include "rf_path.h" -#include "drivers.h" #include "max283x.h" #include "max5864.h" #include "mixer.h" diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index e88d0016..88a4de53 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -36,10 +36,10 @@ #include #include #ifdef IS_PRALINE + #include #include #endif #ifdef IS_NOT_RAD1O - #include #include #include #endif From 9a3abb6413912ab8fc55251e504421121f83e0a4 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 15:40:48 +0200 Subject: [PATCH 093/161] firmware: move rf_path object instance to rf_path.x --- firmware/common/drivers.c | 4 ---- firmware/common/drivers.h | 2 -- firmware/common/radio.c | 2 +- firmware/common/rf_path.c | 5 +++++ firmware/common/rf_path.h | 3 +++ 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index 769013b0..85a17746 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -51,10 +51,6 @@ fpga_driver_t fpga = { }; #endif -rf_path_t rf_path = { - .switchctrl = 0, -}; - jtag_gpio_t jtag_gpio_cpld = {}; jtag_t jtag_cpld = { diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 461ada2c..11ec20a2 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -27,7 +27,6 @@ extern "C" { #include "cpld_jtag.h" #include "platform_detect.h" // IWYU pragma: keep -#include "rf_path.h" #include "sgpio.h" #if defined(IS_PRALINE) #include "fpga.h" @@ -41,7 +40,6 @@ extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif extern sgpio_config_t sgpio_config; -extern rf_path_t rf_path; extern jtag_gpio_t jtag_gpio_cpld; extern jtag_t jtag_cpld; diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 8cf73cc8..17b7486c 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -26,7 +26,6 @@ #include #include "clock_io.h" -#include "drivers.h" #include "fixed_point.h" #include "max283x.h" #include "mixer.h" @@ -35,6 +34,7 @@ #include "transceiver_mode.h" #include "tuning.h" #ifdef IS_PRALINE + #include "drivers.h" #include "fpga.h" #include "tune_config.h" #endif diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 721d647c..e3292532 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -35,6 +35,11 @@ #include "platform_gpio.h" #endif +/* RF Path instance. */ +rf_path_t rf_path = { + .switchctrl = 0, +}; + /* * RF switches on Jawbreaker are controlled by General Purpose Outputs (GPO) on * the RFFC5072. diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index 12f83b53..ca7641e4 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -106,3 +106,6 @@ void rf_path_set_antenna(rf_path_t* const rf_path, const uint_fast8_t enable); #ifdef IS_PRALINE void narrowband_filter_set(const uint8_t value); #endif + +/* RF Path instance. */ +extern rf_path_t rf_path; From 47f91d29cf313c03ded1989498de7519f62d0a3f Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 16:19:05 +0200 Subject: [PATCH 094/161] firmware: move sgpio_config configuration instance to sgpio.x --- firmware/common/clock_gen.c | 1 - firmware/common/clock_io.c | 5 ++++- firmware/common/drivers.c | 6 ------ firmware/common/drivers.h | 2 -- firmware/common/sgpio.c | 5 +++++ firmware/common/sgpio.h | 3 +++ firmware/hackrf_usb/hackrf_usb.c | 2 +- firmware/hackrf_usb/usb_api_sweep.c | 2 +- firmware/hackrf_usb/usb_api_transceiver.c | 2 +- 9 files changed, 15 insertions(+), 13 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index 642fc61b..0f0085c2 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -23,7 +23,6 @@ #include -#include "drivers.h" #include "hackrf_ui.h" #include "i2c_bus.h" #include "platform_detect.h" diff --git a/firmware/common/clock_io.c b/firmware/common/clock_io.c index 279eb8c5..c68d470f 100644 --- a/firmware/common/clock_io.c +++ b/firmware/common/clock_io.c @@ -30,10 +30,13 @@ #include #include -#include "drivers.h" #include "gpdma.h" #include "gpio.h" +#ifdef IS_NOT_PRALINE + #include "sgpio.h" +#endif #ifdef IS_PRALINE + #include "drivers.h" #include "fpga.h" #include "platform_gpio.h" #endif diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index 85a17746..58b30cb2 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -21,8 +21,6 @@ #include "drivers.h" -#include - #if defined(IS_PRALINE) #include #include "fpga.h" @@ -30,10 +28,6 @@ #include "spi_bus.h" #endif -sgpio_config_t sgpio_config = { - .slice_mode_multislice = true, -}; - #ifdef IS_PRALINE ssp_config_t ssp_config_ice40_fpga = { .data_bits = SSP_DATA_8BITS, diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 11ec20a2..d0427e93 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -27,7 +27,6 @@ extern "C" { #include "cpld_jtag.h" #include "platform_detect.h" // IWYU pragma: keep -#include "sgpio.h" #if defined(IS_PRALINE) #include "fpga.h" #include "ice40_spi.h" @@ -39,7 +38,6 @@ extern ssp_config_t ssp_config_ice40_fpga; extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif -extern sgpio_config_t sgpio_config; extern jtag_gpio_t jtag_gpio_cpld; extern jtag_t jtag_cpld; diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index 568f1208..e6151e46 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -35,6 +35,11 @@ #include "clock_io.h" #endif +/* Driver configuration instance. */ +sgpio_config_t sgpio_config = { + .slice_mode_multislice = true, +}; + static void update_q_invert(sgpio_config_t* const config); void sgpio_configure_pin_functions(sgpio_config_t* const config) diff --git a/firmware/common/sgpio.h b/firmware/common/sgpio.h index 409cef0c..767d6f48 100644 --- a/firmware/common/sgpio.h +++ b/firmware/common/sgpio.h @@ -50,3 +50,6 @@ void sgpio_cpld_stream_disable(sgpio_config_t* const config); bool sgpio_cpld_stream_is_enabled(sgpio_config_t* const config); void sgpio_cpld_set_mixer_invert(sgpio_config_t* const config, uint_fast8_t invert); + +/* Driver configuration instance. */ +extern sgpio_config_t sgpio_config; diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index d02ea279..031f2001 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -71,6 +70,7 @@ #ifdef IS_NOT_PRALINE #include #include + #include #endif #include "usb_api_adc.h" diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index ae3b399d..72ff2467 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -25,13 +25,13 @@ #include -#include #include #include #include #include #include #include +#include #include #include #include diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index b6c6782b..7f6f9473 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -27,13 +27,13 @@ #include #include -#include #include #include #include #include #include #include +#include #include #include #include From a426b7518d5ebb267f7895ecbf41d009223725d0 Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Mon, 27 Apr 2026 16:56:20 -0400 Subject: [PATCH 095/161] Improve pins_shutdown() --- firmware/common/operacake_sctimer.c | 27 ++++++----------- firmware/common/pins.c | 34 +++++++++++++++++++-- firmware/common/platform_gpio.c | 1 + firmware/common/platform_gpio.h | 1 + firmware/common/platform_scu.c | 12 ++++++++ firmware/common/platform_scu.h | 12 ++++++++ firmware/common/rf_path.c | 27 +++++++++++++++++ firmware/common/rf_path.h | 1 + firmware/common/sgpio.c | 47 +++++++++++++++++++++++++++++ firmware/common/sgpio.h | 1 + 10 files changed, 143 insertions(+), 20 deletions(-) diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 3b95f6a1..1268114f 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -28,6 +28,7 @@ #include "delay.h" #include "platform_detect.h" +#include "platform_scu.h" #include "sct.h" #ifdef IS_NOT_PRALINE @@ -62,6 +63,8 @@ static uint32_t default_output = 0; */ void operacake_sctimer_init(void) { + const platform_scu_t* scu = platform_scu(); + // We start by resetting the SCTimer RESET_CTRL1 = RESET_CTRL1_SCT_RST; @@ -77,25 +80,15 @@ void operacake_sctimer_init(void) // Pin definitions for the HackRF // U2CTRL0 - scu_pinmux( - P7_4, - SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); + scu_pinmux(scu->CTOUT_13, scu->CTOUT_PINCFG); // U2CTRL1 - scu_pinmux( - P7_5, - SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); + scu_pinmux(scu->CTOUT_12, scu->CTOUT_PINCFG); // U3CTRL0 - scu_pinmux( - P7_6, - SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); + scu_pinmux(scu->CTOUT_11, scu->CTOUT_PINCFG); // U3CTRL1 - scu_pinmux( - P7_7, - SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); + scu_pinmux(scu->CTOUT_8, scu->CTOUT_PINCFG); // U1CTRL - scu_pinmux( - P7_0, - SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); + scu_pinmux(scu->CTOUT_14, scu->CTOUT_PINCFG); uint8_t sct_clock_input; #ifdef IS_NOT_PRALINE @@ -114,8 +107,8 @@ void operacake_sctimer_init(void) #endif #ifdef IS_PRALINE if (IS_PRALINE) { - // Configure pin P6_4 as SCT_IN_6 - scu_pinmux(P6_4, SCU_CLK_IN | SCU_CONF_FUNCTION1); + // Configure pin P6_4 as CTIN_6 + scu_pinmux(scu->SCT_CLK, scu->SCT_CLK_PINCFG); // Use the GIMA to connect MS0/CLK1 (SCT_CLK) on pin P6_4 to the SCTimer GIMA_CTIN_6_IN = 0x0 << 4; diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 6f8123cb..eb445fc3 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -178,9 +178,30 @@ void pins_shutdown(void) scu_pinmux(scu->TRIGGER_IN, scu->TRIGGER_IN_PINCFG); scu_pinmux(scu->TRIGGER_OUT, scu->TRIGGER_OUT_PINCFG); scu_pinmux(scu->PPS_OUT, scu->PPS_OUT_PINCFG); - scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4); - scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->SCT_CLK, scu->SCT_CLK_PINCFG); + + scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + + scu_pinmux(scu->SSP1_CIPO, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->SSP1_COPI, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->SSP1_SCK, SCU_GPIO_PDN | SCU_CONF_FUNCTION2); + + scu_pinmux(scu->XCVR_ENABLE, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->XCVR_RXENABLE, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->XCVR_CS, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->XCVR_RXHP, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->XCVR_LD, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + + scu_pinmux(scu->MIXER_LD, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->MIXER_SCLK, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->MIXER_SDATA, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->MIXER_ENX, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->MIXER_RESETX, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->MIXER_ENBL, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + + scu_pinmux(scu->AD_CS, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); p2_ctrl_set(P2_SIGNAL_CLK3); p1_ctrl_set(P1_SIGNAL_CLKIN); @@ -202,8 +223,12 @@ void pins_shutdown(void) gpio_clear(gpio->fpga_cfg_creset); gpio_output(gpio->fpga_cfg_creset); gpio_input(gpio->fpga_cfg_cdone); + gpio_input(gpio->max5864_select); + + rf_path_pin_shutdown(); } #endif + sgpio_pin_shutdown(&sgpio_config); /* enable input on SCL and SDA pins */ SCU_SFSI2C0 = SCU_I2C0_NOMINAL; @@ -360,6 +385,9 @@ void pins_setup(void) (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; } + scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4); + scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); } #endif diff --git a/firmware/common/platform_gpio.c b/firmware/common/platform_gpio.c index a5b892e8..eeecd90e 100644 --- a/firmware/common/platform_gpio.c +++ b/firmware/common/platform_gpio.c @@ -234,6 +234,7 @@ const platform_gpio_t* platform_gpio(void) gpio.rffc5072_data = &GPIO4_14; gpio.rffc5072_reset = &GPIO2_14; gpio.rffc5072_ld = &GPIO6_25; + gpio.rffc5072_enbl = &GPIO4_12; } #endif diff --git a/firmware/common/platform_gpio.h b/firmware/common/platform_gpio.h index eeb37b05..a7e68bc9 100644 --- a/firmware/common/platform_gpio.h +++ b/firmware/common/platform_gpio.h @@ -137,6 +137,7 @@ typedef struct { gpio_t rffc5072_data; gpio_t rffc5072_reset; gpio_t rffc5072_ld; // PRALINE + gpio_t rffc5072_enbl; // PRALINE gpio_t vco_ce; // RAD1O gpio_t vco_sclk; // RAD1O gpio_t vco_sdata; // RAD1O diff --git a/firmware/common/platform_scu.c b/firmware/common/platform_scu.c index ef894745..c9e468e9 100644 --- a/firmware/common/platform_scu.c +++ b/firmware/common/platform_scu.c @@ -258,10 +258,12 @@ const platform_scu_t* platform_scu(void) scu.MIXER_SDATA = (P9_2); /* GPIO4[14] on P9_2 */ scu.MIXER_RESETX = (P5_5); /* GPIO2[14] on P5_5 */ scu.MIXER_LD = (PD_11); /* GPIO6[25] on PD_11 */ + scu.MIXER_ENBL = (P9_0); /* GPIO4[12] on P9_0 */ scu.MIXER_SCLK_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.MIXER_SDATA_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); scu.MIXER_LD_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.MIXER_ENBL_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION0); } #endif #ifdef IS_RAD1O @@ -365,6 +367,7 @@ const platform_scu_t* platform_scu(void) scu.TRIGGER_IN = (PD_12); /* GPIO6[26] on PD_12 */ scu.TRIGGER_OUT = (P2_6); /* GPIO5[6] on P2_6 */ scu.PPS_OUT = (P2_5); /* GPIO5[5] on P2_5 */ + scu.SCT_CLK = (P6_4); /* CTIN_6 on P6_4 */ scu.P2_CTRL0_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.P2_CTRL1_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); @@ -376,6 +379,7 @@ const platform_scu_t* platform_scu(void) scu.TRIGGER_IN_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.TRIGGER_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); scu.PPS_OUT_PINCFG = (SCU_GPIO_FAST | SCU_CONF_FUNCTION4); + scu.SCT_CLK_PINCFG = (SCU_CLK_IN | SCU_CONF_FUNCTION1); } #endif @@ -438,6 +442,14 @@ const platform_scu_t* platform_scu(void) } #endif + /* SCT */ + scu.CTOUT_8 = (P7_7); + scu.CTOUT_11 = (P7_6); + scu.CTOUT_12 = (P7_5); + scu.CTOUT_13 = (P7_4); + scu.CTOUT_14 = (P7_0); + scu.CTOUT_PINCFG = (SCU_CONF_EPUN_DIS_PULLUP | SCU_CONF_EHS_FAST | SCU_CONF_FUNCTION1); + scu.PINMUX_ISP = (P2_7); /* GPIO0[7] */ scu.PINMUX_GP_CLKIN = (P4_7); diff --git a/firmware/common/platform_scu.h b/firmware/common/platform_scu.h index 1ffb224c..71c0f742 100644 --- a/firmware/common/platform_scu.h +++ b/firmware/common/platform_scu.h @@ -157,7 +157,9 @@ typedef struct { #endif #ifdef IS_PRALINE scu_grp_pin_t MIXER_LD; + scu_grp_pin_t MIXER_ENBL; uint32_t MIXER_LD_PINCFG; + uint32_t MIXER_ENBL_PINCFG; #endif #ifdef IS_RAD1O scu_grp_pin_t VCO_CE; @@ -240,6 +242,7 @@ typedef struct { scu_grp_pin_t TRIGGER_IN; scu_grp_pin_t TRIGGER_OUT; scu_grp_pin_t PPS_OUT; + scu_grp_pin_t SCT_CLK; scu_grp_pin_t P2_CTRL0_PINCFG; scu_grp_pin_t P2_CTRL1_PINCFG; @@ -251,6 +254,7 @@ typedef struct { scu_grp_pin_t TRIGGER_IN_PINCFG; scu_grp_pin_t TRIGGER_OUT_PINCFG; scu_grp_pin_t PPS_OUT_PINCFG; + scu_grp_pin_t SCT_CLK_PINCFG; #endif /* HackRF One r9 */ @@ -300,6 +304,14 @@ typedef struct { scu_grp_pin_t PINMUX_U0_TXD; scu_grp_pin_t PINMUX_U0_RXD; + /* SCT */ + scu_grp_pin_t CTOUT_8; + scu_grp_pin_t CTOUT_11; + scu_grp_pin_t CTOUT_12; + scu_grp_pin_t CTOUT_13; + scu_grp_pin_t CTOUT_14; + scu_grp_pin_t CTOUT_PINCFG; + scu_grp_pin_t PINMUX_ISP; scu_grp_pin_t PINMUX_GP_CLKIN; diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index ea1c0d45..8fc071a7 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -317,6 +317,33 @@ static void switchctrl_set(rf_path_t* const rf_path, const uint8_t gpo) #endif } +void rf_path_pin_shutdown(void) +{ +#ifdef IS_PRALINE + if (IS_PRALINE) { + const platform_scu_t* scu = platform_scu(); + + /* Configure RF switch control signals */ + scu_pinmux(scu->TX_EN, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + board_rev_t rev = detected_revision(); + if ((rev == BOARD_REV_PRALINE_R1_0) || + (rev == BOARD_REV_GSG_PRALINE_R1_0)) { + scu_pinmux(scu->MIX_EN_N_R1_0, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + } else { + scu_pinmux(scu->MIX_EN_N, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + } + scu_pinmux(scu->LPF_EN, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->RF_AMP_EN, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + + /* Configure antenna port power control signal */ + scu_pinmux(scu->ANT_BIAS_EN_N, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + + /* Configure RF power supply (VAA) switch */ + scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + } +#endif +} + void rf_path_pin_setup(rf_path_t* const rf_path) { #ifdef IS_JAWBREAKER diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index 12f83b53..a1e91b4c 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -93,6 +93,7 @@ typedef struct { }; } rf_path_t; +void rf_path_pin_shutdown(void); void rf_path_pin_setup(rf_path_t* const rf_path); void rf_path_init(rf_path_t* const rf_path); diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index 568f1208..a88290db 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -37,6 +37,53 @@ static void update_q_invert(sgpio_config_t* const config); +void sgpio_pin_shutdown(sgpio_config_t* const config) +{ + const platform_scu_t* scu = platform_scu(); + +#ifdef IS_PRALINE + if (IS_PRALINE) { + scu_pinmux(scu->PINMUX_SGPIO0, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO1, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO2, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO3, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO4, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->PINMUX_SGPIO5, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO6, SCU_GPIO_PDN | SCU_CONF_FUNCTION4); + scu_pinmux(scu->PINMUX_SGPIO7, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO8, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO9, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO11, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_SGPIO12, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + } +#endif + +#ifdef IS_H1_R9 + if (IS_H1_R9) { + scu_pinmux( + scu->H1R9_TRIGGER_EN, + SCU_GPIO_PDN | SCU_CONF_FUNCTION4); /* GPIO5[5] */ + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { + scu_pinmux( + scu->TRIGGER_EN, + SCU_GPIO_PDN | SCU_CONF_FUNCTION4); /* GPIO5[12] */ + } +#endif + + gpio_input(config->gpio_q_invert); + +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + trigger_enable(false); + gpio_output(config->gpio_trigger_enable); + } +#endif +} + void sgpio_configure_pin_functions(sgpio_config_t* const config) { const platform_scu_t* scu = platform_scu(); diff --git a/firmware/common/sgpio.h b/firmware/common/sgpio.h index 409cef0c..392209da 100644 --- a/firmware/common/sgpio.h +++ b/firmware/common/sgpio.h @@ -41,6 +41,7 @@ typedef struct { bool slice_mode_multislice; } sgpio_config_t; +void sgpio_pin_shutdown(sgpio_config_t* const config); void sgpio_configure_pin_functions(sgpio_config_t* const config); void sgpio_test_interface(sgpio_config_t* const config); void sgpio_set_slice_mode(sgpio_config_t* const config, const bool multi_slice); From e8cfb86e10bf06a2bd1d8a61f19747ccffaecc06 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 28 Apr 2026 14:52:13 +0100 Subject: [PATCH 096/161] Add picoprintf to RAD1O firmware build. Code taken from https://github.com/rom-p/picoprintf @ dfb7f93. --- firmware/common/picobool.h | 20 ++ firmware/common/picoprintf.c | 342 +++++++++++++++++++++++++++++ firmware/common/picoprintf.h | 43 ++++ firmware/hackrf_usb/CMakeLists.txt | 1 + 4 files changed, 406 insertions(+) create mode 100644 firmware/common/picobool.h create mode 100644 firmware/common/picoprintf.c create mode 100644 firmware/common/picoprintf.h diff --git a/firmware/common/picobool.h b/firmware/common/picobool.h new file mode 100644 index 00000000..3f0324c8 --- /dev/null +++ b/firmware/common/picobool.h @@ -0,0 +1,20 @@ +#ifndef __picobool_h_INCLUDED__ +#define __picobool_h_INCLUDED__ + +// this file provides a bool type which might or might not be available on your platform + +#ifndef __cplusplus // C++: use standard bool + #ifndef __STDC_VERSION__ // Pre-C99: define bool manually + typedef unsigned char bool; + #define true 1 + #define false 0 + #elif __STDC_VERSION__ < 199901L // C90/C95: define bool manually + typedef unsigned char bool; + #define true 1 + #define false 0 + #else // C99+: use standard bool + #include + #endif +#endif + +#endif // __picobool_h_INCLUDED__ diff --git a/firmware/common/picoprintf.c b/firmware/common/picoprintf.c new file mode 100644 index 00000000..18ef565c --- /dev/null +++ b/firmware/common/picoprintf.c @@ -0,0 +1,342 @@ +#include "picoprintf.h" +#include "picobool.h" + +#include // fabs() +#include // llabs() + + +static void flip(char *pLeft, char *pRight) { + pRight--; + while (pLeft < pRight) { + char tmp = *pLeft; + *pLeft = *pRight; + *pRight = tmp; + pLeft++; + pRight--; + } +} + +// using #define over `inline` for enabling porting to old C +#if !defined(MIN) +#define MIN(left, right) (((left) < (right)) ? (left) : (right)) +#endif +#if !defined(MAX) +#define MAX(left, right) (((left) > (right)) ? (left) : (right)) +#endif + + +// returns the pointer to the null-terminating character of the filled string +int pico_vsnprintf(char *pDest, size_t cbDest, const char *pFormat, va_list vl) { + const char *pLowercaseNumberDigits = "0123456789abcdef"; + const char *pUppercaseNumberDigits = "0123456789ABCDEF"; + const char *pOctalDigits = pLowercaseNumberDigits; + const char *pBinaryDigits = pLowercaseNumberDigits; + char *pStart = pDest; + for (char *pEnd = pDest + cbDest - 1; *pFormat && pDest < pEnd; ) { + if (*pFormat != '%') { + *pDest++ = *pFormat++; + } else { // format starts here + pFormat++; // skipping the '%' + if (*pFormat == '%') { // unless it's indeed a '%' + *pDest++ = *pFormat++; + } else { // first, collect the format + char format = '\0'; + int bits_per_digit = 0; // valid in 'b', 'o', 'p' and 'x'/'X' modes only + int whole_chars = 0; // number of chars in the whole part of the number + int decimal_chars = -1; // if not specified, %f are rendered with 6, while %g are rendered with 0 + #ifdef __aarch64__ // these platforms benefit from packing flags into a single variable + struct { + unsigned force_sign:1; + unsigned fill_zeros:1; + unsigned left_align:1; + unsigned seen_period:1; + unsigned seen_numbers:1; + unsigned treat_as_unsigned:1; + unsigned treat_as_long:1; + unsigned render_in_lowercase:1; + } flags = {0}; + #define FLAGS flags. + + #else // other platforms do not benefit from struct packing + unsigned force_sign = 0; + unsigned fill_zeros = 0; + unsigned left_align = 0; + unsigned seen_period = 0; + unsigned seen_numbers = 0; + unsigned treat_as_unsigned = 0; + unsigned treat_as_long = 0; + unsigned render_in_lowercase = 0; + #define FLAGS + #endif // struct packing platforms + for (; *pFormat && '\0' == format; pFormat++) { + switch (*pFormat) { + #ifdef PICOFORMAT_HANDLE_FORCEDSIGN + case '+': + FLAGS force_sign = 1; + break; + #endif // PICOFORMAT_HANDLE_FORCEDSIGN + #ifdef PICOFORMAT_HANDLE_FILL + case '-': // left-align flag + FLAGS left_align = 1; + break; + #endif // PICOFORMAT_HANDLE_FILL + case '0': + if (!FLAGS seen_numbers) { + FLAGS fill_zeros = 1; + break; + } + // fall through + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + *(FLAGS seen_period ? &decimal_chars : &whole_chars ) *= 10; + *(FLAGS seen_period ? &decimal_chars : &whole_chars) += *pFormat - '0'; + FLAGS seen_numbers = 1; + break; + case '.': + FLAGS seen_numbers = FLAGS seen_period = 1; + decimal_chars = 0; + break; + case '*': // dynamic width/precision: read value from arg list + *(FLAGS seen_period ? &decimal_chars : &whole_chars) = va_arg(vl, int); + FLAGS seen_numbers = 1; + break; + case 'l': // long modifier + FLAGS treat_as_long = 1; + break; + case 'u': // unsigned decimal integer + FLAGS treat_as_unsigned = 1; + format = 'd'; + break; + #ifdef PICOFORMAT_HANDLE_BIN + case 'b': + bits_per_digit = 0; + FLAGS treat_as_unsigned = 1; + format = 'b'; + break; + #endif // PICOFORMAT_HANDLE_BIN + #ifdef PICOFORMAT_HANDLE_OCT + case 'o': // octal integer + bits_per_digit = 3; + FLAGS treat_as_unsigned = 1; + format = 'b'; + break; + #endif // PICOFORMAT_HANDLE_OCT + #ifdef PICOFORMAT_HANDLE_HEX + case 'x': // hexadecimal integer + case 'p': // pointer + bits_per_digit = 4; + FLAGS treat_as_unsigned = 1; + FLAGS render_in_lowercase = 1; + format = 'b'; + break; + case 'X': // hexadecimal integer, uppercase + bits_per_digit = 4; + FLAGS treat_as_unsigned = 1; + format = 'b'; + break; + #endif // PICOFORMAT_HANDLE_HEX + #ifdef PICOFORMAT_HANDLE_FLOATS + case 'a': + #ifdef PICOFORMAT_HANDLE_EXPONENTS + case 'e': // floating point, exponent format + #endif // PICOFORMAT_HANDLE_EXPONENTS + case 'f': + FLAGS render_in_lowercase = 1; + // fall through + case 'F': + #endif // PICOFORMAT_HANDLE_FLOATS + case 'c': // single char: always supported + case 'd': // integer: always supported + case 'i': // integer: always supported + case 's': // string of native `char`s: always supported + format = *pFormat; + break; + default: + FORMAT_ERROR_DELEGATE("detected unhandled format specifier: %c", *pFormat); + break; + } + } + + // format is parsed, now render the value + char *pParamStarts = pDest; + switch (format) { + case 'c': // single char + *pDest++ = va_arg(vl, int) & 0xff; + break; + case 's': { // null-terminated string + #ifdef PICOFORMAT_HANDLE_FILL + int len = 0; // effective length, bounded by precision if set + const char *pStr = va_arg(vl, const char*); + for (; pStr[len] && (decimal_chars < 0 || len < decimal_chars); len++); + if (!FLAGS left_align) { // right-align: pad on the left + #ifdef PICOFORMAT_CLANG_QUIRK // clang's non-standard: '0' flag zero-pads strings + char chFill = FLAGS fill_zeros ? '0' : ' '; + #else // PICOFORMAT_CLANG_QUIRK // standard C: '0' flag is undefined for %s, use spaces + char chFill = ' '; + #endif // PICOFORMAT_CLANG_QUIRK + for (; pDest < pEnd && whole_chars > len; whole_chars--) { + *pDest++ = chFill; + } + } + for (int ii = 0; ii < len && pDest < pEnd; ii++) { + *pDest++ = pStr[ii]; + } + if (FLAGS left_align) { // left-align: pad on the right (always spaces; '-' flag overrides '0') + for (; pDest < pEnd && whole_chars > len; whole_chars--) { + *pDest++ = ' '; + } + } + #else // PICOFORMAT_HANDLE_FILL + for (const char *pStr = va_arg(vl, const char*); *pStr && pDest < pEnd; ) { + *pDest++ = *pStr++; + } + #endif // PICOFORMAT_HANDLE_FILL + } + break; + #if defined(PICOFORMAT_HANDLE_HEX) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_BIN) + case 'b': { // binary, oct, or hex integer, always unsigned + long long int val = 0; + if (FLAGS treat_as_long) { + val = va_arg(vl, unsigned long long int); + } else { + val = va_arg(vl, unsigned); + } + unsigned mask = bits_per_digit == 4 ? 0x0f : bits_per_digit == 3 ? 0x07 : 0x01; + #if defined(PICOFORMAT_HANDLE_HEX) + const char* chars = FLAGS render_in_lowercase ? pLowercaseNumberDigits : pUppercaseNumberDigits; + #elif defined(PICOFORMAT_HANDLE_OCT) + const char* chars = pOctalDigits; + #else + const char* chars = pBinaryDigits; + #endif // individual non-decimal formats + while (pDest < pEnd + && (pDest == pParamStarts + || val + || pDest - pParamStarts < whole_chars)) { + char ch; + if (pDest == pParamStarts || 0 != val) { // if first char or there's still meaningful digits + ch = chars[val & mask]; + val >>= bits_per_digit; + } else { + ch = FLAGS fill_zeros ? '0' : ' '; + } + *pDest++ = ch; + } + flip(pParamStarts, pDest); + } + break; + #endif // defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_HEX) + case 'd': // decimal integer + case 'i': { + long long int val = 0; + if (FLAGS treat_as_long) { + if (FLAGS treat_as_unsigned) { + val = va_arg(vl, unsigned long long int); + } else { + val = va_arg(vl, long long int); + } + } else { + if (FLAGS treat_as_unsigned) { + val = va_arg(vl, unsigned); + } else { + val = va_arg(vl, int); + } + } + char chSign = '\0'; + if ((FLAGS force_sign || val < 0) && pDest < pEnd) { + chSign = val < 0 ? '-' : '+'; + val = llabs(val); + whole_chars--; + } + while (pDest < pEnd + && (pDest == pParamStarts + || val + || pDest - pParamStarts < whole_chars)) { + char ch; + if (pDest == pParamStarts || 0 != val) { // if first digit (i.e. zero) or there's still non-zero digits to write + ch = val % 10 + '0'; + val /= 10; + } else { + if ('\0' != chSign && !FLAGS fill_zeros) { // write a sign if filling with spaces, not with zeros + *pDest++ = chSign; + whole_chars++; + chSign = '\0'; // prevent the sign from being written twice + } + #ifdef PICOFORMAT_HANDLE_FILL + ch = FLAGS fill_zeros ? '0' : ' '; + #else // PICOFORMAT_HANDLE_FILL + break; + #endif // PICOFORMAT_HANDLE_FILL + } + *pDest++ = ch; + } + if ('\0' != chSign) { // write a sign if it wasn't written before + *pDest++ = chSign; + } + flip(pParamStarts, pDest); + } + break; + #ifdef PICOFORMAT_HANDLE_FLOATS + case 'f': case 'F': { + double val = va_arg(vl, double); + if (FLAGS force_sign || val < 0.f) { + *pDest++ = val < 0.f ? '-' : '+'; + val = fabs(val); + pParamStarts = pDest; + } + if (val == INFINITY || val == -INFINITY || isnan(val)) { + for (const char *pszVal = isnan(val) ? FLAGS render_in_lowercase ? "nan" : "NAN": FLAGS render_in_lowercase ? "inf" : "INF" + ; pDest < pEnd && *pszVal + ; *pDest++ = *pszVal++); + } else { + if (decimal_chars == -1) { + if (format == 'g') { + decimal_chars = 0; + } else { + decimal_chars = 6; + } + } + whole_chars = MAX(0, whole_chars - decimal_chars); + whole_chars = MAX(whole_chars, pDest - pParamStarts + 1); // at least sign (if present) and first char + for (float digit = 1.f + ; pDest < pEnd && (val >= digit || pDest - pParamStarts < whole_chars) + ; digit *= 10.f) { + char ch = (int)(val / digit) % 10 + '0'; + *pDest++ = ch; + } + flip(pParamStarts, pDest); + if (decimal_chars && pDest < pEnd) { + *pDest++ = '.'; + val += .5f * pow(10.f, -decimal_chars); // compensating for rounding error + for (size_t digit = 0; pDest < pEnd && digit < decimal_chars; digit++) { + val = (val - (int)(val)) * 10.f; + *pDest++ = (int)(val) + '0'; + } + } + } + } + break; + #endif // PICOFORMAT_HANDLE_FLOATS + } + } + } + } + *pDest = '\0'; + return pDest - pStart; +} + + +int pico_snprintf(char *pDest, size_t cbDest, const char *pFormat, ...) { + va_list vl; + va_start(vl, pFormat); + int result = pico_vsnprintf(pDest, cbDest, pFormat, vl); + va_end(vl); + return result; +} diff --git a/firmware/common/picoprintf.h b/firmware/common/picoprintf.h new file mode 100644 index 00000000..82796502 --- /dev/null +++ b/firmware/common/picoprintf.h @@ -0,0 +1,43 @@ +#ifndef __picoprintf_h_INCLUDED__ +#define __picoprintf_h_INCLUDED__ + +#include // size_t +#include // va_* + + +int pico_snprintf(char *pDest, size_t cbDest, const char *pFormat, ...); +int pico_vsnprintf(char *pDest, size_t cbDest, const char *pFormat, va_list vl); + +// PLEASE use `pico_snprintf()` instead!!! This function is vulnerable to buffer overflows +inline int pico_sprintf(char *pDest, const char *pFormat, ...) { + va_list vl; + va_start(vl, pFormat); + int result = pico_vsnprintf(pDest, -1, pFormat, vl); + va_end(vl); + return result; +} + + +// +// IMPORTANT!!! +// comment/uncomment the following lines corresponding to the features you need +// +// #define PICOFORMAT_HANDLE_FILL // uncomment this line to handle "%6i" and "%04d" -- the fill with zeroes or spaces +// #define PICOFORMAT_HANDLE_FORCEDSIGN // uncomment this line to handle "%+d" -- the forced sign placement +// #define PICOFORMAT_HANDLE_BIN // uncomment this line to handle "%b" -- binary representation +// #define PICOFORMAT_HANDLE_OCT // uncomment this line to handle "%o" +// #define PICOFORMAT_HANDLE_HEX // uncomment this line to handle "%x" and "%X" +// #define PICOFORMAT_HANDLE_FLOATS // uncomment this line to handle the "%f" +// #define PICOFORMAT_CLANG_QUIRK // uncomment this line to match clang's non-standard "%010s" behavior (zero-pad strings when both '0' flag and width are set) + + +// by default, the debug builds (determined by `#define _DEBUG`) will real-time print errors when a feature is used that is not enabled above +#ifndef FORMAT_ERROR_DELEGATE + #ifdef _DEBUG + #define FORMAT_ERROR_DELEGATE(__message, __arg) printf(__message, __arg); printf("\n"); + #else // _DEBUG + #define FORMAT_ERROR_DELEGATE(__message, __arg) + #endif // _DEBUG +#endif // FORMAT_ERROR_DELEGATE + +#endif // __picoprintf_h_INCLUDED__ diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index 3fd3ea87..dba0120e 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -136,6 +136,7 @@ if(BOARD STREQUAL "RAD1O") "${PATH_HACKRF_FIRMWARE_COMMON}/rad1o/smallfonts.c" "${PATH_HACKRF_FIRMWARE_COMMON}/rad1o/draw.c" "${PATH_HACKRF_FIRMWARE_COMMON}/rad1o/ubuntu18.c" + "${PATH_HACKRF_FIRMWARE_COMMON}/picoprintf.c" "${PATH_HACKRF_FIRMWARE_COMMON}/ui_rad1o.c" ) endif() From 58334a565b2a93a5b33663732709512289f33471 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 28 Apr 2026 15:42:18 +0100 Subject: [PATCH 097/161] picoprintf: remove unnecessary picobool.h. We don't require workarounds for ancient C environments. --- firmware/common/picobool.h | 20 -------------------- firmware/common/picoprintf.c | 1 - 2 files changed, 21 deletions(-) delete mode 100644 firmware/common/picobool.h diff --git a/firmware/common/picobool.h b/firmware/common/picobool.h deleted file mode 100644 index 3f0324c8..00000000 --- a/firmware/common/picobool.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef __picobool_h_INCLUDED__ -#define __picobool_h_INCLUDED__ - -// this file provides a bool type which might or might not be available on your platform - -#ifndef __cplusplus // C++: use standard bool - #ifndef __STDC_VERSION__ // Pre-C99: define bool manually - typedef unsigned char bool; - #define true 1 - #define false 0 - #elif __STDC_VERSION__ < 199901L // C90/C95: define bool manually - typedef unsigned char bool; - #define true 1 - #define false 0 - #else // C99+: use standard bool - #include - #endif -#endif - -#endif // __picobool_h_INCLUDED__ diff --git a/firmware/common/picoprintf.c b/firmware/common/picoprintf.c index 18ef565c..185ccdfb 100644 --- a/firmware/common/picoprintf.c +++ b/firmware/common/picoprintf.c @@ -1,5 +1,4 @@ #include "picoprintf.h" -#include "picobool.h" #include // fabs() #include // llabs() From 5b4392f609633877ac64b351a6aa362cf73cedb3 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 28 Apr 2026 15:44:09 +0100 Subject: [PATCH 098/161] picoprintf: add LICENSE text to source & header files. --- firmware/common/picoprintf.c | 23 +++++++++++++++++++++++ firmware/common/picoprintf.h | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/firmware/common/picoprintf.c b/firmware/common/picoprintf.c index 185ccdfb..f71af487 100644 --- a/firmware/common/picoprintf.c +++ b/firmware/common/picoprintf.c @@ -1,3 +1,26 @@ +/* MIT License + * + * Copyright (c) 2023-2026 rom-p + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + #include "picoprintf.h" #include // fabs() diff --git a/firmware/common/picoprintf.h b/firmware/common/picoprintf.h index 82796502..96ac9b14 100644 --- a/firmware/common/picoprintf.h +++ b/firmware/common/picoprintf.h @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2023-2026 rom-p + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + #ifndef __picoprintf_h_INCLUDED__ #define __picoprintf_h_INCLUDED__ From 83fd6a00a81dac8863423346788ecc35b848e725 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 28 Apr 2026 15:45:16 +0100 Subject: [PATCH 099/161] picoprintf: Fix unused variable warnings with GCC 15.2. --- firmware/common/picoprintf.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/firmware/common/picoprintf.c b/firmware/common/picoprintf.c index f71af487..64446c3c 100644 --- a/firmware/common/picoprintf.c +++ b/firmware/common/picoprintf.c @@ -23,9 +23,11 @@ #include "picoprintf.h" -#include // fabs() #include // llabs() +#ifdef PICOFORMAT_HANDLE_FLOATS + #include // fabs() +#endif static void flip(char *pLeft, char *pRight) { pRight--; @@ -49,10 +51,16 @@ static void flip(char *pLeft, char *pRight) { // returns the pointer to the null-terminating character of the filled string int pico_vsnprintf(char *pDest, size_t cbDest, const char *pFormat, va_list vl) { +#if defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_HEX) const char *pLowercaseNumberDigits = "0123456789abcdef"; +#endif +#if defined(PICOFORMAT_HANDLE_HEX) const char *pUppercaseNumberDigits = "0123456789ABCDEF"; +#elif defined(PICOFORMAT_HANDLE_OCT) const char *pOctalDigits = pLowercaseNumberDigits; +#elif defined(PICOFORMAT_HANDLE_BIN) const char *pBinaryDigits = pLowercaseNumberDigits; +#endif char *pStart = pDest; for (char *pEnd = pDest + cbDest - 1; *pFormat && pDest < pEnd; ) { if (*pFormat != '%') { @@ -63,7 +71,9 @@ int pico_vsnprintf(char *pDest, size_t cbDest, const char *pFormat, va_list vl) *pDest++ = *pFormat++; } else { // first, collect the format char format = '\0'; + #if defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_HEX) int bits_per_digit = 0; // valid in 'b', 'o', 'p' and 'x'/'X' modes only + #endif int whole_chars = 0; // number of chars in the whole part of the number int decimal_chars = -1; // if not specified, %f are rendered with 6, while %g are rendered with 0 #ifdef __aarch64__ // these platforms benefit from packing flags into a single variable @@ -82,12 +92,16 @@ int pico_vsnprintf(char *pDest, size_t cbDest, const char *pFormat, va_list vl) #else // other platforms do not benefit from struct packing unsigned force_sign = 0; unsigned fill_zeros = 0; + #ifdef PICOFORMAT_HANDLE_FILL unsigned left_align = 0; + #endif unsigned seen_period = 0; unsigned seen_numbers = 0; unsigned treat_as_unsigned = 0; unsigned treat_as_long = 0; + #if defined(PICOFORMAT_HANDLE_HEX) || defined(PICOFORMAT_HANDLE_FLOATS) unsigned render_in_lowercase = 0; + #endif #define FLAGS #endif // struct packing platforms for (; *pFormat && '\0' == format; pFormat++) { @@ -235,7 +249,7 @@ int pico_vsnprintf(char *pDest, size_t cbDest, const char *pFormat, va_list vl) const char* chars = FLAGS render_in_lowercase ? pLowercaseNumberDigits : pUppercaseNumberDigits; #elif defined(PICOFORMAT_HANDLE_OCT) const char* chars = pOctalDigits; - #else + #elif defined(PICOFORMAT_HANDLE_BIN) const char* chars = pBinaryDigits; #endif // individual non-decimal formats while (pDest < pEnd From a20da328ba03eb3f06aac33eda372f1e061e6d32 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 28 Apr 2026 15:45:57 +0100 Subject: [PATCH 100/161] picoprintf: use #pragma once. --- firmware/common/picoprintf.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/firmware/common/picoprintf.h b/firmware/common/picoprintf.h index 96ac9b14..0fb5438b 100644 --- a/firmware/common/picoprintf.h +++ b/firmware/common/picoprintf.h @@ -20,8 +20,7 @@ * SOFTWARE. */ -#ifndef __picoprintf_h_INCLUDED__ -#define __picoprintf_h_INCLUDED__ +#pragma once #include // size_t #include // va_* @@ -61,5 +60,3 @@ inline int pico_sprintf(char *pDest, const char *pFormat, ...) { #define FORMAT_ERROR_DELEGATE(__message, __arg) #endif // _DEBUG #endif // FORMAT_ERROR_DELEGATE - -#endif // __picoprintf_h_INCLUDED__ From f970722195856b5f910f393b8871becef6061d4f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 28 Apr 2026 16:36:14 +0100 Subject: [PATCH 101/161] picoprintf: Standardize code style. --- firmware/common/picoprintf.c | 715 +++++++++++++++++++---------------- firmware/common/picoprintf.h | 38 +- 2 files changed, 404 insertions(+), 349 deletions(-) diff --git a/firmware/common/picoprintf.c b/firmware/common/picoprintf.c index 64446c3c..1959edb5 100644 --- a/firmware/common/picoprintf.c +++ b/firmware/common/picoprintf.c @@ -23,356 +23,411 @@ #include "picoprintf.h" -#include // llabs() +#include // llabs() #ifdef PICOFORMAT_HANDLE_FLOATS - #include // fabs() + #include // fabs() #endif -static void flip(char *pLeft, char *pRight) { - pRight--; - while (pLeft < pRight) { - char tmp = *pLeft; - *pLeft = *pRight; - *pRight = tmp; - pLeft++; - pRight--; - } +static void flip(char* pLeft, char* pRight) +{ + pRight--; + while (pLeft < pRight) { + char tmp = *pLeft; + *pLeft = *pRight; + *pRight = tmp; + pLeft++; + pRight--; + } } // using #define over `inline` for enabling porting to old C #if !defined(MIN) -#define MIN(left, right) (((left) < (right)) ? (left) : (right)) + #define MIN(left, right) (((left) < (right)) ? (left) : (right)) #endif #if !defined(MAX) -#define MAX(left, right) (((left) > (right)) ? (left) : (right)) + #define MAX(left, right) (((left) > (right)) ? (left) : (right)) #endif - // returns the pointer to the null-terminating character of the filled string -int pico_vsnprintf(char *pDest, size_t cbDest, const char *pFormat, va_list vl) { -#if defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_HEX) - const char *pLowercaseNumberDigits = "0123456789abcdef"; +int pico_vsnprintf(char* pDest, size_t cbDest, const char* pFormat, va_list vl) +{ +#if defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || \ + defined(PICOFORMAT_HANDLE_HEX) + const char* pLowercaseNumberDigits = "0123456789abcdef"; #endif #if defined(PICOFORMAT_HANDLE_HEX) - const char *pUppercaseNumberDigits = "0123456789ABCDEF"; + const char* pUppercaseNumberDigits = "0123456789ABCDEF"; #elif defined(PICOFORMAT_HANDLE_OCT) - const char *pOctalDigits = pLowercaseNumberDigits; + const char* pOctalDigits = pLowercaseNumberDigits; #elif defined(PICOFORMAT_HANDLE_BIN) - const char *pBinaryDigits = pLowercaseNumberDigits; + const char* pBinaryDigits = pLowercaseNumberDigits; #endif - char *pStart = pDest; - for (char *pEnd = pDest + cbDest - 1; *pFormat && pDest < pEnd; ) { - if (*pFormat != '%') { - *pDest++ = *pFormat++; - } else { // format starts here - pFormat++; // skipping the '%' - if (*pFormat == '%') { // unless it's indeed a '%' - *pDest++ = *pFormat++; - } else { // first, collect the format - char format = '\0'; - #if defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_HEX) - int bits_per_digit = 0; // valid in 'b', 'o', 'p' and 'x'/'X' modes only - #endif - int whole_chars = 0; // number of chars in the whole part of the number - int decimal_chars = -1; // if not specified, %f are rendered with 6, while %g are rendered with 0 - #ifdef __aarch64__ // these platforms benefit from packing flags into a single variable - struct { - unsigned force_sign:1; - unsigned fill_zeros:1; - unsigned left_align:1; - unsigned seen_period:1; - unsigned seen_numbers:1; - unsigned treat_as_unsigned:1; - unsigned treat_as_long:1; - unsigned render_in_lowercase:1; - } flags = {0}; - #define FLAGS flags. + char* pStart = pDest; + for (char* pEnd = pDest + cbDest - 1; *pFormat && pDest < pEnd;) { + if (*pFormat != '%') { + *pDest++ = *pFormat++; + } else { // format starts here + pFormat++; // skipping the '%' + if (*pFormat == '%') { // unless it's indeed a '%' + *pDest++ = *pFormat++; + } else { // first, collect the format + char format = '\0'; +#if defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || \ + defined(PICOFORMAT_HANDLE_HEX) + int bits_per_digit = + 0; // valid in 'b', 'o', 'p' and 'x'/'X' modes only +#endif + int whole_chars = + 0; // number of chars in the whole part of the number + int decimal_chars = + -1; // if not specified, %f are rendered with 6, while %g are rendered with 0 +#ifdef __aarch64__ // these platforms benefit from packing flags into a single variable + struct { + unsigned force_sign : 1; + unsigned fill_zeros : 1; + unsigned left_align : 1; + unsigned seen_period : 1; + unsigned seen_numbers : 1; + unsigned treat_as_unsigned : 1; + unsigned treat_as_long : 1; + unsigned render_in_lowercase : 1; + } flags = {0}; - #else // other platforms do not benefit from struct packing - unsigned force_sign = 0; - unsigned fill_zeros = 0; - #ifdef PICOFORMAT_HANDLE_FILL - unsigned left_align = 0; - #endif - unsigned seen_period = 0; - unsigned seen_numbers = 0; - unsigned treat_as_unsigned = 0; - unsigned treat_as_long = 0; - #if defined(PICOFORMAT_HANDLE_HEX) || defined(PICOFORMAT_HANDLE_FLOATS) - unsigned render_in_lowercase = 0; - #endif - #define FLAGS - #endif // struct packing platforms - for (; *pFormat && '\0' == format; pFormat++) { - switch (*pFormat) { - #ifdef PICOFORMAT_HANDLE_FORCEDSIGN - case '+': - FLAGS force_sign = 1; - break; - #endif // PICOFORMAT_HANDLE_FORCEDSIGN - #ifdef PICOFORMAT_HANDLE_FILL - case '-': // left-align flag - FLAGS left_align = 1; - break; - #endif // PICOFORMAT_HANDLE_FILL - case '0': - if (!FLAGS seen_numbers) { - FLAGS fill_zeros = 1; - break; - } - // fall through - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - *(FLAGS seen_period ? &decimal_chars : &whole_chars ) *= 10; - *(FLAGS seen_period ? &decimal_chars : &whole_chars) += *pFormat - '0'; - FLAGS seen_numbers = 1; - break; - case '.': - FLAGS seen_numbers = FLAGS seen_period = 1; - decimal_chars = 0; - break; - case '*': // dynamic width/precision: read value from arg list - *(FLAGS seen_period ? &decimal_chars : &whole_chars) = va_arg(vl, int); - FLAGS seen_numbers = 1; - break; - case 'l': // long modifier - FLAGS treat_as_long = 1; - break; - case 'u': // unsigned decimal integer - FLAGS treat_as_unsigned = 1; - format = 'd'; - break; - #ifdef PICOFORMAT_HANDLE_BIN - case 'b': - bits_per_digit = 0; - FLAGS treat_as_unsigned = 1; - format = 'b'; - break; - #endif // PICOFORMAT_HANDLE_BIN - #ifdef PICOFORMAT_HANDLE_OCT - case 'o': // octal integer - bits_per_digit = 3; - FLAGS treat_as_unsigned = 1; - format = 'b'; - break; - #endif // PICOFORMAT_HANDLE_OCT - #ifdef PICOFORMAT_HANDLE_HEX - case 'x': // hexadecimal integer - case 'p': // pointer - bits_per_digit = 4; - FLAGS treat_as_unsigned = 1; - FLAGS render_in_lowercase = 1; - format = 'b'; - break; - case 'X': // hexadecimal integer, uppercase - bits_per_digit = 4; - FLAGS treat_as_unsigned = 1; - format = 'b'; - break; - #endif // PICOFORMAT_HANDLE_HEX - #ifdef PICOFORMAT_HANDLE_FLOATS - case 'a': - #ifdef PICOFORMAT_HANDLE_EXPONENTS - case 'e': // floating point, exponent format - #endif // PICOFORMAT_HANDLE_EXPONENTS - case 'f': - FLAGS render_in_lowercase = 1; - // fall through - case 'F': - #endif // PICOFORMAT_HANDLE_FLOATS - case 'c': // single char: always supported - case 'd': // integer: always supported - case 'i': // integer: always supported - case 's': // string of native `char`s: always supported - format = *pFormat; - break; - default: - FORMAT_ERROR_DELEGATE("detected unhandled format specifier: %c", *pFormat); - break; - } - } + #define FLAGS flags. - // format is parsed, now render the value - char *pParamStarts = pDest; - switch (format) { - case 'c': // single char - *pDest++ = va_arg(vl, int) & 0xff; - break; - case 's': { // null-terminated string - #ifdef PICOFORMAT_HANDLE_FILL - int len = 0; // effective length, bounded by precision if set - const char *pStr = va_arg(vl, const char*); - for (; pStr[len] && (decimal_chars < 0 || len < decimal_chars); len++); - if (!FLAGS left_align) { // right-align: pad on the left - #ifdef PICOFORMAT_CLANG_QUIRK // clang's non-standard: '0' flag zero-pads strings - char chFill = FLAGS fill_zeros ? '0' : ' '; - #else // PICOFORMAT_CLANG_QUIRK // standard C: '0' flag is undefined for %s, use spaces - char chFill = ' '; - #endif // PICOFORMAT_CLANG_QUIRK - for (; pDest < pEnd && whole_chars > len; whole_chars--) { - *pDest++ = chFill; - } - } - for (int ii = 0; ii < len && pDest < pEnd; ii++) { - *pDest++ = pStr[ii]; - } - if (FLAGS left_align) { // left-align: pad on the right (always spaces; '-' flag overrides '0') - for (; pDest < pEnd && whole_chars > len; whole_chars--) { - *pDest++ = ' '; - } - } - #else // PICOFORMAT_HANDLE_FILL - for (const char *pStr = va_arg(vl, const char*); *pStr && pDest < pEnd; ) { - *pDest++ = *pStr++; - } - #endif // PICOFORMAT_HANDLE_FILL - } - break; - #if defined(PICOFORMAT_HANDLE_HEX) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_BIN) - case 'b': { // binary, oct, or hex integer, always unsigned - long long int val = 0; - if (FLAGS treat_as_long) { - val = va_arg(vl, unsigned long long int); - } else { - val = va_arg(vl, unsigned); - } - unsigned mask = bits_per_digit == 4 ? 0x0f : bits_per_digit == 3 ? 0x07 : 0x01; - #if defined(PICOFORMAT_HANDLE_HEX) - const char* chars = FLAGS render_in_lowercase ? pLowercaseNumberDigits : pUppercaseNumberDigits; - #elif defined(PICOFORMAT_HANDLE_OCT) - const char* chars = pOctalDigits; - #elif defined(PICOFORMAT_HANDLE_BIN) - const char* chars = pBinaryDigits; - #endif // individual non-decimal formats - while (pDest < pEnd - && (pDest == pParamStarts - || val - || pDest - pParamStarts < whole_chars)) { - char ch; - if (pDest == pParamStarts || 0 != val) { // if first char or there's still meaningful digits - ch = chars[val & mask]; - val >>= bits_per_digit; - } else { - ch = FLAGS fill_zeros ? '0' : ' '; - } - *pDest++ = ch; - } - flip(pParamStarts, pDest); - } - break; - #endif // defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_HEX) - case 'd': // decimal integer - case 'i': { - long long int val = 0; - if (FLAGS treat_as_long) { - if (FLAGS treat_as_unsigned) { - val = va_arg(vl, unsigned long long int); - } else { - val = va_arg(vl, long long int); - } - } else { - if (FLAGS treat_as_unsigned) { - val = va_arg(vl, unsigned); - } else { - val = va_arg(vl, int); - } - } - char chSign = '\0'; - if ((FLAGS force_sign || val < 0) && pDest < pEnd) { - chSign = val < 0 ? '-' : '+'; - val = llabs(val); - whole_chars--; - } - while (pDest < pEnd - && (pDest == pParamStarts - || val - || pDest - pParamStarts < whole_chars)) { - char ch; - if (pDest == pParamStarts || 0 != val) { // if first digit (i.e. zero) or there's still non-zero digits to write - ch = val % 10 + '0'; - val /= 10; - } else { - if ('\0' != chSign && !FLAGS fill_zeros) { // write a sign if filling with spaces, not with zeros - *pDest++ = chSign; - whole_chars++; - chSign = '\0'; // prevent the sign from being written twice - } - #ifdef PICOFORMAT_HANDLE_FILL - ch = FLAGS fill_zeros ? '0' : ' '; - #else // PICOFORMAT_HANDLE_FILL - break; - #endif // PICOFORMAT_HANDLE_FILL - } - *pDest++ = ch; - } - if ('\0' != chSign) { // write a sign if it wasn't written before - *pDest++ = chSign; - } - flip(pParamStarts, pDest); - } - break; - #ifdef PICOFORMAT_HANDLE_FLOATS - case 'f': case 'F': { - double val = va_arg(vl, double); - if (FLAGS force_sign || val < 0.f) { - *pDest++ = val < 0.f ? '-' : '+'; - val = fabs(val); - pParamStarts = pDest; - } - if (val == INFINITY || val == -INFINITY || isnan(val)) { - for (const char *pszVal = isnan(val) ? FLAGS render_in_lowercase ? "nan" : "NAN": FLAGS render_in_lowercase ? "inf" : "INF" - ; pDest < pEnd && *pszVal - ; *pDest++ = *pszVal++); - } else { - if (decimal_chars == -1) { - if (format == 'g') { - decimal_chars = 0; - } else { - decimal_chars = 6; - } - } - whole_chars = MAX(0, whole_chars - decimal_chars); - whole_chars = MAX(whole_chars, pDest - pParamStarts + 1); // at least sign (if present) and first char - for (float digit = 1.f - ; pDest < pEnd && (val >= digit || pDest - pParamStarts < whole_chars) - ; digit *= 10.f) { - char ch = (int)(val / digit) % 10 + '0'; - *pDest++ = ch; - } - flip(pParamStarts, pDest); - if (decimal_chars && pDest < pEnd) { - *pDest++ = '.'; - val += .5f * pow(10.f, -decimal_chars); // compensating for rounding error - for (size_t digit = 0; pDest < pEnd && digit < decimal_chars; digit++) { - val = (val - (int)(val)) * 10.f; - *pDest++ = (int)(val) + '0'; - } - } - } - } - break; - #endif // PICOFORMAT_HANDLE_FLOATS - } - } - } - } - *pDest = '\0'; - return pDest - pStart; +#else // other platforms do not benefit from struct packing + unsigned force_sign = 0; + unsigned fill_zeros = 0; + #ifdef PICOFORMAT_HANDLE_FILL + unsigned left_align = 0; + #endif + unsigned seen_period = 0; + unsigned seen_numbers = 0; + unsigned treat_as_unsigned = 0; + unsigned treat_as_long = 0; + #if defined(PICOFORMAT_HANDLE_HEX) || defined(PICOFORMAT_HANDLE_FLOATS) + unsigned render_in_lowercase = 0; + #endif + #define FLAGS +#endif // struct packing platforms + for (; *pFormat && '\0' == format; pFormat++) { + switch (*pFormat) { +#ifdef PICOFORMAT_HANDLE_FORCEDSIGN + case '+': + FLAGS force_sign = 1; + break; +#endif // PICOFORMAT_HANDLE_FORCEDSIGN +#ifdef PICOFORMAT_HANDLE_FILL + case '-': // left-align flag + FLAGS left_align = 1; + break; +#endif // PICOFORMAT_HANDLE_FILL + case '0': + if (!FLAGS seen_numbers) { + FLAGS fill_zeros = 1; + break; + } + // fall through + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + *(FLAGS seen_period ? &decimal_chars : + &whole_chars) *= 10; + *(FLAGS seen_period ? &decimal_chars : + &whole_chars) += + *pFormat - '0'; + FLAGS seen_numbers = 1; + break; + case '.': + FLAGS seen_numbers = FLAGS seen_period = + 1; + decimal_chars = 0; + break; + case '*': // dynamic width/precision: read value from arg list + *(FLAGS seen_period ? &decimal_chars : + &whole_chars) = + va_arg(vl, int); + FLAGS seen_numbers = 1; + break; + case 'l': // long modifier + FLAGS treat_as_long = 1; + break; + case 'u': // unsigned decimal integer + FLAGS treat_as_unsigned = 1; + format = 'd'; + break; +#ifdef PICOFORMAT_HANDLE_BIN + case 'b': + bits_per_digit = 0; + FLAGS treat_as_unsigned = 1; + format = 'b'; + break; +#endif // PICOFORMAT_HANDLE_BIN +#ifdef PICOFORMAT_HANDLE_OCT + case 'o': // octal integer + bits_per_digit = 3; + FLAGS treat_as_unsigned = 1; + format = 'b'; + break; +#endif // PICOFORMAT_HANDLE_OCT +#ifdef PICOFORMAT_HANDLE_HEX + case 'x': // hexadecimal integer + case 'p': // pointer + bits_per_digit = 4; + FLAGS treat_as_unsigned = 1; + FLAGS render_in_lowercase = 1; + format = 'b'; + break; + case 'X': // hexadecimal integer, uppercase + bits_per_digit = 4; + FLAGS treat_as_unsigned = 1; + format = 'b'; + break; +#endif // PICOFORMAT_HANDLE_HEX +#ifdef PICOFORMAT_HANDLE_FLOATS + case 'a': + #ifdef PICOFORMAT_HANDLE_EXPONENTS + case 'e': // floating point, exponent format + #endif // PICOFORMAT_HANDLE_EXPONENTS + case 'f': + FLAGS render_in_lowercase = 1; + // fall through + case 'F': +#endif // PICOFORMAT_HANDLE_FLOATS + case 'c': // single char: always supported + case 'd': // integer: always supported + case 'i': // integer: always supported + case 's': // string of native `char`s: always supported + format = *pFormat; + break; + default: + FORMAT_ERROR_DELEGATE( + "detected unhandled format specifier: %c", + *pFormat); + break; + } + } + + // format is parsed, now render the value + char* pParamStarts = pDest; + switch (format) { + case 'c': // single char + *pDest++ = va_arg(vl, int) & 0xff; + break; + case 's': { // null-terminated string +#ifdef PICOFORMAT_HANDLE_FILL + int len = + 0; // effective length, bounded by precision if set + const char* pStr = va_arg(vl, const char*); + for (; pStr[len] && + (decimal_chars < 0 || len < decimal_chars); + len++) + ; + if (!FLAGS left_align) { // right-align: pad on the left + #ifdef PICOFORMAT_CLANG_QUIRK // clang's non-standard: '0' flag zero-pads strings + char chFill = + FLAGS fill_zeros ? '0' : ' '; + #else // PICOFORMAT_CLANG_QUIRK // standard C: '0' flag is undefined for %s, use spaces + char chFill = ' '; + #endif // PICOFORMAT_CLANG_QUIRK + for (; pDest < pEnd && whole_chars > len; + whole_chars--) { + *pDest++ = chFill; + } + } + for (int ii = 0; ii < len && pDest < pEnd; ii++) { + *pDest++ = pStr[ii]; + } + if (FLAGS left_align) { // left-align: pad on the right (always spaces; '-' flag overrides '0') + for (; pDest < pEnd && whole_chars > len; + whole_chars--) { + *pDest++ = ' '; + } + } +#else // PICOFORMAT_HANDLE_FILL + for (const char* pStr = va_arg(vl, const char*); + *pStr && pDest < pEnd;) { + *pDest++ = *pStr++; + } +#endif // PICOFORMAT_HANDLE_FILL + } break; +#if defined(PICOFORMAT_HANDLE_HEX) || defined(PICOFORMAT_HANDLE_OCT) || \ + defined(PICOFORMAT_HANDLE_BIN) + case 'b': { // binary, oct, or hex integer, always unsigned + long long int val = 0; + if (FLAGS treat_as_long) { + val = va_arg(vl, unsigned long long int); + } else { + val = va_arg(vl, unsigned); + } + unsigned mask = bits_per_digit == 4 ? 0x0f : + bits_per_digit == 3 ? 0x07 : + 0x01; + #if defined(PICOFORMAT_HANDLE_HEX) + const char* chars = FLAGS render_in_lowercase ? + pLowercaseNumberDigits : + pUppercaseNumberDigits; + #elif defined(PICOFORMAT_HANDLE_OCT) + const char* chars = pOctalDigits; + #elif defined(PICOFORMAT_HANDLE_BIN) + const char* chars = pBinaryDigits; + #endif // individual non-decimal formats + while (pDest < pEnd && + (pDest == pParamStarts || val || + pDest - pParamStarts < whole_chars)) { + char ch; + if (pDest == pParamStarts || + 0 != val) { // if first char or there's still meaningful digits + ch = chars[val & mask]; + val >>= bits_per_digit; + } else { + ch = FLAGS fill_zeros ? '0' : ' '; + } + *pDest++ = ch; + } + flip(pParamStarts, pDest); + } break; +#endif // defined(PICOFORMAT_HANDLE_BIN) || defined(PICOFORMAT_HANDLE_OCT) || defined(PICOFORMAT_HANDLE_HEX) + case 'd': // decimal integer + case 'i': { + long long int val = 0; + if (FLAGS treat_as_long) { + if (FLAGS treat_as_unsigned) { + val = va_arg( + vl, + unsigned long long int); + } else { + val = va_arg(vl, long long int); + } + } else { + if (FLAGS treat_as_unsigned) { + val = va_arg(vl, unsigned); + } else { + val = va_arg(vl, int); + } + } + char chSign = '\0'; + if ((FLAGS force_sign || val < 0) && + pDest < pEnd) { + chSign = val < 0 ? '-' : '+'; + val = llabs(val); + whole_chars--; + } + while (pDest < pEnd && + (pDest == pParamStarts || val || + pDest - pParamStarts < whole_chars)) { + char ch; + if (pDest == pParamStarts || + 0 != val) { // if first digit (i.e. zero) or there's still non-zero digits to write + ch = val % 10 + '0'; + val /= 10; + } else { + if ('\0' != chSign && + !FLAGS fill_zeros) { // write a sign if filling with spaces, not with zeros + *pDest++ = chSign; + whole_chars++; + chSign = + '\0'; // prevent the sign from being written twice + } +#ifdef PICOFORMAT_HANDLE_FILL + ch = FLAGS fill_zeros ? '0' : ' '; +#else // PICOFORMAT_HANDLE_FILL + break; +#endif // PICOFORMAT_HANDLE_FILL + } + *pDest++ = ch; + } + if ('\0' != + chSign) { // write a sign if it wasn't written before + *pDest++ = chSign; + } + flip(pParamStarts, pDest); + } break; +#ifdef PICOFORMAT_HANDLE_FLOATS + case 'f': + case 'F': { + double val = va_arg(vl, double); + if (FLAGS force_sign || val < 0.f) { + *pDest++ = val < 0.f ? '-' : '+'; + val = fabs(val); + pParamStarts = pDest; + } + if (val == INFINITY || val == -INFINITY || + isnan(val)) { + for (const char* pszVal = isnan(val) ? + FLAGS render_in_lowercase ? + "nan" : + "NAN" : + FLAGS render_in_lowercase ? + "inf" : + "INF"; + pDest < pEnd && *pszVal; + *pDest++ = *pszVal++) + ; + } else { + if (decimal_chars == -1) { + if (format == 'g') { + decimal_chars = 0; + } else { + decimal_chars = 6; + } + } + whole_chars = + MAX(0, + whole_chars - decimal_chars); + whole_chars = MAX( + whole_chars, + pDest - pParamStarts + + 1); // at least sign (if present) and first char + for (float digit = 1.f; pDest < pEnd && + (val >= digit || + pDest - pParamStarts < whole_chars); + digit *= 10.f) { + char ch = + (int) (val / digit) % 10 + + '0'; + *pDest++ = ch; + } + flip(pParamStarts, pDest); + if (decimal_chars && pDest < pEnd) { + *pDest++ = '.'; + val += .5f * + pow(10.f, + -decimal_chars); // compensating for rounding error + for (size_t digit = 0; + pDest < pEnd && + digit < decimal_chars; + digit++) { + val = (val - + (int) (val)) * + 10.f; + *pDest++ = + (int) (val) + '0'; + } + } + } + } break; +#endif // PICOFORMAT_HANDLE_FLOATS + } + } + } + } + *pDest = '\0'; + return pDest - pStart; } - -int pico_snprintf(char *pDest, size_t cbDest, const char *pFormat, ...) { - va_list vl; - va_start(vl, pFormat); - int result = pico_vsnprintf(pDest, cbDest, pFormat, vl); - va_end(vl); - return result; +int pico_snprintf(char* pDest, size_t cbDest, const char* pFormat, ...) +{ + va_list vl; + va_start(vl, pFormat); + int result = pico_vsnprintf(pDest, cbDest, pFormat, vl); + va_end(vl); + return result; } diff --git a/firmware/common/picoprintf.h b/firmware/common/picoprintf.h index 0fb5438b..aadb27ea 100644 --- a/firmware/common/picoprintf.h +++ b/firmware/common/picoprintf.h @@ -22,23 +22,22 @@ #pragma once -#include // size_t -#include // va_* +#include // size_t +#include // va_* - -int pico_snprintf(char *pDest, size_t cbDest, const char *pFormat, ...); -int pico_vsnprintf(char *pDest, size_t cbDest, const char *pFormat, va_list vl); +int pico_snprintf(char* pDest, size_t cbDest, const char* pFormat, ...); +int pico_vsnprintf(char* pDest, size_t cbDest, const char* pFormat, va_list vl); // PLEASE use `pico_snprintf()` instead!!! This function is vulnerable to buffer overflows -inline int pico_sprintf(char *pDest, const char *pFormat, ...) { - va_list vl; - va_start(vl, pFormat); - int result = pico_vsnprintf(pDest, -1, pFormat, vl); - va_end(vl); - return result; +inline int pico_sprintf(char* pDest, const char* pFormat, ...) +{ + va_list vl; + va_start(vl, pFormat); + int result = pico_vsnprintf(pDest, -1, pFormat, vl); + va_end(vl); + return result; } - // // IMPORTANT!!! // comment/uncomment the following lines corresponding to the features you need @@ -51,12 +50,13 @@ inline int pico_sprintf(char *pDest, const char *pFormat, ...) { // #define PICOFORMAT_HANDLE_FLOATS // uncomment this line to handle the "%f" // #define PICOFORMAT_CLANG_QUIRK // uncomment this line to match clang's non-standard "%010s" behavior (zero-pad strings when both '0' flag and width are set) - // by default, the debug builds (determined by `#define _DEBUG`) will real-time print errors when a feature is used that is not enabled above #ifndef FORMAT_ERROR_DELEGATE - #ifdef _DEBUG - #define FORMAT_ERROR_DELEGATE(__message, __arg) printf(__message, __arg); printf("\n"); - #else // _DEBUG - #define FORMAT_ERROR_DELEGATE(__message, __arg) - #endif // _DEBUG -#endif // FORMAT_ERROR_DELEGATE + #ifdef _DEBUG + #define FORMAT_ERROR_DELEGATE(__message, __arg) \ + printf(__message, __arg); \ + printf("\n"); + #else // _DEBUG + #define FORMAT_ERROR_DELEGATE(__message, __arg) + #endif // _DEBUG +#endif // FORMAT_ERROR_DELEGATE From ec1ff486b44e3c7f9a69aa293b0f5a6642047087 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 28 Apr 2026 15:22:06 +0100 Subject: [PATCH 102/161] picoprintf: Enable additional features. --- firmware/common/picoprintf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/common/picoprintf.h b/firmware/common/picoprintf.h index aadb27ea..313ee91d 100644 --- a/firmware/common/picoprintf.h +++ b/firmware/common/picoprintf.h @@ -42,11 +42,11 @@ inline int pico_sprintf(char* pDest, const char* pFormat, ...) // IMPORTANT!!! // comment/uncomment the following lines corresponding to the features you need // -// #define PICOFORMAT_HANDLE_FILL // uncomment this line to handle "%6i" and "%04d" -- the fill with zeroes or spaces +#define PICOFORMAT_HANDLE_FILL // uncomment this line to handle "%6i" and "%04d" -- the fill with zeroes or spaces // #define PICOFORMAT_HANDLE_FORCEDSIGN // uncomment this line to handle "%+d" -- the forced sign placement // #define PICOFORMAT_HANDLE_BIN // uncomment this line to handle "%b" -- binary representation // #define PICOFORMAT_HANDLE_OCT // uncomment this line to handle "%o" -// #define PICOFORMAT_HANDLE_HEX // uncomment this line to handle "%x" and "%X" +#define PICOFORMAT_HANDLE_HEX // uncomment this line to handle "%x" and "%X" // #define PICOFORMAT_HANDLE_FLOATS // uncomment this line to handle the "%f" // #define PICOFORMAT_CLANG_QUIRK // uncomment this line to match clang's non-standard "%010s" behavior (zero-pad strings when both '0' flag and width are set) From f73c2b8e6a4ab1666fa387e82994fe24f2bfb842 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 30 Apr 2026 14:44:29 +0100 Subject: [PATCH 103/161] rad1o: Add formatting support to rad1o_lcdPrint. --- firmware/common/rad1o/print.c | 18 ++++++++++++++++-- firmware/common/rad1o/print.h | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/firmware/common/rad1o/print.c b/firmware/common/rad1o/print.c index d279f4e7..345af5e0 100644 --- a/firmware/common/rad1o/print.c +++ b/firmware/common/rad1o/print.c @@ -1,5 +1,11 @@ #include "print.h" +#include +#include +#include + +#include + #include "display.h" #include "render.h" #include "smallfonts.h" @@ -7,9 +13,17 @@ static int32_t x = 0; static int32_t y = 0; -void rad1o_lcdPrint(const char* string) +void rad1o_lcdPrint(const char* fmt, ...) { - x = rad1o_DoString(x, y, string); + va_list args1, args2; + va_start(args1, fmt); + va_copy(args2, args1); + int buflen = pico_vsnprintf(NULL, 0, fmt, args1) + 1; + va_end(args1); + char* buf = alloca(buflen); + pico_vsnprintf(buf, buflen, fmt, args2); + va_end(args2); + x = rad1o_DoString(x, y, buf); } void rad1o_lcdNl(void) diff --git a/firmware/common/rad1o/print.h b/firmware/common/rad1o/print.h index 8981da25..858f1b22 100644 --- a/firmware/common/rad1o/print.h +++ b/firmware/common/rad1o/print.h @@ -2,7 +2,7 @@ #include -void rad1o_lcdPrint(const char* string); +void rad1o_lcdPrint(const char* string, ...); void rad1o_lcdNl(void); void rad1o_lcdClear(void); void rad1o_lcdMoveCrsr(int32_t dx, int32_t dy); From 9688caaf1c3aa0de92d6bac6d611af46bbaa73ff Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 30 Apr 2026 14:45:27 +0100 Subject: [PATCH 104/161] ui_rad1o: Eliminate use of sprintf. --- firmware/common/ui_rad1o.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index 8df630bd..a157b6ef 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -24,7 +24,6 @@ #include #include -#include #include "clock_gen.h" #include "drivers.h" @@ -60,22 +59,20 @@ static bool enabled = false; #define DIV_ROUND_CLOSEST(n, d) ((n + (d / 2)) / d) -static void print_hz(char* tmp, char* fmt, uint64_t hz) +static void print_hz(char* fmt, uint64_t hz) { uint32_t mhz, khz; mhz = DIV_ROUND_CLOSEST(hz, 1000000); hz -= mhz * 1000000; khz = DIV_ROUND_CLOSEST(hz, 1000); - sprintf(tmp, fmt, (unsigned int) mhz, (unsigned int) khz); - rad1o_lcdPrint(tmp); + rad1o_lcdPrint(fmt, (unsigned int) mhz, (unsigned int) khz); } static void draw_frequency(void) { - char tmp[100]; rad1o_setTextColor(BLACK, GREEN); rad1o_setIntFont(&Font_Ubuntu18pt); - print_hz(tmp, "%4u.%03u", freq); + print_hz("%4u.%03u", freq); rad1o_setIntFont(&Font_7x8); rad1o_lcdMoveCrsr(1, 18 - 7); rad1o_lcdPrint("MHz"); @@ -115,8 +112,6 @@ static void draw_tx_rx(void) static void ui_update(void) { - char tmp[100]; - if (!enabled) { return; } @@ -156,12 +151,12 @@ static void ui_update(void) rad1o_setTextColor(BLACK, WHITE); rad1o_lcdSetCrsr(2, 71); rad1o_lcdPrint("Rate: "); - print_hz(tmp, "%2u.%03u MHz", sample_rate); + print_hz("%2u.%03u MHz", sample_rate); rad1o_lcdNl(); rad1o_lcdMoveCrsr(2, 0); rad1o_lcdPrint("Filter: "); - print_hz(tmp, "%2u.%03u MHz", filter_bw); + print_hz("%2u.%03u MHz", filter_bw); rad1o_lcdNl(); rad1o_drawHLine(88, 0, RESX - 1, WHITE); @@ -185,8 +180,7 @@ static void ui_update(void) if (direction == RF_PATH_DIRECTION_TX) { rad1o_setTextColor(BLACK, RED); } - sprintf(tmp, " TX: %u dB", (unsigned int) bbtxvga_gain); - rad1o_lcdPrint(tmp); + rad1o_lcdPrint(" TX: %u dB", (unsigned int) bbtxvga_gain); rad1o_lcdNl(); rad1o_lcdMoveCrsr(2, 0); @@ -194,12 +188,10 @@ static void ui_update(void) if (direction == RF_PATH_DIRECTION_RX) { rad1o_setTextColor(BLACK, GREEN); } - sprintf(tmp, "LNA: %2u dB", (unsigned int) bblna_gain); - rad1o_lcdPrint(tmp); + rad1o_lcdPrint("LNA: %2u dB", (unsigned int) bblna_gain); rad1o_lcdNl(); rad1o_lcdMoveCrsr(2, 0); - sprintf(tmp, "VGA: %2u dB", (unsigned int) bbvga_gain); - rad1o_lcdPrint(tmp); + rad1o_lcdPrint("VGA: %2u dB", (unsigned int) bbvga_gain); rad1o_lcdNl(); rad1o_lcdDisplay(); From 90ab84ba28be0e32b99af42e663d78f0445ff1a8 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 16:24:25 +0200 Subject: [PATCH 105/161] firmware: move jtag_cpld driver instance to cpld_jtag.x --- firmware/common/cpld_jtag.c | 7 +++++++ firmware/common/cpld_jtag.h | 4 ++++ firmware/common/drivers.c | 6 ------ firmware/common/drivers.h | 3 --- firmware/common/pins.c | 3 ++- firmware/common/portapack.c | 1 - firmware/hackrf-common.cmake | 1 + firmware/hackrf_usb/CMakeLists.txt | 1 - firmware/hackrf_usb/hackrf_usb.c | 1 - firmware/hackrf_usb/usb_api_cpld.c | 1 - 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 034ba196..92f89f27 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -34,6 +34,13 @@ static uint32_t xsvf_buffer_len, xsvf_pos; static unsigned char* xsvf_buffer; #endif +/* Driver instance. */ +jtag_gpio_t jtag_gpio_cpld = {}; + +jtag_t jtag_cpld = { + .gpio = &jtag_gpio_cpld, +}; + void cpld_jtag_take(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index 0994c858..14f70437 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -59,3 +59,7 @@ int cpld_jtag_program( unsigned char* const buffer, refill_buffer_cb refill); unsigned char cpld_jtag_get_next_byte(void); + +/* Driver instance. */ +extern jtag_gpio_t jtag_gpio_cpld; +extern jtag_t jtag_cpld; diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index 58b30cb2..1444534c 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -45,12 +45,6 @@ fpga_driver_t fpga = { }; #endif -jtag_gpio_t jtag_gpio_cpld = {}; - -jtag_t jtag_cpld = { - .gpio = &jtag_gpio_cpld, -}; - #ifdef IS_PRALINE void ssp1_set_mode_ice40(void) { diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index d0427e93..1fe7d72c 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -25,7 +25,6 @@ extern "C" { #endif -#include "cpld_jtag.h" #include "platform_detect.h" // IWYU pragma: keep #if defined(IS_PRALINE) #include "fpga.h" @@ -38,8 +37,6 @@ extern ssp_config_t ssp_config_ice40_fpga; extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif -extern jtag_gpio_t jtag_gpio_cpld; -extern jtag_t jtag_cpld; #if defined(IS_PRALINE) void ssp1_set_mode_ice40(void); diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 24aeb033..f695d1ef 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -26,7 +26,7 @@ #include #include -#include "drivers.h" +#include "cpld_jtag.h" #include "gpio.h" #include "leds.h" #include "max283x.h" @@ -41,6 +41,7 @@ #include "w25q80bv.h" #if defined(IS_PRALINE) #include "clock_io.h" + #include "drivers.h" #endif void pins_shutdown(void) diff --git a/firmware/common/portapack.c b/firmware/common/portapack.c index f25a4a6e..d3d848fc 100644 --- a/firmware/common/portapack.c +++ b/firmware/common/portapack.c @@ -26,7 +26,6 @@ #include "cpld_jtag.h" #include "delay.h" -#include "drivers.h" #include "gpio.h" #include "gpio_lpc.h" #include "platform_gpio.h" diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index 04d8b1a3..d31cd6c0 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -172,6 +172,7 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/power.c ${PATH_HACKRF_FIRMWARE_COMMON}/cpu_clock.c ${PATH_HACKRF_FIRMWARE_COMMON}/delay.c + ${PATH_HACKRF_FIRMWARE_COMMON}/cpld_jtag.c ${PATH_HACKRF_FIRMWARE_COMMON}/pins.c ${PATH_HACKRF_FIRMWARE_COMMON}/leds.c ${PATH_HACKRF_FIRMWARE_COMMON}/sgpio.c diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index 3fd3ea87..fe392f49 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -66,7 +66,6 @@ set(SRC_M4 usb_api_adc.c "${PATH_HACKRF_FIRMWARE_COMMON}/usb_queue.c" "${PATH_HACKRF_FIRMWARE_COMMON}/fault_handler.c" - "${PATH_HACKRF_FIRMWARE_COMMON}/cpld_jtag.c" "${PATH_HACKRF_FIRMWARE_COMMON}/crc.c" "${PATH_HACKRF_FIRMWARE_COMMON}/rom_iap.c" "${PATH_HACKRF_FIRMWARE_COMMON}/operacake.c" diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 031f2001..7419d063 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -70,7 +70,6 @@ #ifdef IS_NOT_PRALINE #include #include - #include #endif #include "usb_api_adc.h" diff --git a/firmware/hackrf_usb/usb_api_cpld.c b/firmware/hackrf_usb/usb_api_cpld.c index ed2beeda..391b9366 100644 --- a/firmware/hackrf_usb/usb_api_cpld.c +++ b/firmware/hackrf_usb/usb_api_cpld.c @@ -29,7 +29,6 @@ #include #include -#include #include #include #include From d0f61abcd791a792428b765a3c01540acc28ecbb Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 16:31:14 +0200 Subject: [PATCH 106/161] firmware: move ice40 driver instance to ice40_spi.x --- firmware/common/drivers.c | 20 -------------------- firmware/common/drivers.h | 8 -------- firmware/common/fpga.c | 1 - firmware/common/fpga_image.c | 1 - firmware/common/ice40_spi.c | 17 +++++++++++++++++ firmware/common/ice40_spi.h | 6 ++++++ firmware/common/pins.c | 2 +- 7 files changed, 24 insertions(+), 31 deletions(-) diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index 1444534c..65ca10a9 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -22,32 +22,12 @@ #include "drivers.h" #if defined(IS_PRALINE) - #include #include "fpga.h" #include "ice40_spi.h" - #include "spi_bus.h" #endif #ifdef IS_PRALINE -ssp_config_t ssp_config_ice40_fpga = { - .data_bits = SSP_DATA_8BITS, - .spi_mode = SSP_CPOL_1_CPHA_1, - .serial_clock_rate = 21, - .clock_prescale_rate = 2, -}; - -ice40_spi_driver_t ice40 = { - .bus = &spi_bus_ssp1, -}; - fpga_driver_t fpga = { .bus = &ice40, }; #endif - -#ifdef IS_PRALINE -void ssp1_set_mode_ice40(void) -{ - spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); -} -#endif diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 1fe7d72c..1128a2dc 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -28,20 +28,12 @@ extern "C" { #include "platform_detect.h" // IWYU pragma: keep #if defined(IS_PRALINE) #include "fpga.h" - #include "ice40_spi.h" - #include "spi_ssp.h" #endif #if defined(IS_PRALINE) -extern ssp_config_t ssp_config_ice40_fpga; -extern ice40_spi_driver_t ice40; extern fpga_driver_t fpga; #endif -#if defined(IS_PRALINE) -void ssp1_set_mode_ice40(void); -#endif - #ifdef __cplusplus } #endif diff --git a/firmware/common/fpga.c b/firmware/common/fpga.c index 3bdd636e..b339fc15 100644 --- a/firmware/common/fpga.c +++ b/firmware/common/fpga.c @@ -24,7 +24,6 @@ #include -#include "drivers.h" #include "ice40_spi.h" #include "max283x.h" diff --git a/firmware/common/fpga_image.c b/firmware/common/fpga_image.c index a9f46aee..3ebadad8 100644 --- a/firmware/common/fpga_image.c +++ b/firmware/common/fpga_image.c @@ -23,7 +23,6 @@ #include #include -#include "drivers.h" #include "fpga.h" #include "ice40_spi.h" #include "lz4_blk.h" diff --git a/firmware/common/ice40_spi.c b/firmware/common/ice40_spi.c index c8364b8a..2685092a 100644 --- a/firmware/common/ice40_spi.c +++ b/firmware/common/ice40_spi.c @@ -28,6 +28,23 @@ #include "delay.h" #include "platform_scu.h" +/* Driver instance. */ +ssp_config_t ssp_config_ice40_fpga = { + .data_bits = SSP_DATA_8BITS, + .spi_mode = SSP_CPOL_1_CPHA_1, + .serial_clock_rate = 21, + .clock_prescale_rate = 2, +}; + +ice40_spi_driver_t ice40 = { + .bus = &spi_bus_ssp1, +}; + +void ssp1_set_mode_ice40(void) +{ + spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); +} + void ice40_spi_target_init(ice40_spi_driver_t* const drv) { const platform_scu_t* scu = platform_scu(); diff --git a/firmware/common/ice40_spi.h b/firmware/common/ice40_spi.h index 4ceddffa..a1362033 100644 --- a/firmware/common/ice40_spi.h +++ b/firmware/common/ice40_spi.h @@ -27,6 +27,7 @@ #include "gpio.h" #include "spi_bus.h" +#include "spi_ssp.h" typedef struct { spi_bus_t* const bus; @@ -43,3 +44,8 @@ bool ice40_spi_syscfg_program( uint8_t* buf, size_t (*read_block_cb)(void* ctx), void* read_ctx); + +/* Driver instance. */ +extern ssp_config_t ssp_config_ice40_fpga; +extern ice40_spi_driver_t ice40; +void ssp1_set_mode_ice40(void); diff --git a/firmware/common/pins.c b/firmware/common/pins.c index f695d1ef..c51de383 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -41,7 +41,7 @@ #include "w25q80bv.h" #if defined(IS_PRALINE) #include "clock_io.h" - #include "drivers.h" + #include "ice40_spi.h" #endif void pins_shutdown(void) From 8569c1dfd26d55f4f2e6df6b4265ef11018c9e60 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 16:35:44 +0200 Subject: [PATCH 107/161] firmware: move fpga object instance to fpga.x --- firmware/common/clock_io.c | 1 - firmware/common/drivers.c | 11 ----------- firmware/common/drivers.h | 9 --------- firmware/common/fpga.c | 5 +++++ firmware/common/fpga.h | 3 ++- firmware/common/fpga_selftest.c | 1 - firmware/common/radio.c | 1 - firmware/hackrf_usb/usb_api_register.c | 1 - 8 files changed, 7 insertions(+), 25 deletions(-) diff --git a/firmware/common/clock_io.c b/firmware/common/clock_io.c index c68d470f..b0904f89 100644 --- a/firmware/common/clock_io.c +++ b/firmware/common/clock_io.c @@ -36,7 +36,6 @@ #include "sgpio.h" #endif #ifdef IS_PRALINE - #include "drivers.h" #include "fpga.h" #include "platform_gpio.h" #endif diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c index 65ca10a9..739f9b1f 100644 --- a/firmware/common/drivers.c +++ b/firmware/common/drivers.c @@ -20,14 +20,3 @@ */ #include "drivers.h" - -#if defined(IS_PRALINE) - #include "fpga.h" - #include "ice40_spi.h" -#endif - -#ifdef IS_PRALINE -fpga_driver_t fpga = { - .bus = &ice40, -}; -#endif diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h index 1128a2dc..0071a0be 100644 --- a/firmware/common/drivers.h +++ b/firmware/common/drivers.h @@ -25,15 +25,6 @@ extern "C" { #endif -#include "platform_detect.h" // IWYU pragma: keep -#if defined(IS_PRALINE) - #include "fpga.h" -#endif - -#if defined(IS_PRALINE) -extern fpga_driver_t fpga; -#endif - #ifdef __cplusplus } #endif diff --git a/firmware/common/fpga.c b/firmware/common/fpga.c index b339fc15..f68b6525 100644 --- a/firmware/common/fpga.c +++ b/firmware/common/fpga.c @@ -27,6 +27,11 @@ #include "ice40_spi.h" #include "max283x.h" +/* Driver instance. */ +fpga_driver_t fpga = { + .bus = &ice40, +}; + /* Set up all registers according to the loaded bitstream's defaults. */ void fpga_init(fpga_driver_t* const drv) { diff --git a/firmware/common/fpga.h b/firmware/common/fpga.h index 65826b21..1765fcdd 100644 --- a/firmware/common/fpga.h +++ b/firmware/common/fpga.h @@ -91,4 +91,5 @@ bool fpga_spi_selftest(void); bool fpga_sgpio_selftest(void); bool fpga_if_xcvr_selftest(void); -//void narrowband_filter_set(const uint8_t value); +/* Driver instance. */ +extern fpga_driver_t fpga; diff --git a/firmware/common/fpga_selftest.c b/firmware/common/fpga_selftest.c index 30e70ea2..c51dd008 100644 --- a/firmware/common/fpga_selftest.c +++ b/firmware/common/fpga_selftest.c @@ -25,7 +25,6 @@ #include "clock_gen.h" #include "delay.h" -#include "drivers.h" #include "fixed_point.h" #include "fpga.h" #include "ice40_spi.h" diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 17b7486c..eb01b5de 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -34,7 +34,6 @@ #include "transceiver_mode.h" #include "tuning.h" #ifdef IS_PRALINE - #include "drivers.h" #include "fpga.h" #include "tune_config.h" #endif diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index 88a4de53..30d1d493 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -36,7 +36,6 @@ #include #include #ifdef IS_PRALINE - #include #include #endif #ifdef IS_NOT_RAD1O From 8bdefc2d899e4a26cca2c37b30faf0b454ad5f20 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 30 Apr 2026 16:38:10 +0200 Subject: [PATCH 108/161] firmware: remove drivers.x --- firmware/common/drivers.c | 22 ---------------------- firmware/common/drivers.h | 30 ------------------------------ firmware/hackrf-common.cmake | 1 - 3 files changed, 53 deletions(-) delete mode 100644 firmware/common/drivers.c delete mode 100644 firmware/common/drivers.h diff --git a/firmware/common/drivers.c b/firmware/common/drivers.c deleted file mode 100644 index 739f9b1f..00000000 --- a/firmware/common/drivers.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2026 Great Scott Gadgets - * - * This file is part of HackRF. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, - * Boston, MA 02110-1301, USA. - */ - -#include "drivers.h" diff --git a/firmware/common/drivers.h b/firmware/common/drivers.h deleted file mode 100644 index 0071a0be..00000000 --- a/firmware/common/drivers.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2026 Great Scott Gadgets - * - * This file is part of HackRF. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, - * Boston, MA 02110-1301, USA. - */ - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __cplusplus -} -#endif diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index d31cd6c0..eb1d5716 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -168,7 +168,6 @@ endmacro() macro(DeclareTargets) SET(SRC_M4 ${SRC_M4} - ${PATH_HACKRF_FIRMWARE_COMMON}/drivers.c ${PATH_HACKRF_FIRMWARE_COMMON}/power.c ${PATH_HACKRF_FIRMWARE_COMMON}/cpu_clock.c ${PATH_HACKRF_FIRMWARE_COMMON}/delay.c From 9c81954b1b31cd7166f0d8df345c24803a9b4c06 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Fri, 24 Apr 2026 10:43:00 +0200 Subject: [PATCH 109/161] firmware: move platform preprocessor definitions to cmake --- firmware/common/platform_detect.h | 62 ---------------------- firmware/hackrf-common.cmake | 2 + firmware/platform-detect.cmake | 87 +++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 62 deletions(-) create mode 100644 firmware/platform-detect.cmake diff --git a/firmware/common/platform_detect.h b/firmware/common/platform_detect.h index 0331fe9c..4f0f0f43 100644 --- a/firmware/common/platform_detect.h +++ b/firmware/common/platform_detect.h @@ -21,7 +21,6 @@ #pragma once -#include #include #define BOARD_REV_GSG (0x80) @@ -32,67 +31,6 @@ #define PLATFORM_HACKRF1_R9 (1 << 3) #define PLATFORM_PRALINE (1 << 4) -/* clang-format off */ - -/* Helper macros for platform-specific code. */ -#if defined(UNIVERSAL) - #define IS_PRALINE (detected_platform() == BOARD_ID_PRALINE) - #define IS_NOT_PRALINE (!IS_PRALINE) - #define IS_HACKRF_ONE ( \ - detected_platform() == BOARD_ID_HACKRF1_OG || \ - detected_platform() == BOARD_ID_HACKRF1_R9 \ - ) - #define IS_NOT_HACKRF_ONE (!IS_HACKRF_ONE) - #define IS_H1_R9 (detected_platform() == BOARD_ID_HACKRF1_R9) - #define IS_NOT_H1_R9 (!IS_H1_R9) - #define IS_NOT_RAD1O true - #define IS_NOT_JAWBREAKER true - #define IS_H1_OR_PRALINE true - #define IS_H1_OR_RAD1O IS_HACKRF_ONE - #define IS_H1_OR_JAWBREAKER IS_HACKRF_ONE - #define IS_FOUR_LEDS IS_PRALINE - #define IS_EXPANSION_COMPATIBLE true -#elif defined(HACKRF_ONE) - #define IS_NOT_PRALINE true - #define IS_HACKRF_ONE true - #define IS_H1_R9 (detected_platform() == BOARD_ID_HACKRF1_R9) - #define IS_NOT_H1_R9 (!IS_H1_R9) - #define IS_NOT_RAD1O true - #define IS_NOT_JAWBREAKER true - #define IS_H1_OR_PRALINE true - #define IS_H1_OR_RAD1O true - #define IS_H1_OR_JAWBREAKER true - #define IS_EXPANSION_COMPATIBLE true -#elif defined(PRALINE) - #define IS_PRALINE true - #define IS_NOT_HACKRF_ONE true - #define IS_NOT_H1_R9 true - #define IS_NOT_RAD1O true - #define IS_NOT_JAWBREAKER true - #define IS_H1_OR_PRALINE true - #define IS_FOUR_LEDS true - #define IS_EXPANSION_COMPATIBLE true -#elif defined(RAD1O) - #define IS_NOT_PRALINE true - #define IS_NOT_HACKRF_ONE true - #define IS_NOT_H1_R9 true - #define IS_RAD1O true - #define IS_NOT_JAWBREAKER true - #define IS_H1_OR_RAD1O true - #define IS_FOUR_LEDS true -#elif defined(JAWBREAKER) - #define IS_NOT_PRALINE true - #define IS_NOT_HACKRF_ONE true - #define IS_NOT_H1_R9 true - #define IS_NOT_RAD1O true - #define IS_JAWBREAKER true - #define IS_H1_OR_JAWBREAKER true -#else - #error "No recognised platform defined" -#endif - -/* clang-format on */ - typedef enum { BOARD_ID_JELLYBEAN = 0, BOARD_ID_JAWBREAKER = 1, diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index eb1d5716..dfc67d9e 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -74,6 +74,8 @@ if(NOT DEFINED BOARD) set(BOARD HACKRF_ONE) endif() +include(${PATH_HACKRF_FIRMWARE}/platform-detect.cmake) + if(BOARD STREQUAL "HACKRF_ONE" OR BOARD STREQUAL "PRALINE" OR BOARD STREQUAL "UNIVERSAL") set(MCU_PARTNO LPC4320) else() diff --git a/firmware/platform-detect.cmake b/firmware/platform-detect.cmake new file mode 100644 index 00000000..dcaeba58 --- /dev/null +++ b/firmware/platform-detect.cmake @@ -0,0 +1,87 @@ +# Copyright 2026 Great Scott Gadgets +# +# This file is part of HackRF. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +cmake_minimum_required(VERSION 3.12.0) + +if(BOARD STREQUAL "UNIVERSAL") + add_compile_definitions( + "IS_PRALINE=(detected_platform() == BOARD_ID_PRALINE)" + "IS_NOT_PRALINE=(!IS_PRALINE)" + "IS_HACKRF_ONE=( \ + detected_platform() == BOARD_ID_HACKRF1_OG || \ + detected_platform() == BOARD_ID_HACKRF1_R9 \ + )" + "IS_NOT_HACKRF_ONE=(!IS_HACKRF_ONE)" + "IS_H1_R9=(detected_platform() == BOARD_ID_HACKRF1_R9)" + "IS_NOT_H1_R9=(!IS_H1_R9)" + "IS_NOT_RAD1O=1" + "IS_NOT_JAWBREAKER=1" + "IS_H1_OR_PRALINE=1" + "IS_H1_OR_RAD1O=IS_HACKRF_ONE" + "IS_H1_OR_JAWBREAKER=IS_HACKRF_ONE" + "IS_FOUR_LEDS=IS_PRALINE" + "IS_EXPANSION_COMPATIBLE=1" + ) +elseif(BOARD STREQUAL "HACKRF_ONE") + add_compile_definitions( + "IS_NOT_PRALINE=1" + "IS_HACKRF_ONE=1" + "IS_H1_R9=(detected_platform() == BOARD_ID_HACKRF1_R9)" + "IS_NOT_H1_R9=(!IS_H1_R9)" + "IS_NOT_RAD1O=1" + "IS_NOT_JAWBREAKER=1" + "IS_H1_OR_PRALINE=1" + "IS_H1_OR_RAD1O=1" + "IS_H1_OR_JAWBREAKER=1" + "IS_EXPANSION_COMPATIBLE=1" + ) +elseif(BOARD STREQUAL "PRALINE") + add_compile_definitions( + "IS_PRALINE=1" + "IS_NOT_HACKRF_ONE=1" + "IS_NOT_H1_R9=1" + "IS_NOT_RAD1O=1" + "IS_NOT_JAWBREAKER=1" + "IS_H1_OR_PRALINE=1" + "IS_FOUR_LEDS=1" + "IS_EXPANSION_COMPATIBLE=1" + ) +elseif(BOARD STREQUAL "RAD1O") + add_compile_definitions( + "IS_NOT_PRALINE=1" + "IS_NOT_HACKRF_ONE=1" + "IS_NOT_H1_R9=1" + "IS_RAD1O=1" + "IS_NOT_JAWBREAKER=1" + "IS_H1_OR_RAD1O=1" + "IS_FOUR_LEDS=1" + ) +elseif(BOARD STREQUAL "JAWBREAKER") + add_compile_definitions( + "IS_NOT_PRALINE=1" + "IS_NOT_HACKRF_ONE=1" + "IS_NOT_H1_R9=1" + "IS_NOT_RAD1O=1" + "IS_JAWBREAKER=1" + "IS_H1_OR_JAWBREAKER=1" + ) +else() + message(FATAL_ERROR "No recognized platform defined: ${BOARD}") +endif() From 01ffff68eea4db1f39b76bff1ab64f1edeb11d6b Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Fri, 24 Apr 2026 11:55:25 +0200 Subject: [PATCH 110/161] firmware: remove spurious platform_detect.h includes --- firmware/common/clock_gen.h | 2 -- firmware/common/clock_io.c | 1 + firmware/common/clock_io.h | 2 -- firmware/common/cpld_jtag.c | 1 - firmware/common/cpld_jtag.h | 1 - firmware/common/cpu_clock.c | 3 --- firmware/common/cpu_clock.h | 2 -- firmware/common/hackrf_ui.c | 1 - firmware/common/max283x.h | 1 - firmware/common/mixer.h | 2 -- firmware/common/operacake_sctimer.c | 5 ++++- firmware/common/pins.c | 2 +- firmware/common/pins.h | 2 -- firmware/common/platform_gpio.h | 1 - firmware/common/platform_scu.h | 2 -- firmware/common/power.c | 10 ++++------ firmware/common/power.h | 2 -- firmware/common/rf_path.h | 2 -- firmware/common/rffc5071.h | 1 - firmware/common/selftest.h | 2 -- firmware/common/sgpio.h | 1 - firmware/hackrf_usb/usb_api_register.c | 1 + firmware/hackrf_usb/usb_api_register.h | 2 -- firmware/hackrf_usb/usb_api_selftest.c | 7 +++---- firmware/hackrf_usb/usb_descriptor.c | 2 -- firmware/hackrf_usb/usb_descriptor.h | 2 -- firmware/hackrf_usb/usb_device.c | 1 - firmware/hackrf_usb/usb_device.h | 2 -- firmware/include-what-you-use.cmake | 18 +++++++++++++++++- 29 files changed, 31 insertions(+), 50 deletions(-) diff --git a/firmware/common/clock_gen.h b/firmware/common/clock_gen.h index 0bc6439b..fa1d3da0 100644 --- a/firmware/common/clock_gen.h +++ b/firmware/common/clock_gen.h @@ -25,8 +25,6 @@ extern "C" { #endif -#include "platform_detect.h" // IWYU pragma: keep - #include #include "fixed_point.h" diff --git a/firmware/common/clock_io.c b/firmware/common/clock_io.c index b0904f89..d4d14af0 100644 --- a/firmware/common/clock_io.c +++ b/firmware/common/clock_io.c @@ -32,6 +32,7 @@ #include "gpdma.h" #include "gpio.h" +#include "platform_detect.h" #ifdef IS_NOT_PRALINE #include "sgpio.h" #endif diff --git a/firmware/common/clock_io.h b/firmware/common/clock_io.h index df540022..4868b2d9 100644 --- a/firmware/common/clock_io.h +++ b/firmware/common/clock_io.h @@ -24,8 +24,6 @@ #include #include -#include "platform_detect.h" // IWYU pragma: keep - void clkin_detect_init(void); uint32_t clkin_frequency(void); diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 92f89f27..441fefe8 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -22,7 +22,6 @@ #include "cpld_jtag.h" #include "platform_detect.h" - #ifdef IS_NOT_PRALINE #include #include "xapp058/micro.h" diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index 14f70437..f49f1317 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -24,7 +24,6 @@ #include #include "gpio.h" -#include "platform_detect.h" typedef struct { gpio_t gpio_tck; diff --git a/firmware/common/cpu_clock.c b/firmware/common/cpu_clock.c index aa4ac910..37445e51 100644 --- a/firmware/common/cpu_clock.c +++ b/firmware/common/cpu_clock.c @@ -31,9 +31,6 @@ #include "delay.h" #include "i2c_bus.h" #include "si5351c.h" -#ifdef IS_NOT_RAD1O - #include "platform_detect.h" -#endif /* Configure PLL1 (Main MCU Clock) to max speed (204MHz). diff --git a/firmware/common/cpu_clock.h b/firmware/common/cpu_clock.h index b0393081..92ad6441 100644 --- a/firmware/common/cpu_clock.h +++ b/firmware/common/cpu_clock.h @@ -25,8 +25,6 @@ extern "C" { #endif -#include "platform_detect.h" // IWYU pragma: keep - void cpu_clock_init(void); #ifdef __cplusplus diff --git a/firmware/common/hackrf_ui.c b/firmware/common/hackrf_ui.c index 4224d40f..ac243890 100644 --- a/firmware/common/hackrf_ui.c +++ b/firmware/common/hackrf_ui.c @@ -23,7 +23,6 @@ #include #include "hackrf_ui.h" -#include "platform_detect.h" // IWYU pragma: keep #include "transceiver_mode.h" #ifdef IS_EXPANSION_COMPATIBLE #include "ui_portapack.h" diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index b4e44620..4db5edfe 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -27,7 +27,6 @@ #include #include "fixed_point.h" -#include "platform_detect.h" #include "spi_ssp.h" #ifdef IS_PRALINE diff --git a/firmware/common/mixer.h b/firmware/common/mixer.h index 35c0f047..15afe35e 100644 --- a/firmware/common/mixer.h +++ b/firmware/common/mixer.h @@ -26,8 +26,6 @@ #include #include "fixed_point.h" -#include "platform_detect.h" - #ifdef IS_RAD1O #include "max2871.h" #endif diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 1268114f..9325f65d 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -30,12 +30,15 @@ #include "platform_detect.h" #include "platform_scu.h" #include "sct.h" - #ifdef IS_NOT_PRALINE #include #include #endif +#include "delay.h" +#include "platform_detect.h" +#include "sct.h" + #define U1CTRL_SET SCT_OUT14_SET #define U1CTRL_CLR SCT_OUT14_CLR #define U2CTRL0_SET SCT_OUT13_SET diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 48479f95..3010cd9f 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -39,7 +39,7 @@ #include "rf_path.h" #include "sgpio.h" #include "w25q80bv.h" -#if defined(IS_PRALINE) +#ifdef IS_PRALINE #include "clock_io.h" #include "ice40_spi.h" #endif diff --git a/firmware/common/pins.h b/firmware/common/pins.h index b9454d18..bc109e5b 100644 --- a/firmware/common/pins.h +++ b/firmware/common/pins.h @@ -27,8 +27,6 @@ extern "C" { #endif -#include "platform_detect.h" // IWYU pragma: keep - void pins_shutdown(void); void pins_setup(void); diff --git a/firmware/common/platform_gpio.h b/firmware/common/platform_gpio.h index a7e68bc9..c57df96e 100644 --- a/firmware/common/platform_gpio.h +++ b/firmware/common/platform_gpio.h @@ -27,7 +27,6 @@ extern "C" { #include "gpio.h" #include "gpio_lpc.h" -#include "platform_detect.h" typedef struct { /* LEDs */ diff --git a/firmware/common/platform_scu.h b/firmware/common/platform_scu.h index 71c0f742..a3702bf7 100644 --- a/firmware/common/platform_scu.h +++ b/firmware/common/platform_scu.h @@ -29,8 +29,6 @@ extern "C" { #include -#include "platform_detect.h" - /* * SCU PinMux */ diff --git a/firmware/common/power.c b/firmware/common/power.c index 25424aab..1256e595 100644 --- a/firmware/common/power.c +++ b/firmware/common/power.c @@ -24,16 +24,14 @@ #if defined(IS_HACKRF_ONE) #include #endif -#if defined(IS_HACKRF_ONE) || defined(IS_RAD1O) || defined(IS_JAWBREAKER) - #include "platform_detect.h" -#endif + +#include "gpio.h" +#include "platform_detect.h" +#include "platform_gpio.h" #if defined(IS_PRALINE) || defined(IS_RAD1O) #include "delay.h" #endif -#include "gpio.h" -#include "platform_gpio.h" - #ifdef IS_PRALINE void enable_1v2_power(void) { diff --git a/firmware/common/power.h b/firmware/common/power.h index 22b2345e..fc8af67a 100644 --- a/firmware/common/power.h +++ b/firmware/common/power.h @@ -25,8 +25,6 @@ extern "C" { #endif -#include "platform_detect.h" // IWYU pragma: keep - #ifdef IS_PRALINE void enable_1v2_power(void); void disable_1v2_power(void); diff --git a/firmware/common/rf_path.h b/firmware/common/rf_path.h index 3b97ac62..a1796f13 100644 --- a/firmware/common/rf_path.h +++ b/firmware/common/rf_path.h @@ -25,8 +25,6 @@ #include -#include "platform_detect.h" // IWYU pragma: keep - #ifdef IS_NOT_JAWBREAKER #include "gpio.h" #endif diff --git a/firmware/common/rffc5071.h b/firmware/common/rffc5071.h index c6b2c0c2..40971061 100644 --- a/firmware/common/rffc5071.h +++ b/firmware/common/rffc5071.h @@ -27,7 +27,6 @@ #include "fixed_point.h" #include "gpio.h" -#include "platform_detect.h" // IWYU pragma: keep #include "spi_bus.h" /* 31 registers, each containing 16 bits of data. */ diff --git a/firmware/common/selftest.h b/firmware/common/selftest.h index 9fca53a6..cea60da2 100644 --- a/firmware/common/selftest.h +++ b/firmware/common/selftest.h @@ -24,8 +24,6 @@ #include #include -#include "platform_detect.h" // IWYU pragma: keep - #define NUM_LOCK_ATTEMPTS 3 enum { diff --git a/firmware/common/sgpio.h b/firmware/common/sgpio.h index 58a30c27..0aebdb5f 100644 --- a/firmware/common/sgpio.h +++ b/firmware/common/sgpio.h @@ -26,7 +26,6 @@ #include #include "gpio.h" -#include "platform_detect.h" // IWYU pragma: keep typedef enum { SGPIO_DIRECTION_RX, diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index 30d1d493..1a9f0238 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -21,6 +21,7 @@ * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ + #include "usb_api_register.h" #include diff --git a/firmware/hackrf_usb/usb_api_register.h b/firmware/hackrf_usb/usb_api_register.h index aef02d03..a5a36550 100644 --- a/firmware/hackrf_usb/usb_api_register.h +++ b/firmware/hackrf_usb/usb_api_register.h @@ -23,8 +23,6 @@ #pragma once -#include "platform_detect.h" // IWYU pragma: keep - #include #include diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index 4c812366..7fde6f68 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -23,6 +23,9 @@ #include #include +#ifdef IS_NOT_RAD1O + #include +#endif #include #include @@ -33,10 +36,6 @@ #include #include -#ifdef IS_NOT_RAD1O - #include -#endif - static char* itoa(int val, int base) { static char buf[32] = {0}; diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index 067317b3..005babb3 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -26,8 +26,6 @@ #include -#include "platform_detect.h" - #define USB_VENDOR_ID (0x1D50) #define USB_API_VERSION (0x0112) diff --git a/firmware/hackrf_usb/usb_descriptor.h b/firmware/hackrf_usb/usb_descriptor.h index ef41d6ac..bd43f4aa 100644 --- a/firmware/hackrf_usb/usb_descriptor.h +++ b/firmware/hackrf_usb/usb_descriptor.h @@ -24,8 +24,6 @@ #include -#include "platform_detect.h" - #if defined(IS_HACKRF_ONE) || defined(IS_PRALINE) extern uint8_t usb_descriptor_device_hackrf[]; #endif diff --git a/firmware/hackrf_usb/usb_device.c b/firmware/hackrf_usb/usb_device.c index a2af1158..90347e17 100644 --- a/firmware/hackrf_usb/usb_device.c +++ b/firmware/hackrf_usb/usb_device.c @@ -25,7 +25,6 @@ #include -#include "platform_detect.h" #include "usb_descriptor.h" usb_configuration_t usb_configuration_high_speed = { diff --git a/firmware/hackrf_usb/usb_device.h b/firmware/hackrf_usb/usb_device.h index f442db37..5ef53360 100644 --- a/firmware/hackrf_usb/usb_device.h +++ b/firmware/hackrf_usb/usb_device.h @@ -25,8 +25,6 @@ #include -#include "platform_detect.h" - extern usb_device_t usb_device; #ifdef IS_HACKRF_ONE diff --git a/firmware/include-what-you-use.cmake b/firmware/include-what-you-use.cmake index b5c1bc7e..291cfdab 100644 --- a/firmware/include-what-you-use.cmake +++ b/firmware/include-what-you-use.cmake @@ -28,11 +28,27 @@ endif() # include-what-you-use arguments: # # --no_fwd_decls - Disable forward declaration suggestions. +# --keep - Always keep the specified include. # --mapping_file - Specify mapping files for excluding suggestions. +# -w - Disable spurious clang warnings. +# +# NOTE: We specify '--keep platform_detect.h' due to iwyu 0.23 not +# being able to detect usage of symbols within preprocessor +# macros. This results in false positives for the removal of the +# include. +# +# The alternate solution is to use "// IWYU pragma: always-keep" +# within "platform_detect.h" itself but this triggers the +# angle-quote-curse on 0.23. (e.g. https://github.com/include-what-you-use/include-what-you-use/issues/1087) +# +# iwyu versions from at least 0.26 onwards do not suffer from these +# problems which will allow us to remove it in future. # list(APPEND IWYU_COMMAND -Xiwyu --no_fwd_decls - -Xiwyu --mapping_file=${CMAKE_SOURCE_DIR}/libopencm3.imp) + -Xiwyu --keep ${CMAKE_SOURCE_DIR}/common/platform_detect.h + -Xiwyu --mapping_file=${CMAKE_SOURCE_DIR}/libopencm3.imp + -w) set(CMAKE_C_INCLUDE_WHAT_YOU_USE ${IWYU_COMMAND}) set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_COMMAND}) From 23104c7bd9fa4dddda0bc5c6db512679b5626cc7 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 28 Apr 2026 11:58:18 +0100 Subject: [PATCH 111/161] firmware: bump CMake requirement to 3.12. --- .github/workflows/build.yml | 4 ++-- .github/workflows/includes-check.yml | 2 +- firmware/CMakeLists.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a93a27c1..02239f82 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -160,10 +160,10 @@ jobs: matrix: os: ['macos', 'ubuntu', 'windows'] board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE', 'UNIVERSAL'] - cmake: ['3.10.0', 'latest'] + cmake: ['3.12.0', 'latest'] exclude: - os: 'windows' - cmake: '3.10.0' + cmake: '3.12.0' # GitHub doesn't give us many macOS runners, so don't build for every board. # We just need to know that building firmware works on macOS. Use PRALINE # since that's the most complicated one due to FPGA bitstreams. diff --git a/.github/workflows/includes-check.yml b/.github/workflows/includes-check.yml index 11640069..f16800fe 100644 --- a/.github/workflows/includes-check.yml +++ b/.github/workflows/includes-check.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: board: ['HACKRF_ONE', 'JAWBREAKER', 'RAD1O', 'PRALINE', 'UNIVERSAL'] - cmake: ['3.10.0', 'latest'] + cmake: ['3.12.0', 'latest'] # Don't cancel all builds when one fails fail-fast: false diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 611e7870..844ae708 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -23,7 +23,7 @@ option(CHECK_INCLUDES "Check firmware sources for unused includes and transitive dependencies. (Requires iwyu)" OFF) -cmake_minimum_required(VERSION 3.10.0) +cmake_minimum_required(VERSION 3.12.0) set(CMAKE_TOOLCHAIN_FILE toolchain-arm-cortex-m.cmake) project (hackrf_firmware_all C) From 6ff7f8ed6346a396e74d85ed7e7b5b9049052806 Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Thu, 7 May 2026 18:56:53 -0400 Subject: [PATCH 112/161] Fix IWYU --keep syntax for compatibility with 0.22 --- firmware/include-what-you-use.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/include-what-you-use.cmake b/firmware/include-what-you-use.cmake index 291cfdab..37e2a2a6 100644 --- a/firmware/include-what-you-use.cmake +++ b/firmware/include-what-you-use.cmake @@ -46,7 +46,7 @@ endif() # list(APPEND IWYU_COMMAND -Xiwyu --no_fwd_decls - -Xiwyu --keep ${CMAKE_SOURCE_DIR}/common/platform_detect.h + -Xiwyu --keep=${CMAKE_SOURCE_DIR}/common/platform_detect.h -Xiwyu --mapping_file=${CMAKE_SOURCE_DIR}/libopencm3.imp -w) From db44436fda46150bc443b0c1f65b1e70803d7cb5 Mon Sep 17 00:00:00 2001 From: Michael Ossmann Date: Thu, 7 May 2026 19:30:30 -0400 Subject: [PATCH 113/161] Remove redundant includes --- firmware/common/operacake_sctimer.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 9325f65d..76453e57 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -35,10 +35,6 @@ #include #endif -#include "delay.h" -#include "platform_detect.h" -#include "sct.h" - #define U1CTRL_SET SCT_OUT14_SET #define U1CTRL_CLR SCT_OUT14_CLR #define U2CTRL0_SET SCT_OUT13_SET From fe060fd04b1c18a1755cd800632c45bcf9b5fa2b Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 11 May 2026 12:19:29 +0100 Subject: [PATCH 114/161] libhackrf: Set timeouts for all USB transfers. --- host/libhackrf/src/hackrf.c | 129 ++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index da3c0f15..7bafd5f3 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -54,6 +54,10 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSI #define FROM_LE64(x) x #endif +#define DEFAULT_REQUEST_TIMEOUT 100 +#define CPLD_WRITE_TIMEOUT 10000 +#define SPIFLASH_WRITE_TIMEOUT 50000 // W25Q32JV max chip erase time + // TODO: Factor this into a shared #include so that firmware can use // the same values. typedef enum { @@ -778,7 +782,7 @@ static int hackrf_open_setup(libusb_device_handle* usb_device, hackrf_device** d 0, (unsigned char*) &buffer_size, 4, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 4) { last_libusb_error = result; @@ -948,7 +952,7 @@ int ADDCALL hackrf_set_transceiver_mode( 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -977,7 +981,7 @@ int ADDCALL hackrf_max2837_read( register_number, (unsigned char*) value, 2, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 2) { last_libusb_error = result; @@ -1006,7 +1010,7 @@ int ADDCALL hackrf_max2831_read( register_number, (unsigned char*) value, 2, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 2) { last_libusb_error = result; @@ -1039,7 +1043,7 @@ int ADDCALL hackrf_max2837_write( register_number, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1072,7 +1076,7 @@ int ADDCALL hackrf_max2831_write( register_number, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1103,7 +1107,7 @@ int ADDCALL hackrf_si5351c_read( register_number, (unsigned char*) &temp_value, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; @@ -1137,7 +1141,7 @@ int ADDCALL hackrf_si5351c_write( register_number, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1161,7 +1165,7 @@ int ADDCALL hackrf_set_baseband_filter_bandwidth( bandwidth_hz >> 16, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1190,7 +1194,7 @@ int ADDCALL hackrf_rffc5071_read( register_number, (unsigned char*) value, 2, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 2) { last_libusb_error = result; @@ -1220,7 +1224,7 @@ int ADDCALL hackrf_rffc5071_write( register_number, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1246,7 +1250,7 @@ int ADDCALL hackrf_fpga_read_register( register_number, (unsigned char*) value, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; @@ -1273,7 +1277,7 @@ int ADDCALL hackrf_fpga_write_register( register_number, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1297,7 +1301,7 @@ int ADDCALL hackrf_read_selftest(hackrf_device* device, hackrf_selftest* selftes 0, (unsigned char*) selftest, sizeof(hackrf_selftest), - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 2) { last_libusb_error = result; @@ -1429,7 +1433,7 @@ int ADDCALL hackrf_read_adc(hackrf_device* device, uint8_t adc_channel, uint16_t adc_channel, (unsigned char*) value, 2, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 2) { last_libusb_error = result; @@ -1453,7 +1457,7 @@ int ADDCALL hackrf_get_m0_state(hackrf_device* device, hackrf_m0_state* state) 0, (unsigned char*) state, sizeof(hackrf_m0_state), - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < sizeof(hackrf_m0_state)) { last_libusb_error = result; @@ -1488,7 +1492,7 @@ int ADDCALL hackrf_set_tx_underrun_limit(hackrf_device* device, uint32_t value) value >> 16, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1512,7 +1516,7 @@ int ADDCALL hackrf_set_rx_overrun_limit(hackrf_device* device, uint32_t value) value >> 16, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1534,7 +1538,7 @@ int ADDCALL hackrf_spiflash_erase(hackrf_device* device) 0, NULL, 0, - 0); + SPIFLASH_WRITE_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1565,7 +1569,7 @@ int ADDCALL hackrf_spiflash_write( address & 0xFFFF, data, length, - 0); + SPIFLASH_WRITE_TIMEOUT); if (result < length) { last_libusb_error = result; @@ -1595,7 +1599,7 @@ int ADDCALL hackrf_spiflash_read( address & 0xFFFF, data, length, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < length) { last_libusb_error = result; @@ -1618,7 +1622,7 @@ int ADDCALL hackrf_spiflash_status(hackrf_device* device, uint8_t* data) 0, data, 2, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; @@ -1641,7 +1645,7 @@ int ADDCALL hackrf_spiflash_clear_status(hackrf_device* device) 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1671,8 +1675,7 @@ int ADDCALL hackrf_cpld_write( &data[i], chunk_size, &transferred, - 10000 // long timeout to allow for CPLD programming - ); + CPLD_WRITE_TIMEOUT); if (result != LIBUSB_SUCCESS) { last_libusb_error = result; @@ -1694,7 +1697,7 @@ int ADDCALL hackrf_board_id_read(hackrf_device* device, uint8_t* value) 0, value, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; @@ -1718,7 +1721,7 @@ int ADDCALL hackrf_version_string_read( 0, (unsigned char*) version, length, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 0) { last_libusb_error = result; @@ -1769,7 +1772,7 @@ int ADDCALL hackrf_set_freq(hackrf_device* device, const uint64_t freq_hz) 0, (unsigned char*) &set_freq_params, length, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < length) { last_libusb_error = result; @@ -1829,7 +1832,7 @@ int ADDCALL hackrf_set_freq_explicit( 0, (unsigned char*) ¶ms, length, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < length) { last_libusb_error = result; @@ -1871,7 +1874,7 @@ int ADDCALL hackrf_set_sample_rate_manual( 0, (unsigned char*) &set_fracrate_params, length, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < length) { last_libusb_error = result; @@ -1943,7 +1946,7 @@ int ADDCALL hackrf_set_amp_enable(hackrf_device* device, const uint8_t value) 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -1969,7 +1972,7 @@ int ADDCALL hackrf_board_partid_serialno_read( 0, (unsigned char*) read_partid_serialno, length, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < length) { last_libusb_error = result; @@ -2010,7 +2013,7 @@ int ADDCALL hackrf_set_lna_gain(hackrf_device* device, uint32_t value) value, &retval, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 1 || !retval) { return HACKRF_ERROR_INVALID_PARAM; @@ -2037,7 +2040,7 @@ int ADDCALL hackrf_set_vga_gain(hackrf_device* device, uint32_t value) value, &retval, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 1 || !retval) { return HACKRF_ERROR_INVALID_PARAM; @@ -2063,7 +2066,7 @@ int ADDCALL hackrf_set_txvga_gain(hackrf_device* device, uint32_t value) value, &retval, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 1 || !retval) { return HACKRF_ERROR_INVALID_PARAM; @@ -2084,7 +2087,7 @@ int ADDCALL hackrf_set_antenna_enable(hackrf_device* device, const uint8_t value 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -2401,7 +2404,7 @@ ADDAPI int ADDCALL hackrf_enable_tx_flush( device->buffer_size, hackrf_libusb_flush_callback, device, - 0); + DEFAULT_REQUEST_TIMEOUT); device->flush_transfer->flags = LIBUSB_TRANSFER_FREE_BUFFER; @@ -2669,7 +2672,7 @@ int ADDCALL hackrf_set_hw_sync_mode(hackrf_device* device, const uint8_t value) 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -2750,7 +2753,7 @@ int ADDCALL hackrf_init_sweep( (num_bytes >> 16) & 0xffff, data, size, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < size) { last_libusb_error = result; @@ -2784,7 +2787,7 @@ int ADDCALL hackrf_get_operacake_boards(hackrf_device* device, uint8_t* boards) 0, boards, 8, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 8) { last_libusb_error = result; @@ -2823,7 +2826,7 @@ int ADDCALL hackrf_set_operacake_mode( (uint8_t) mode, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -2862,7 +2865,7 @@ int ADDCALL hackrf_get_operacake_mode( 0, &buf, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; @@ -2905,7 +2908,7 @@ int ADDCALL hackrf_set_operacake_ports( port_a | (port_b << 8), NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -2927,7 +2930,7 @@ int ADDCALL hackrf_reset(hackrf_device* device) 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -2957,7 +2960,7 @@ int ADDCALL hackrf_set_operacake_ranges( 0, ranges, len_ranges, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < len_ranges) { last_libusb_error = result; @@ -2998,7 +3001,7 @@ int ADDCALL hackrf_set_operacake_freq_ranges( 0, range_bytes, len_ranges, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < len_ranges) { last_libusb_error = result; @@ -3040,7 +3043,7 @@ int ADDCALL hackrf_set_operacake_dwell_times( 0, dwell_data, data_len, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < data_len) { last_libusb_error = result; @@ -3063,7 +3066,7 @@ int ADDCALL hackrf_set_clkout_enable(hackrf_device* device, const uint8_t value) 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3085,7 +3088,7 @@ int ADDCALL hackrf_get_clkin_status(hackrf_device* device, uint8_t* status) 0, status, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; return HACKRF_ERROR_LIBUSB; @@ -3114,7 +3117,7 @@ int ADDCALL hackrf_operacake_gpio_test( 0, (unsigned char*) test_result, 2, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; @@ -3140,7 +3143,7 @@ int ADDCALL hackrf_cpld_checksum(hackrf_device* device, uint32_t* crc) 0, (unsigned char*) crc, length, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < length) { last_libusb_error = result; @@ -3165,7 +3168,7 @@ int ADDCALL hackrf_set_ui_enable(hackrf_device* device, const uint8_t value) 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3223,7 +3226,7 @@ int ADDCALL hackrf_board_rev_read(hackrf_device* device, uint8_t* value) 0, value, 1, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; @@ -3307,7 +3310,7 @@ int ADDCALL hackrf_supported_platform_read(hackrf_device* device, uint32_t* valu 0, &data[0], 4, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 1) { last_libusb_error = result; @@ -3330,7 +3333,7 @@ int ADDCALL hackrf_set_leds(hackrf_device* device, const uint8_t state) 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3376,7 +3379,7 @@ int ADDCALL hackrf_set_user_bias_t_opts( 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3399,7 +3402,7 @@ int ADDCALL hackrf_set_p1_ctrl(hackrf_device* device, const enum p1_ctrl_signal 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3422,7 +3425,7 @@ int ADDCALL hackrf_set_p2_ctrl(hackrf_device* device, const enum p2_ctrl_signal 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3447,7 +3450,7 @@ int ADDCALL hackrf_set_clkin_ctrl( 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3470,7 +3473,7 @@ int ADDCALL hackrf_set_narrowband_filter(hackrf_device* device, const uint8_t va 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3493,7 +3496,7 @@ int ADDCALL hackrf_set_fpga_bitstream(hackrf_device* device, const uint8_t index 0, NULL, 0, - 0); + DEFAULT_REQUEST_TIMEOUT); if (result != 0) { last_libusb_error = result; @@ -3521,7 +3524,7 @@ int ADDCALL hackrf_radio_read_register( bank, (unsigned char*) value, length, - 0); + DEFAULT_REQUEST_TIMEOUT); *value = FROM_LE64(*value); if (result < length) { @@ -3561,7 +3564,7 @@ int ADDCALL hackrf_radio_write_register( bank, &data[0], sizeof(data), - 0); + DEFAULT_REQUEST_TIMEOUT); if (result < 9) { last_libusb_error = result; From b4600148116b0aaeb91291d3c8bd7c50977dbde2 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 13 May 2026 21:52:51 +0100 Subject: [PATCH 115/161] Move cpld_jtag_sram_load() into cpld_jtag.c. --- firmware/common/cpld_jtag.c | 13 +++++++++++++ firmware/common/cpld_jtag.h | 1 + firmware/hackrf_usb/hackrf_usb.c | 15 --------------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 441fefe8..4d313d01 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -21,6 +21,7 @@ #include "cpld_jtag.h" +#include "cpld_xc2c.h" #include "platform_detect.h" #ifdef IS_NOT_PRALINE #include @@ -138,4 +139,16 @@ unsigned char cpld_jtag_get_next_byte(void) xsvf_pos++; return byte; } + +bool cpld_jtag_sram_load(jtag_t* const jtag) +{ + cpld_jtag_take(jtag); + cpld_xc2c64a_jtag_sram_write(jtag, &cpld_hackrf_program_sram); + const bool success = cpld_xc2c64a_jtag_sram_verify( + jtag, + &cpld_hackrf_program_sram, + &cpld_hackrf_verify); + cpld_jtag_release(jtag); + return success; +} #endif diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index f49f1317..b9a4b52f 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -58,6 +58,7 @@ int cpld_jtag_program( unsigned char* const buffer, refill_buffer_cb refill); unsigned char cpld_jtag_get_next_byte(void); +bool cpld_jtag_sram_load(jtag_t* const jtag); /* Driver instance. */ extern jtag_gpio_t jtag_gpio_cpld; diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 97863876..eb7f85c5 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -69,7 +69,6 @@ #endif #ifdef IS_NOT_PRALINE #include - #include #endif #include "usb_api_adc.h" @@ -261,20 +260,6 @@ void usb_set_descriptor_by_serial_number(void) } } -#ifdef IS_NOT_PRALINE -static bool cpld_jtag_sram_load(jtag_t* const jtag) -{ - cpld_jtag_take(jtag); - cpld_xc2c64a_jtag_sram_write(jtag, &cpld_hackrf_program_sram); - const bool success = cpld_xc2c64a_jtag_sram_verify( - jtag, - &cpld_hackrf_program_sram, - &cpld_hackrf_verify); - cpld_jtag_release(jtag); - return success; -} -#endif - static void m0_rom_to_ram(void) { uint32_t* dest = &__ram_m0_start__; From d00dae3835f15a398915d764393a4c0b3e256ff0 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 3 May 2026 09:03:37 +0100 Subject: [PATCH 116/161] si5351c: API: change terminology to refer to XTAL/CLKIN as "inputs". There are multiple similar but different things called a "source" or "clock source", or that are set in a register called "SRC", in the context of the Si5351 and its usage in HackRF. One place we can be unambiguous is that there are only two inputs to the Si5351C itself: XTAL and CLKIN. Let's have a type for that alone, and name it as such when we use it. There should be no functional changes in this commit. --- firmware/common/clock_gen.c | 9 +++++---- firmware/common/si5351c.c | 28 ++++++++++++++-------------- firmware/common/si5351c.h | 17 +++++++---------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index 0f0085c2..f1f0bc60 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -111,7 +111,7 @@ void clock_gen_init(void) sample_rate_set(SR_FP_MHZ(10), true); si5351c_configure_clock_control(&si5351c); - si5351c_set_clock_source(&si5351c, PLL_SOURCE_XTAL); + si5351c_change_input(&si5351c, SI5351C_INPUT_XTAL); // soft reset si5351c_reset_pll(&si5351c, SI5351C_PLL_BOTH); si5351c_enable_clock_outputs(&si5351c); @@ -159,9 +159,10 @@ clock_source_t activate_best_clock_source(void) /* No external or PortaPack clock was found. Use HackRF Si5351C crystal. */ } - si5351c_set_clock_source( - &si5351c, - (source == CLOCK_SOURCE_HACKRF) ? PLL_SOURCE_XTAL : PLL_SOURCE_CLKIN); + si5351c_input_t input = (source == CLOCK_SOURCE_HACKRF) ? SI5351C_INPUT_XTAL : + SI5351C_INPUT_CLKIN; + si5351c_change_input(&si5351c, input); + hackrf_ui()->set_clock_source(source); return source; diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index a7b605f5..23d18e93 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -48,7 +48,8 @@ si5351c_driver_t si5351c = { .i2c_address = 0x60, }; -static enum pll_sources active_clock_source = PLL_SOURCE_UNINITIALIZED; +static bool input_initialized = false; +static si5351c_input_t active_input; /* External clock output default is deactivated as it creates noise */ static bool clkout_enabled = false; static uint8_t outputs_disabled = 0xff; @@ -160,13 +161,11 @@ void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv) * CLKIN_DIV=0 (Divide by 1) * Set both PLLA_SRC and PLLB_SRC */ -void si5351c_configure_pll_sources( - si5351c_driver_t* const drv, - const enum pll_sources source) +void si5351c_configure_inputs(si5351c_driver_t* const drv, const si5351c_input_t input) { uint8_t data[] = {15, 0x00}; - if (source == PLL_SOURCE_CLKIN) { + if (input == SI5351C_INPUT_CLKIN) { data[1] = 0x0c; } si5351c_write(drv, data, sizeof(data)); @@ -175,11 +174,11 @@ void si5351c_configure_pll_sources( /* MultiSynth NA (PLLA) and NB (PLLB) */ void si5351c_configure_pll_multisynth( si5351c_driver_t* const drv, - const enum pll_sources source) + const si5351c_input_t input) { /* XTAL: 25 MHz * (0x0e00 + 512) / 128 = 800 MHz, integer mode */ uint8_t data[] = {26, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00}; - if (source == PLL_SOURCE_CLKIN) { + if (input == SI5351C_INPUT_CLKIN) { /* CLKIN: 10 MHz * (0x2600 + 512) / 128 = 800 MHz, integer mode */ data[4] = 0x26; } @@ -415,9 +414,9 @@ void si5351c_set_int_mode( } } -void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_sources source) +void si5351c_change_input(si5351c_driver_t* const drv, si5351c_input_t input) { - if (source == active_clock_source) { + if (input_initialized && input == active_input) { return; } si5351c_disable_all_outputs(drv); @@ -427,8 +426,8 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source * HackRF One r9 always uses PLL A on the XTAL input * but externally switches that input to CLKIN. */ - si5351c_configure_pll_sources(drv, PLL_SOURCE_XTAL); - if (source == PLL_SOURCE_CLKIN) { + si5351c_configure_inputs(drv, SI5351C_INPUT_XTAL); + if (input == SI5351C_INPUT_CLKIN) { gpio_set(platform_gpio()->h1r9_clkin_en); } else { gpio_clear(platform_gpio()->h1r9_clkin_en); @@ -437,11 +436,12 @@ void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_source #endif #ifdef IS_NOT_H1_R9 if (IS_NOT_H1_R9) { - si5351c_configure_pll_sources(drv, source); + si5351c_configure_inputs(drv, input); } #endif - si5351c_configure_pll_multisynth(drv, source); - active_clock_source = source; + si5351c_configure_pll_multisynth(drv, input); + active_input = input; + input_initialized = true; si5351c_reset_pll(drv, SI5351C_PLL_BOTH); } diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index b38eac90..aedc7d05 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -64,11 +64,10 @@ typedef enum { #define SI5351C_CLK_PLL_SRC(x) ((x & SI5351C_PLL_B) << 4) -enum pll_sources { - PLL_SOURCE_UNINITIALIZED = -1, - PLL_SOURCE_XTAL = 0, - PLL_SOURCE_CLKIN = 1, -}; +typedef enum { + SI5351C_INPUT_XTAL = 0, + SI5351C_INPUT_CLKIN = 1, +} si5351c_input_t; typedef struct { i2c_bus_t* const bus; @@ -80,12 +79,10 @@ void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv); void si5351c_power_down_all_clocks(si5351c_driver_t* const drv); void si5351c_set_crystal_configuration(si5351c_driver_t* const drv); void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv); -void si5351c_configure_pll_sources( - si5351c_driver_t* const drv, - const enum pll_sources source); +void si5351c_configure_inputs(si5351c_driver_t* const drv, const si5351c_input_t input); void si5351c_configure_pll_multisynth( si5351c_driver_t* const drv, - const enum pll_sources source); + const si5351c_input_t input); void si5351c_reset_pll(si5351c_driver_t* const drv, si5351c_pll_t pll); void si5351c_configure_multisynth( si5351c_driver_t* const drv, @@ -100,7 +97,7 @@ void si5351c_set_int_mode( si5351c_driver_t* const drv, const uint_fast8_t ms_number, const uint_fast8_t on); -void si5351c_set_clock_source(si5351c_driver_t* const drv, const enum pll_sources source); +void si5351c_change_input(si5351c_driver_t* const drv, const si5351c_input_t input); bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv); void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val); From 098b9dd9f20fd971efbd84429a8904d633092d12 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 3 May 2026 09:19:45 +0100 Subject: [PATCH 117/161] si5351c: API: distinguish between PLL IDs and PLL masks. We're going to want a type that just identifies one PLL, and is consistent with how the PLLs are numbered in register settings. The current si5351c_pll_t is a bitmask which allows referring to both PLLs. Let's make that a different type, and name things more clearly when using it. --- firmware/common/clock_gen.c | 4 ++-- firmware/common/si5351c.c | 16 ++++++++-------- firmware/common/si5351c.h | 15 ++++++++++----- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index f1f0bc60..e91e3822 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -113,7 +113,7 @@ void clock_gen_init(void) si5351c_configure_clock_control(&si5351c); si5351c_change_input(&si5351c, SI5351C_INPUT_XTAL); // soft reset - si5351c_reset_pll(&si5351c, SI5351C_PLL_BOTH); + si5351c_reset_plls(&si5351c, SI5351C_PLL_MASK_BOTH); si5351c_enable_clock_outputs(&si5351c); } @@ -412,7 +412,7 @@ fp_28_36_t sample_rate_set(const fp_28_36_t sample_rate, const bool program) } /* Reset PLL to synchronize output clock phase. */ - si5351c_reset_pll(&si5351c, SI5351C_PLL_A); + si5351c_reset_plls(&si5351c, SI5351C_PLL_MASK_A); } #endif diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 23d18e93..30c23129 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -91,7 +91,7 @@ void si5351c_disable_all_outputs(si5351c_driver_t* const drv) } /* Disable all CLKx outputs using selected PLL. */ -void si5351c_disable_pll_outputs(si5351c_driver_t* const drv, si5351c_pll_t pll) +void si5351c_disable_pll_outputs(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) { /* * Bitmask defines outputs using PLL B. Other outputs are assumed to @@ -102,10 +102,10 @@ void si5351c_disable_pll_outputs(si5351c_driver_t* const drv, si5351c_pll_t pll) pllb_outputs = 0x30; } - if (pll & SI5351C_PLL_A) { + if (mask & SI5351C_PLL_MASK_A) { outputs_disabled |= ~pllb_outputs; } - if (pll & SI5351C_PLL_B) { + if (mask & SI5351C_PLL_MASK_B) { outputs_disabled |= pllb_outputs; } uint8_t data[] = {3, outputs_disabled}; @@ -189,18 +189,18 @@ void si5351c_configure_pll_multisynth( si5351c_write(drv, data, sizeof(data)); } -void si5351c_reset_pll(si5351c_driver_t* const drv, si5351c_pll_t pll) +void si5351c_reset_plls(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) { uint8_t value = 0; - if (pll & SI5351C_PLL_A) { + if (mask & SI5351C_PLL_MASK_A) { value |= 0x20; } - if (pll & SI5351C_PLL_B) { + if (mask & SI5351C_PLL_MASK_B) { value |= 0x80; } - si5351c_disable_pll_outputs(drv, pll); + si5351c_disable_pll_outputs(drv, mask); uint8_t data[] = {177, value}; si5351c_write(drv, data, sizeof(data)); delay_us_at_mhz(2000, 204); @@ -442,7 +442,7 @@ void si5351c_change_input(si5351c_driver_t* const drv, si5351c_input_t input) si5351c_configure_pll_multisynth(drv, input); active_input = input; input_initialized = true; - si5351c_reset_pll(drv, SI5351C_PLL_BOTH); + si5351c_reset_plls(drv, SI5351C_PLL_MASK_BOTH); } bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index aedc7d05..491266cf 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -57,12 +57,17 @@ extern "C" { #define SI5351C_REVID 0x03 typedef enum { - SI5351C_PLL_A = 1, - SI5351C_PLL_B = 2, - SI5351C_PLL_BOTH = 3, + SI5351C_PLL_A = 0, + SI5351C_PLL_B = 1, } si5351c_pll_t; -#define SI5351C_CLK_PLL_SRC(x) ((x & SI5351C_PLL_B) << 4) +#define SI5351C_CLK_PLL_SRC(x) ((x & SI5351C_PLL_B) << 5) + +typedef enum { + SI5351C_PLL_MASK_A = 1, + SI5351C_PLL_MASK_B = 2, + SI5351C_PLL_MASK_BOTH = 3, +} si5351c_pll_mask_t; typedef enum { SI5351C_INPUT_XTAL = 0, @@ -83,7 +88,7 @@ void si5351c_configure_inputs(si5351c_driver_t* const drv, const si5351c_input_t void si5351c_configure_pll_multisynth( si5351c_driver_t* const drv, const si5351c_input_t input); -void si5351c_reset_pll(si5351c_driver_t* const drv, si5351c_pll_t pll); +void si5351c_reset_plls(si5351c_driver_t* const drv, si5351c_pll_mask_t mask); void si5351c_configure_multisynth( si5351c_driver_t* const drv, const uint_fast8_t ms_number, From a1cdbaddbf3bf20d74a3da7819093826d6d2a475 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 3 May 2026 09:32:03 +0100 Subject: [PATCH 118/161] si5351c: internal: Add register definitions and helpers. --- firmware/common/si5351c_regs.def | 494 +++++++++++++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 firmware/common/si5351c_regs.def diff --git a/firmware/common/si5351c_regs.def b/firmware/common/si5351c_regs.def new file mode 100644 index 00000000..5c3a6774 --- /dev/null +++ b/firmware/common/si5351c_regs.def @@ -0,0 +1,494 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +static inline void si5351c_reg_set_clean(si5351c_driver_t* drv, int reg) +{ + drv->regs_dirty[reg / 32] &= ~(1 << (reg % 32)); +} + +static inline void si5351c_reg_set_dirty(si5351c_driver_t* drv, int reg) +{ + drv->regs_dirty[reg / 32] |= (1 << (reg % 32)); +} + +static inline bool si5351c_reg_is_dirty(si5351c_driver_t* drv, int reg) +{ + return (drv->regs_dirty[reg / 32] & (1 << (reg % 32))) ? true : false; +} + +/* Generate static inline accessors that operate on the global + * regs. Done this way to (1) allow defs to be scraped out and used + * elsewhere, e.g. in scripts, (2) to avoid dealing with endian + * (structs). This may be used in firmware, or on host predefined + * register loads. */ + +/* On set_, register is always set dirty, even if nothing + * changed. This makes sure that writes that have side effects, + * e.g. frequency setting, are not skipped. */ + +/* n=name, r=regnum, o=offset (bits from LSB) of LSB of field, + * l=length (bits) */ +#define __MREG__(n,r,o,l) \ +static inline uint8_t get_##n(si5351c_driver_t* const _d) { \ + return (_d->regs[r] >> o) & ((1L<regs[r] &= (uint8_t)(~(((1L<regs[r] |= (uint8_t)(((v&((1L<> 8); \ + set_##n##_BOT(_d, v); \ +} + +#define __3PART__(n) \ +static inline uint32_t get_##n(si5351c_driver_t* const _d) { \ + return (get_##n##_TOP(_d) << 16) | (get_##n##_MID(_d) << 8) | get_##n##_BOT(_d); \ +} \ +static inline void set_##n(si5351c_driver_t* const _d, uint32_t v) { \ + set_##n##_TOP(_d, v >> 16); \ + set_##n##_MID(_d, v >> 8); \ + set_##n##_BOT(_d, v); \ +} + +/* Generate accessors for per-PLL and per-multisynth registers */ +/* n=name, p=prefix, s=suffix */ + +#define __PER_PLL__(n, p, s) \ +static inline uint32_t get_##n(si5351c_driver_t* const _d, si5351c_pll_t pll) { \ + return (pll == SI5351C_PLL_A) ? get_##p##A##s(_d) : get_##p##B##s(_d); \ +} \ +static inline void set_##n(si5351c_driver_t* const _d, si5351c_pll_t pll, uint32_t v) { \ + if (pll == SI5351C_PLL_A) { \ + set_##p##A##s(_d, v); \ + } else { \ + set_##p##B##s(_d, v); \ + } \ +} + +#define __PER_MS__(n, p, s) \ +static inline uint32_t get_##n(si5351c_driver_t* const _d, uint8_t i) { \ + switch (i) { \ + case 0: return get_##p##0##s(_d); \ + case 1: return get_##p##1##s(_d); \ + case 2: return get_##p##2##s(_d); \ + case 3: return get_##p##3##s(_d); \ + case 4: return get_##p##4##s(_d); \ + case 5: return get_##p##5##s(_d); \ + case 6: return get_##p##6##s(_d); \ + case 7: return get_##p##7##s(_d); \ + default: return 0; \ + } \ +} \ +static inline void set_##n(si5351c_driver_t* const _d, uint8_t i, uint32_t v) { \ + switch (i) { \ + case 0: set_##p##0##s(_d, v); return; \ + case 1: set_##p##1##s(_d, v); return; \ + case 2: set_##p##2##s(_d, v); return; \ + case 3: set_##p##3##s(_d, v); return; \ + case 4: set_##p##4##s(_d, v); return; \ + case 5: set_##p##5##s(_d, v); return; \ + case 6: set_##p##6##s(_d, v); return; \ + case 7: set_##p##7##s(_d, v); return; \ + } \ +} \ +static inline void set_all_##n(si5351c_driver_t* const _d, uint32_t v) { \ + set_##p##0##s(_d, v); \ + set_##p##1##s(_d, v); \ + set_##p##2##s(_d, v); \ + set_##p##3##s(_d, v); \ + set_##p##4##s(_d, v); \ + set_##p##5##s(_d, v); \ + set_##p##6##s(_d, v); \ + set_##p##7##s(_d, v); \ +} + +#define __PER_FRAC_MS__(n, p, s) \ +static inline void set_##n(si5351c_driver_t* const _d, uint8_t i, uint32_t v) { \ + switch (i) { \ + case 0: set_##p##0##s(_d, v); return; \ + case 1: set_##p##1##s(_d, v); return; \ + case 2: set_##p##2##s(_d, v); return; \ + case 3: set_##p##3##s(_d, v); return; \ + case 4: set_##p##4##s(_d, v); return; \ + case 5: set_##p##5##s(_d, v); return; \ + } \ +} + +/* Register 0: Device Status */ +__MREG__(SYS_INIT, 0, 7, 1) +__MREG__(LOL_B, 0, 6, 1) +__MREG__(LOL_A, 0, 5, 1) +__MREG__(LOS_CLKIN, 0, 4, 1) +__MREG__(LOS_XTAL, 0, 3, 1) +__MREG__(REVID, 0, 0, 2) + +/* Register 1: Interrupt Status Sticky */ +__MREG__(SYS_INIT_STKY, 1, 7, 1) +__MREG__(LOL_B_STKY, 1, 6, 1) +__MREG__(LOL_A_STKY, 1, 5, 1) +__MREG__(LOS_CLKIN_STKY, 1, 4, 1) +__MREG__(LOS_XTAL_STKY, 1, 3, 1) + +/* Register 2: Interrupt Status Mask */ +__MREG__(SYS_INIT_MASK, 2, 7, 1) +__MREG__(LOL_B_MASK, 2, 6, 1) +__MREG__(LOL_A_MASK, 2, 5, 1) +__MREG__(LOS_CLKIN_MASK, 2, 4, 1) +__MREG__(LOS_XTAL_MASK, 2, 3, 1) + +/* Register 3: Output Enable Control */ +__MREG__(CLK7_OEB, 3, 7, 1) +__MREG__(CLK6_OEB, 3, 6, 1) +__MREG__(CLK5_OEB, 3, 5, 1) +__MREG__(CLK4_OEB, 3, 4, 1) +__MREG__(CLK3_OEB, 3, 3, 1) +__MREG__(CLK2_OEB, 3, 2, 1) +__MREG__(CLK1_OEB, 3, 1, 1) +__MREG__(CLK0_OEB, 3, 0, 1) + +/* Register 4: OEB Pin Enable Control */ +__MREG__(OEB_CLK7, 4, 7, 1) +__MREG__(OEB_CLK6, 4, 6, 1) +__MREG__(OEB_CLK5, 4, 5, 1) +__MREG__(OEB_CLK4, 4, 4, 1) +__MREG__(OEB_CLK3, 4, 3, 1) +__MREG__(OEB_CLK2, 4, 2, 1) +__MREG__(OEB_CLK1, 4, 1, 1) +__MREG__(OEB_CLK0, 4, 0, 1) + +/* Register 9: OEB Pin Enable Control Mask */ +__MREG__(OEB_MASK7, 9, 7, 1) +__MREG__(OEB_MASK6, 9, 6, 1) +__MREG__(OEB_MASK5, 9, 5, 1) +__MREG__(OEB_MASK4, 9, 4, 1) +__MREG__(OEB_MASK3, 9, 3, 1) +__MREG__(OEB_MASK2, 9, 2, 1) +__MREG__(OEB_MASK1, 9, 1, 1) +__MREG__(OEB_MASK0, 9, 0, 1) + +/* Register 15: PLL Input Source */ +__MREG__(CLKIN_DIV, 15, 6, 2) +__MREG__(PLLB_SRC, 15, 3, 1) +__MREG__(PLLA_SRC, 15, 2, 1) + +/* Register 16: CLK0 Control */ +__MREG__(CLK0_PDN, 16, 7, 1) +__MREG__(MS0_INT, 16, 6, 1) +__MREG__(MS0_SRC, 16, 5, 1) +__MREG__(CLK0_INV, 16, 4, 1) +__MREG__(CLK0_SRC, 16, 2, 2) +__MREG__(CLK0_IDRV, 16, 0, 2) + +/* Register 17: CLK1 Control */ +__MREG__(CLK1_PDN, 17, 7, 1) +__MREG__(MS1_INT, 17, 6, 1) +__MREG__(MS1_SRC, 17, 5, 1) +__MREG__(CLK1_INV, 17, 4, 1) +__MREG__(CLK1_SRC, 17, 2, 2) +__MREG__(CLK1_IDRV, 17, 0, 2) + +/* Register 18: CLK2 Control */ +__MREG__(CLK2_PDN, 18, 7, 1) +__MREG__(MS2_INT, 18, 6, 1) +__MREG__(MS2_SRC, 18, 5, 1) +__MREG__(CLK2_INV, 18, 4, 1) +__MREG__(CLK2_SRC, 18, 2, 2) +__MREG__(CLK2_IDRV, 18, 0, 2) + +/* Register 19: CLK3 Control */ +__MREG__(CLK3_PDN, 19, 7, 1) +__MREG__(MS3_INT, 19, 6, 1) +__MREG__(MS3_SRC, 19, 5, 1) +__MREG__(CLK3_INV, 19, 4, 1) +__MREG__(CLK3_SRC, 19, 2, 2) +__MREG__(CLK3_IDRV, 19, 0, 2) + +/* Register 20: CLK4 Control */ +__MREG__(CLK4_PDN, 20, 7, 1) +__MREG__(MS4_INT, 20, 6, 1) +__MREG__(MS4_SRC, 20, 5, 1) +__MREG__(CLK4_INV, 20, 4, 1) +__MREG__(CLK4_SRC, 20, 2, 2) +__MREG__(CLK4_IDRV, 20, 0, 2) + +/* Register 21: CLK5 Control */ +__MREG__(CLK5_PDN, 21, 7, 1) +__MREG__(MS5_INT, 21, 6, 1) +__MREG__(MS5_SRC, 21, 5, 1) +__MREG__(CLK5_INV, 21, 4, 1) +__MREG__(CLK5_SRC, 21, 2, 2) +__MREG__(CLK5_IDRV, 21, 0, 2) + +/* Register 22: CLK6 Control */ +__MREG__(CLK6_PDN, 22, 7, 1) +__MREG__(FBA_INT, 22, 6, 1) +__MREG__(MS6_SRC, 22, 5, 1) +__MREG__(CLK6_INV, 22, 4, 1) +__MREG__(CLK6_SRC, 22, 2, 2) +__MREG__(CLK6_IDRV, 22, 0, 2) + +/* Register 23: CLK7 Control */ +__MREG__(CLK7_PDN, 23, 7, 1) +__MREG__(FBB_INT, 23, 6, 1) +__MREG__(MS7_SRC, 23, 5, 1) +__MREG__(CLK7_INV, 23, 4, 1) +__MREG__(CLK7_SRC, 23, 2, 2) +__MREG__(CLK7_IDRV, 23, 0, 2) + +/* Register 24: CLK0-CLK3 Disable State */ +__MREG__(CLK3_DIS_STATE, 24, 6, 2) +__MREG__(CLK2_DIS_STATE, 24, 4, 2) +__MREG__(CLK1_DIS_STATE, 24, 2, 2) +__MREG__(CLK0_DIS_STATE, 24, 0, 2) + +/* Register 25: CLK4-CLK7 Disable State */ +__MREG__(CLK7_DIS_STATE, 25, 6, 2) +__MREG__(CLK6_DIS_STATE, 25, 4, 2) +__MREG__(CLK5_DIS_STATE, 25, 2, 2) +__MREG__(CLK4_DIS_STATE, 25, 0, 2) + +/* Registers 26-33: Multisynth NA Parameters */ +__MREG__(MSNA_P3_MID, 26, 0, 8) +__MREG__(MSNA_P3_BOT, 27, 0, 8) +__MREG__(MSNA_P1_TOP, 28, 0, 2) +__MREG__(MSNA_P1_MID, 29, 0, 8) +__MREG__(MSNA_P1_BOT, 30, 0, 8) +__MREG__(MSNA_P3_TOP, 31, 4, 4) +__MREG__(MSNA_P2_TOP, 31, 0, 4) +__MREG__(MSNA_P2_MID, 32, 0, 8) +__MREG__(MSNA_P2_BOT, 33, 0, 8) + +/* Registers 34-41: Multisynth NB Parameters */ +__MREG__(MSNB_P3_MID, 34, 0, 8) +__MREG__(MSNB_P3_BOT, 35, 0, 8) +__MREG__(MSNB_P1_TOP, 36, 0, 2) +__MREG__(MSNB_P1_MID, 37, 0, 8) +__MREG__(MSNB_P1_BOT, 38, 0, 8) +__MREG__(MSNB_P3_TOP, 39, 4, 4) +__MREG__(MSNB_P2_TOP, 39, 0, 4) +__MREG__(MSNB_P2_MID, 40, 0, 8) +__MREG__(MSNB_P2_BOT, 41, 0, 8) + +/* Registers 42-49: Multisynth 0 Parameters */ +__MREG__(MS0_P3_MID, 42, 0, 8) +__MREG__(MS0_P3_BOT, 43, 0, 8) +__MREG__(R0_DIV, 44, 4, 3) +__MREG__(MS0_DIVBY4, 44, 2, 2) +__MREG__(MS0_P1_TOP, 44, 0, 2) +__MREG__(MS0_P1_MID, 45, 0, 8) +__MREG__(MS0_P1_BOT, 46, 0, 8) +__MREG__(MS0_P3_TOP, 47, 4, 4) +__MREG__(MS0_P2_TOP, 47, 0, 4) +__MREG__(MS0_P2_MID, 48, 0, 8) +__MREG__(MS0_P2_BOT, 49, 0, 8) + +/* Registers 50-57: Multisynth 1 Parameters */ +__MREG__(MS1_P3_MID, 50, 0, 8) +__MREG__(MS1_P3_BOT, 51, 0, 8) +__MREG__(R1_DIV, 52, 4, 3) +__MREG__(MS1_DIVBY4, 52, 2, 2) +__MREG__(MS1_P1_TOP, 52, 0, 2) +__MREG__(MS1_P1_MID, 53, 0, 8) +__MREG__(MS1_P1_BOT, 54, 0, 8) +__MREG__(MS1_P3_TOP, 55, 4, 4) +__MREG__(MS1_P2_TOP, 55, 0, 4) +__MREG__(MS1_P2_MID, 56, 0, 8) +__MREG__(MS1_P2_BOT, 57, 0, 8) + +/* Registers 58-65: Multisynth 2 Parameters */ +__MREG__(MS2_P3_MID, 58, 0, 8) +__MREG__(MS2_P3_BOT, 59, 0, 8) +__MREG__(R2_DIV, 60, 4, 3) +__MREG__(MS2_DIVBY4, 60, 2, 2) +__MREG__(MS2_P1_TOP, 60, 0, 2) +__MREG__(MS2_P1_MID, 61, 0, 8) +__MREG__(MS2_P1_BOT, 62, 0, 8) +__MREG__(MS2_P3_TOP, 63, 4, 4) +__MREG__(MS2_P2_TOP, 63, 0, 4) +__MREG__(MS2_P2_MID, 64, 0, 8) +__MREG__(MS2_P2_BOT, 65, 0, 8) + +/* Registers 66-73: Multisynth 3 Parameters */ +__MREG__(MS3_P3_MID, 66, 0, 8) +__MREG__(MS3_P3_BOT, 67, 0, 8) +__MREG__(R3_DIV, 68, 4, 3) +__MREG__(MS3_DIVBY4, 68, 2, 2) +__MREG__(MS3_P1_TOP, 68, 0, 2) +__MREG__(MS3_P1_MID, 69, 0, 8) +__MREG__(MS3_P1_BOT, 70, 0, 8) +__MREG__(MS3_P3_TOP, 71, 4, 4) +__MREG__(MS3_P2_TOP, 71, 0, 4) +__MREG__(MS3_P2_MID, 72, 0, 8) +__MREG__(MS3_P2_BOT, 73, 0, 8) + +/* Registers 74-81: Multisynth 4 Parameters */ +__MREG__(MS4_P3_MID, 74, 0, 8) +__MREG__(MS4_P3_BOT, 75, 0, 8) +__MREG__(R4_DIV, 76, 4, 3) +__MREG__(MS4_DIVBY4, 76, 2, 2) +__MREG__(MS4_P1_TOP, 76, 0, 2) +__MREG__(MS4_P1_MID, 77, 0, 8) +__MREG__(MS4_P1_BOT, 78, 0, 8) +__MREG__(MS4_P3_TOP, 79, 4, 4) +__MREG__(MS4_P2_TOP, 79, 0, 4) +__MREG__(MS4_P2_MID, 80, 0, 8) +__MREG__(MS4_P2_BOT, 81, 0, 8) + +/* Registers 82-89: Multisynth 5 Parameters */ +__MREG__(MS5_P3_MID, 82, 0, 8) +__MREG__(MS5_P3_BOT, 83, 0, 8) +__MREG__(R5_DIV, 84, 4, 3) +__MREG__(MS5_DIVBY4, 84, 2, 2) +__MREG__(MS5_P1_TOP, 84, 0, 2) +__MREG__(MS5_P1_MID, 85, 0, 8) +__MREG__(MS5_P1_BOT, 86, 0, 8) +__MREG__(MS5_P3_TOP, 87, 4, 4) +__MREG__(MS5_P2_TOP, 87, 0, 4) +__MREG__(MS5_P2_MID, 88, 0, 8) +__MREG__(MS5_P2_BOT, 89, 0, 8) + +/* Register 90: Multisynth 6 Parameters */ +__MREG__(MS6_P1, 90, 0, 8) + +/* Register 91: Multisynth 7 Parameters */ +__MREG__(MS7_P1, 91, 0, 8) + +/* Register 92: Clock 6 and 7 Output Divider */ +__MREG__(R7_DIV, 92, 4, 3) +__MREG__(R6_DIV, 92, 0, 3) + +/* Registers 149-161: Spread Spectrum Parameters */ +__MREG__(SSC_EN, 149, 7, 1) +__MREG__(SSDN_P2_TOP, 149, 0, 7) +__MREG__(SSDN_P2_BOT, 150, 0, 8) +__MREG__(SSC_MODE, 151, 7, 1) +__MREG__(SSDN_P3_TOP, 151, 0, 7) +__MREG__(SSDN_P3_BOT, 152, 0, 8) +__MREG__(SSDN_P1_BOT, 153, 0, 8) +__MREG__(SSUDP_TOP, 154, 4, 4) +__MREG__(SSDN_P1_TOP, 154, 0, 4) +__MREG__(SSUDP_BOT, 155, 0, 8) +__MREG__(SSUP_P2_TOP, 156, 0, 7) +__MREG__(SSUP_P2_BOT, 157, 0, 8) +__MREG__(SSUP_P3_TOP, 158, 0, 8) +__MREG__(SSUP_P3_BOT, 159, 0, 8) +__MREG__(SSUP_P1_BOT, 160, 0, 8) +__MREG__(SS_NCLK, 161, 4, 4) +__MREG__(SSUP_P1_TOP, 161, 0, 4) + +/* Registers 162-163: VCXO Parameter */ +__MREG__(VCXO_PARAM_BOT, 162, 0, 8) +__MREG__(VCXO_PARAM_MID, 163, 0, 8) +__MREG__(VCXO_PARAM_TOP, 164, 0, 6) + +/* Registers 165-170: CLK0-CLK5 Initial Phase Offsets */ +__MREG__(CLK0_PHOFF, 165, 0, 7) +__MREG__(CLK1_PHOFF, 166, 0, 7) +__MREG__(CLK2_PHOFF, 167, 0, 7) +__MREG__(CLK3_PHOFF, 168, 0, 7) +__MREG__(CLK4_PHOFF, 169, 0, 7) +__MREG__(CLK5_PHOFF, 170, 0, 7) + +/* Register 177: PLL Reset */ +__MREG__(PLLB_RST, 177, 7, 1) +__MREG__(PLLA_RST, 177, 5, 1) + +/* Register 183: Crystal Internal Load Capacitance */ +__MREG__(XTAL_CL, 183, 6, 2) + +/* Register 187: Fanout Enable */ +__MREG__(CLKIN_FANOUT_EN, 187, 7, 1) +__MREG__(XO_FANOUT_EN, 187, 6, 1) +__MREG__(MS_FANOUT_EN, 187, 4, 1) + +/* 3-part parameters */ +__3PART__(MSNA_P1) +__3PART__(MSNA_P2) +__3PART__(MSNA_P3) +__3PART__(MSNB_P1) +__3PART__(MSNB_P2) +__3PART__(MSNB_P3) +__3PART__(MS0_P1) +__3PART__(MS0_P2) +__3PART__(MS0_P3) +__3PART__(MS1_P1) +__3PART__(MS1_P2) +__3PART__(MS1_P3) +__3PART__(MS2_P1) +__3PART__(MS2_P2) +__3PART__(MS2_P3) +__3PART__(MS3_P1) +__3PART__(MS3_P2) +__3PART__(MS3_P3) +__3PART__(MS4_P1) +__3PART__(MS4_P2) +__3PART__(MS4_P3) +__3PART__(MS5_P1) +__3PART__(MS5_P2) +__3PART__(MS5_P3) + +/* 2-part parameters */ +__2PART__(SSDN_P1) +__2PART__(SSDN_P2) +__2PART__(SSDN_P3) +__2PART__(SSUP_P1) +__2PART__(SSUP_P2) +__2PART__(SSUP_P3) +__2PART__(SSUDP) + +/* Per-PLL register groups */ +__PER_PLL__(LOL, LOL_, ) +__PER_PLL__(LOL_STKY, LOL_, _STKY) +__PER_PLL__(LOL_MASK, LOL_, _MASK) +__PER_PLL__(PLL_SRC, PLL, _SRC) +__PER_PLL__(MSN_P1, MSN, _P1) +__PER_PLL__(MSN_P2, MSN, _P2) +__PER_PLL__(MSN_P3, MSN, _P3) +__PER_PLL__(PLL_RST, PLL, _RST) + +/* Per-multisynth register groups */ +__PER_MS__(CLK_OEB, CLK, _OEB) +__PER_MS__(OEB_CLK, OEB_CLK, ) +__PER_MS__(OEB_MASK, OEB_MASK, ) +__PER_MS__(CLK_PDN, CLK, _PDN) +__PER_MS__(MS_SRC, MS, _SRC) +__PER_MS__(CLK_INV, CLK, _INV) +__PER_MS__(CLK_SRC, CLK, _SRC) +__PER_MS__(CLK_IDRV, CLK, _IDRV) +__PER_MS__(CLK_DIS_STATE, CLK, _DIS_STATE) +__PER_MS__(R_DIV, R, _DIV) +__PER_MS__(MS_P1, MS, _P1) +__PER_FRAC_MS__(MS_INT, MS, _INT) +__PER_FRAC_MS__(MS_P2, MS, _P2) +__PER_FRAC_MS__(MS_P3, MS, _P3) +__PER_FRAC_MS__(MS_DIVBY4, MS, _DIVBY4) +__PER_FRAC_MS__(CLK_PHOFF, CLK, _PHOFF) From 016910567bc236fcfb14ebb741185c468416cda8 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sun, 3 May 2026 09:32:58 +0100 Subject: [PATCH 119/161] si5351c: internal: reimplement using register helpers. --- firmware/common/si5351c.c | 480 +++++++++++++++++++++++--------------- firmware/common/si5351c.h | 72 ++++-- 2 files changed, 336 insertions(+), 216 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 30c23129..1f27d8db 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Great Scott Gadgets + * Copyright 2012-2026 Great Scott Gadgets * Copyright 2012 Jared Boone * * This file is part of HackRF. @@ -22,6 +22,7 @@ #include #include +#include #include "clock_io.h" #include "delay.h" @@ -36,6 +37,8 @@ #include "platform_scu.h" #endif +#include "si5351c_regs.def" + /* Driver instance. */ // const i2c_lpc_config_t i2c_config_si5351c_slow_clock = { // .duty_cycle_count = 15, @@ -52,13 +55,16 @@ static bool input_initialized = false; static si5351c_input_t active_input; /* External clock output default is deactivated as it creates noise */ static bool clkout_enabled = false; -static uint8_t outputs_disabled = 0xff; /* write to single register */ void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val) { const uint8_t data_tx[] = {reg, val}; - si5351c_write(drv, data_tx, 2); + i2c_bus_transfer(drv->bus, drv->i2c_address, data_tx, 2, NULL, 0); + if (reg < SI5351C_CACHED_REGS) { + drv->regs[reg] = val; + si5351c_reg_set_clean(drv, reg); + } } /* read single register */ @@ -67,72 +73,100 @@ uint8_t si5351c_read_single(si5351c_driver_t* const drv, uint8_t reg) const uint8_t data_tx[] = {reg}; uint8_t data_rx[] = {0x00}; i2c_bus_transfer(drv->bus, drv->i2c_address, data_tx, 1, data_rx, 1); - return data_rx[0]; + uint8_t val = data_rx[0]; + if (reg < SI5351C_CACHED_REGS) { + drv->regs[reg] = val; + si5351c_reg_set_clean(drv, reg); + } + return val; } -/* - * Write to one or more contiguous registers. data[0] should be the first - * register number, one or more values follow. - */ -void si5351c_write( - si5351c_driver_t* const drv, - const uint8_t* const data, - const size_t data_count) +/* Commit changes to register values. */ +void si5351c_regs_commit(si5351c_driver_t* drv) { - i2c_bus_transfer(drv->bus, drv->i2c_address, data, data_count, NULL, 0); + for (int start = 0; start < SI5351C_CACHED_REGS; start++) { + if (si5351c_reg_is_dirty(drv, start)) { + if (start == SI5351C_CACHED_REGS - 1) { + si5351c_write_single(drv, start, drv->regs[start]); + } else { + int end; + for (end = start + 1; end < 256; end++) { + if (!si5351c_reg_is_dirty(drv, end)) + break; + } + size_t len = 1 + (end - start); + uint8_t data_tx[len]; + data_tx[0] = start; + for (int i = 0; i < (end - start); i++) { + data_tx[1 + i] = drv->regs[start + i]; + } + i2c_bus_transfer( + drv->bus, + drv->i2c_address, + data_tx, + len, + NULL, + 0); + for (int i = start; i < end; i++) { + si5351c_reg_set_clean(drv, i); + } + start = end; + } + } + } + + /* Reset bits are self-clearing. */ + drv->regs[177] = 0; } /* Disable all CLKx outputs. */ void si5351c_disable_all_outputs(si5351c_driver_t* const drv) { - outputs_disabled = 0xff; - uint8_t data[] = {3, outputs_disabled}; - si5351c_write(drv, data, sizeof(data)); + set_all_CLK_OEB(drv, SI5351C_OUTPUT_DISABLE); + si5351c_regs_commit(drv); } /* Disable all CLKx outputs using selected PLL. */ void si5351c_disable_pll_outputs(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) { - /* - * Bitmask defines outputs using PLL B. Other outputs are assumed to - * use PLL A. - */ - uint8_t pllb_outputs = 0x00; - if (detected_platform() == BOARD_ID_PRALINE) { - pllb_outputs = 0x30; + /* For each CLKx output, check if it is using the specified PLL. */ + for (int op = 0; op < 8; op++) { + int ms; + /* First check which multisynth is used by this output. */ + switch (get_CLK_SRC(drv, op)) { + case SI5351C_SRC_MULTISYNTH_SELF: + ms = op; + break; + case SI5351C_SRC_MULTISYNTH_0_4: + ms = (op < 4) ? 0 : 4; + break; + default: + /* This output is not using any PLL */ + continue; + } + /* Now check which PLL is used by that multisynth. */ + if (mask & (1 << get_MS_SRC(drv, ms))) { + /* This output depends on the specified PLL; disable it. */ + set_CLK_OEB(drv, op, SI5351C_OUTPUT_DISABLE); + } } - - if (mask & SI5351C_PLL_MASK_A) { - outputs_disabled |= ~pllb_outputs; - } - if (mask & SI5351C_PLL_MASK_B) { - outputs_disabled |= pllb_outputs; - } - uint8_t data[] = {3, outputs_disabled}; - si5351c_write(drv, data, sizeof(data)); + si5351c_regs_commit(drv); } /* Turn off OEB pin control for all CLKx */ void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv) { - uint8_t data[] = {9, 0xFF}; - si5351c_write(drv, data, sizeof(data)); + set_all_OEB_MASK(drv, true); + si5351c_regs_commit(drv); } /* Power down all CLKx */ void si5351c_power_down_all_clocks(si5351c_driver_t* const drv) { - uint8_t data[] = { - 16, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN, - SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE, - SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE}; - si5351c_write(drv, data, sizeof(data)); + set_all_CLK_PDN(drv, true); + set_FBA_INT(drv, true); + set_FBB_INT(drv, true); + si5351c_regs_commit(drv); } /* @@ -142,8 +176,8 @@ void si5351c_power_down_all_clocks(si5351c_driver_t* const drv) */ void si5351c_set_crystal_configuration(si5351c_driver_t* const drv) { - uint8_t data[] = {183, 0x80}; - si5351c_write(drv, data, sizeof(data)); + set_XTAL_CL(drv, SI5351C_XTAL_8PF); + si5351c_regs_commit(drv); } /* @@ -152,8 +186,10 @@ void si5351c_set_crystal_configuration(si5351c_driver_t* const drv) */ void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv) { - uint8_t data[] = {187, 0xD0}; - si5351c_write(drv, data, sizeof(data)); + set_CLKIN_FANOUT_EN(drv, true); + set_XO_FANOUT_EN(drv, true); + set_MS_FANOUT_EN(drv, true); + si5351c_regs_commit(drv); } /* @@ -163,12 +199,10 @@ void si5351c_enable_xo_and_ms_fanout(si5351c_driver_t* const drv) */ void si5351c_configure_inputs(si5351c_driver_t* const drv, const si5351c_input_t input) { - uint8_t data[] = {15, 0x00}; - - if (input == SI5351C_INPUT_CLKIN) { - data[1] = 0x0c; - } - si5351c_write(drv, data, sizeof(data)); + set_CLKIN_DIV(drv, SI5351C_DIV_1); + set_PLLA_SRC(drv, input); + set_PLLB_SRC(drv, input); + si5351c_regs_commit(drv); } /* MultiSynth NA (PLLA) and NB (PLLB) */ @@ -176,33 +210,26 @@ void si5351c_configure_pll_multisynth( si5351c_driver_t* const drv, const si5351c_input_t input) { - /* XTAL: 25 MHz * (0x0e00 + 512) / 128 = 800 MHz, integer mode */ - uint8_t data[] = {26, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00}; - if (input == SI5351C_INPUT_CLKIN) { - /* CLKIN: 10 MHz * (0x2600 + 512) / 128 = 800 MHz, integer mode */ - data[4] = 0x26; + for (si5351c_pll_t pll = SI5351C_PLL_A; pll <= SI5351C_PLL_B; pll++) { + if (input == SI5351C_INPUT_CLKIN) { + /* CLKIN: 10 MHz * (0x2600 + 512) / 128 = 800 MHz, integer mode */ + set_MSN_P1(drv, pll, 0x2600); + } else { + /* XTAL: 25 MHz * (0x0e00 + 512) / 128 = 800 MHz, integer mode */ + set_MSN_P1(drv, pll, 0x0E00); + } + set_MSN_P2(drv, pll, 0); + set_MSN_P3(drv, pll, 1); } - si5351c_write(drv, data, sizeof(data)); - - /* Apply same configuration to PLL B. */ - data[0] = 34; - si5351c_write(drv, data, sizeof(data)); + si5351c_regs_commit(drv); } void si5351c_reset_plls(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) { - uint8_t value = 0; - - if (mask & SI5351C_PLL_MASK_A) { - value |= 0x20; - } - if (mask & SI5351C_PLL_MASK_B) { - value |= 0x80; - } - si5351c_disable_pll_outputs(drv, mask); - uint8_t data[] = {177, value}; - si5351c_write(drv, data, sizeof(data)); + set_PLLA_RST(drv, (mask & SI5351C_PLL_MASK_A) ? true : false); + set_PLLB_RST(drv, (mask & SI5351C_PLL_MASK_B) ? true : false); + si5351c_regs_commit(drv); delay_us_at_mhz(2000, 204); si5351c_enable_clock_outputs(drv); } @@ -227,110 +254,166 @@ void si5351c_configure_multisynth( * ... * 7 means divide by 128 */ - const uint_fast8_t register_number = 42 + (ms_number * 8); - uint8_t data[] = { - register_number, - (p3 >> 8) & 0xFF, - (p3 >> 0) & 0xFF, - (r_div << 4) | (0 << 2) | ((p1 >> 16) & 0x3), - (p1 >> 8) & 0xFF, - (p1 >> 0) & 0xFF, - (((p3 >> 16) & 0xF) << 4) | (((p2 >> 16) & 0xF) << 0), - (p2 >> 8) & 0xFF, - (p2 >> 0) & 0xFF}; - si5351c_write(drv, data, sizeof(data)); + set_MS_P1(drv, ms_number, p1); + set_MS_P2(drv, ms_number, p2); + set_MS_P3(drv, ms_number, p3); + set_R_DIV(drv, ms_number, r_div); + si5351c_regs_commit(drv); } void si5351c_configure_clock_control(si5351c_driver_t* const drv) { - uint8_t clkout_ctrl; + si5351c_clk_ctrl_t clkout = { + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_8MA, + }; - if (clkout_enabled) { - clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); - } else { - clkout_ctrl = SI5351C_CLK_POWERDOWN | SI5351C_CLK_INT_MODE; + si5351c_clk_ctrl_t powered_down = { + .power_down = true, + .mode = SI5351C_MODE_INT, + }; + + if (!clkout_enabled) { + clkout = powered_down; } + si5351c_clk_ctrl_t clk[8]; + /* Clock to CPU is deactivated as it is not used and creates noise */ /* External clock output is kept in current state */ - uint8_t data[] = { - 16, - SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA), - SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_0_4) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA) | SI5351C_CLK_INV, - SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_0_4) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA), - clkout_ctrl, - SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA) | SI5351C_CLK_INV, - SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA), - SI5351C_CLK_POWERDOWN | - SI5351C_CLK_INT_MODE, /* not connected, but: PLL A int mode */ - SI5351C_CLK_POWERDOWN | - SI5351C_CLK_INT_MODE /* not connected, but: PLL B int mode */ + + /* CLK0: MAX5864/CPLD */ + clk[0] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_8MA, }; + /* CLK1: CPLD */ + clk[1] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_0_4, + .drive = SI5351C_DRIVE_2MA, + .invert = true, + }; + /* CLK2: SGPIO */ + clk[2] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_0_4, + .drive = SI5351C_DRIVE_2MA, + }; + /* CLK3: CLKOUT */ + clk[3] = clkout; + /* CLK4: RFFC5072 (MAX2837 on rad1o) */ + clk[4] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_6MA, + .invert = true, + }; + /* CLK5: MAX2837 (MAX2871 on rad1o) */ + clk[5] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; + /* CLK6: none */ + clk[6] = powered_down; + /* CLK7: LPC43xx */ + clk[7] = powered_down; + #ifdef IS_H1_R9 if (IS_H1_R9) { - data[1] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_6MA); - data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); - data[3] = clkout_ctrl; - data[4] = SI5351C_CLK_POWERDOWN; - data[5] = SI5351C_CLK_POWERDOWN; - data[6] = SI5351C_CLK_POWERDOWN; + /* CLK0: MAX5864/CPLD/SGPIO (sample clocks) */ + clk[0] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_6MA, + }; + /* CLK1: RFFC5072/MAX2839 */ + clk[1] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; + /* CLK2: CLKOUT/LPC4320 */ + clk[2] = clkout; + /* Other outputs not present on Si5351A */ + clk[3] = powered_down; + clk[4] = powered_down; + clk[5] = powered_down; + clk[6] = powered_down; + clk[7] = powered_down; } #endif #ifdef IS_PRALINE if (IS_PRALINE) { /* CLK0: AFE_CLK */ - data[1] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); + clk[0] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; /* CLK1: SCT_CLK and FPGA_CLK */ - data[2] = SI5351C_CLK_FRAC_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); + clk[1] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_2MA, + }; /* CLK3: CLKOUT */ - clkout_ctrl = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_8MA); + clkout.pll = SI5351C_PLL_B; + clk[3] = clkout; /* CLK4: XCVR_CLK */ - data[5] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA) | SI5351C_CLK_INV; - data[6] = SI5351C_CLK_INT_MODE | SI5351C_CLK_PLL_SRC(SI5351C_PLL_B) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_4MA); + clk[4] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_B, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + .invert = true, + }; + /* CLK5: MIX_CLK */ + clk[5] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_B, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - data[3] = SI5351C_CLK_FRAC_MODE | - SI5351C_CLK_PLL_SRC(SI5351C_PLL_A) | - SI5351C_CLK_SRC(SI5351C_CLK_SRC_MULTISYNTH_SELF) | - SI5351C_CLK_IDRV(SI5351C_CLK_IDRV_2MA); + clk[2] = (si5351c_clk_ctrl_t){ + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_2MA, + }; } else { /* CLK2: MCU_CLK */ - data[3] = SI5351C_CLK_POWERDOWN; + clk[2] = powered_down; } } #endif - si5351c_write(drv, data, sizeof(data)); -} -#define SI5351C_CLK_ENABLE(x) (0 << x) -#define SI5351C_CLK_DISABLE(x) (1 << x) -#define SI5351C_REG_OUTPUT_EN (3) + for (int i = 0; i < 8; i++) { + set_CLK_PDN(drv, i, clk[i].power_down); + set_MS_INT(drv, i, clk[i].mode); + set_MS_SRC(drv, i, clk[i].pll); + set_CLK_SRC(drv, i, clk[i].source); + set_CLK_IDRV(drv, i, clk[i].drive); + set_CLK_INV(drv, i, clk[i].invert); + } + + si5351c_regs_commit(drv); +} void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) { @@ -340,46 +423,61 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) /* 3: External clock output is deactivated by default */ uint8_t clkout = 3; - uint8_t value = 0; + + bool enable[8]; #ifdef IS_PRALINE if (IS_PRALINE) { - value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_ENABLE(4) | SI5351C_CLK_ENABLE(5) | - SI5351C_CLK_DISABLE(6) | SI5351C_CLK_DISABLE(7); + enable[0] = true; + enable[1] = true; if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - value |= SI5351C_CLK_ENABLE(2); + enable[2] = true; } else { - value |= SI5351C_CLK_DISABLE(2); + enable[2] = false; } + enable[3] = false; + enable[4] = true; + enable[5] = true; + enable[6] = false; + enable[7] = false; } #endif #ifdef IS_NOT_PRALINE if (IS_NOT_PRALINE) { - value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_ENABLE(2) | SI5351C_CLK_ENABLE(4) | - SI5351C_CLK_ENABLE(5) | SI5351C_CLK_DISABLE(6) | - SI5351C_CLK_DISABLE(7); + enable[0] = true; + enable[1] = true; + enable[2] = true; + enable[3] = false; + enable[4] = true; + enable[5] = true; + enable[6] = false; + enable[7] = false; /* HackRF One r9 has only three clock generator outputs. */ #ifdef IS_H1_R9 if (IS_H1_R9) { clkout = 2; - value = SI5351C_CLK_ENABLE(0) | SI5351C_CLK_ENABLE(1) | - SI5351C_CLK_DISABLE(3) | SI5351C_CLK_DISABLE(4) | - SI5351C_CLK_DISABLE(5) | SI5351C_CLK_DISABLE(6) | - SI5351C_CLK_DISABLE(7); + enable[0] = true; + enable[1] = true; + enable[3] = false; + enable[4] = false; + enable[5] = false; + enable[6] = false; + enable[7] = false; } #endif } #endif + enable[clkout] = clkout_enabled; - value |= (clkout_enabled) ? SI5351C_CLK_ENABLE(clkout) : - SI5351C_CLK_DISABLE(clkout); - uint8_t data[] = {SI5351C_REG_OUTPUT_EN, value}; - si5351c_write(drv, data, sizeof(data)); - outputs_disabled = value; + for (int i = 0; i < 8; i++) { + set_CLK_OEB( + drv, + i, + enable[i] ? SI5351C_OUTPUT_ENABLE : SI5351C_OUTPUT_DISABLE); + } + si5351c_regs_commit(drv); #ifdef IS_H1_R9 if (IS_H1_R9) { @@ -398,20 +496,8 @@ void si5351c_set_int_mode( const uint_fast8_t ms_number, const uint_fast8_t on) { - uint8_t data[] = {16, 0}; - - if (ms_number < 8) { - data[0] = 16 + ms_number; - data[1] = si5351c_read_single(drv, data[0]); - - if (on) { - data[1] |= SI5351C_CLK_INT_MODE; - } else { - data[1] &= ~(SI5351C_CLK_INT_MODE); - } - - si5351c_write(drv, data, 2); - } + set_MS_INT(drv, ms_number, on); + si5351c_regs_commit(drv); } void si5351c_change_input(si5351c_driver_t* const drv, si5351c_input_t input) @@ -451,7 +537,9 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) uint32_t f = clkin_frequency(); return (f > 9000000) && (f < 11000000); } else { - return (si5351c_read_single(drv, 0) & SI5351C_LOS) == 0; + si5351c_read_single(drv, LOS_CLKIN); + bool los_clkin = get_LOS_CLKIN(drv); + return !los_clkin; } } @@ -475,7 +563,8 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable) void si5351c_init(si5351c_driver_t* const drv) { /* Read revision ID */ - selftest.si5351_rev_id = si5351c_read_single(drv, 0) & SI5351C_REVID; + si5351c_read_single(drv, REVID); + selftest.si5351_rev_id = get_REVID(drv); /* Read back interrupt status mask register, flip the mask bits and verify. */ uint8_t int_mask = si5351c_read_single(drv, 2); @@ -494,6 +583,17 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } + /* Cache all current register values. */ + uint8_t data_tx[] = {0}; + i2c_bus_transfer( + drv->bus, + drv->i2c_address, + data_tx, + 1, + drv->regs, + sizeof(drv->regs)); + memset(drv->regs_dirty, 0, sizeof(drv->regs_dirty)); + #ifdef IS_H1_R9 if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); @@ -529,8 +629,6 @@ void si5351c_set_phase( const uint8_t ms_number, const uint8_t offset) { - const uint8_t address = 165 + ms_number; - if (ms_number < 8) { - si5351c_write_single(drv, address, offset & 0x7f); - } + set_CLK_PHOFF(drv, ms_number, offset); + si5351c_regs_commit(drv); } diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index 491266cf..5be31788 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Great Scott Gadgets + * Copyright 2012-2026 Great Scott Gadgets * Copyright 2012 Jared Boone * * This file is part of HackRF. @@ -28,47 +28,62 @@ extern "C" { #include #include -#include #include "i2c_bus.h" #include "i2c_lpc.h" #define SI_INTDIV(x) (x * 128 - 512) -#define SI5351C_CLK_POWERDOWN (1 << 7) -#define SI5351C_CLK_INT_MODE (1 << 6) -#define SI5351C_CLK_FRAC_MODE (0 << 6) +#define SI5351C_CACHED_REGS 188 -#define SI5351C_CLK_INV (1 << 4) +typedef enum { + SI5351C_MODE_FRAC = 0, + SI5351C_MODE_INT = 1, +} si5351c_mode_t; -#define SI5351C_CLK_SRC(x) (x << 2) -#define SI5351C_CLK_SRC_XTAL 0 -#define SI5351C_CLK_SRC_CLKIN 1 -#define SI5351C_CLK_SRC_MULTISYNTH_0_4 2 -#define SI5351C_CLK_SRC_MULTISYNTH_SELF 3 +typedef enum { + SI5351C_SRC_XTAL = 0, + SI5351C_SRC_CLKIN = 1, + SI5351C_SRC_MULTISYNTH_0_4 = 2, + SI5351C_SRC_MULTISYNTH_SELF = 3, +} si5351c_src_t; -#define SI5351C_CLK_IDRV(x) (x << 0) -#define SI5351C_CLK_IDRV_2MA 0 -#define SI5351C_CLK_IDRV_4MA 1 -#define SI5351C_CLK_IDRV_6MA 2 -#define SI5351C_CLK_IDRV_8MA 3 - -#define SI5351C_LOS (1 << 4) -#define SI5351C_REVID 0x03 +typedef enum { + SI5351C_DRIVE_2MA = 0, + SI5351C_DRIVE_4MA = 1, + SI5351C_DRIVE_6MA = 2, + SI5351C_DRIVE_8MA = 3, +} si5351c_drive_t; typedef enum { SI5351C_PLL_A = 0, SI5351C_PLL_B = 1, } si5351c_pll_t; -#define SI5351C_CLK_PLL_SRC(x) ((x & SI5351C_PLL_B) << 5) - typedef enum { SI5351C_PLL_MASK_A = 1, SI5351C_PLL_MASK_B = 2, SI5351C_PLL_MASK_BOTH = 3, } si5351c_pll_mask_t; +typedef enum { + SI5351C_XTAL_6PF = 1, + SI5351C_XTAL_8PF = 2, + SI5351C_XTAL_10PF = 3, +} si5351c_xtal_t; + +typedef enum { + SI5351C_OUTPUT_ENABLE = 0, + SI5351C_OUTPUT_DISABLE = 1, +} si5351c_output_t; + +typedef enum { + SI5351C_DIV_1 = 0, + SI5351C_DIV_2 = 1, + SI5351C_DIV_4 = 2, + SI5351C_DIV_8 = 3, +} si5351c_div_t; + typedef enum { SI5351C_INPUT_XTAL = 0, SI5351C_INPUT_CLKIN = 1, @@ -77,8 +92,19 @@ typedef enum { typedef struct { i2c_bus_t* const bus; uint8_t i2c_address; + uint8_t regs[SI5351C_CACHED_REGS]; + uint32_t regs_dirty[(SI5351C_CACHED_REGS + 31) / 32]; } si5351c_driver_t; +typedef struct { + bool power_down; + si5351c_mode_t mode; + si5351c_pll_t pll; + si5351c_src_t source; + si5351c_drive_t drive; + bool invert; +} si5351c_clk_ctrl_t; + void si5351c_disable_all_outputs(si5351c_driver_t* const drv); void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv); void si5351c_power_down_all_clocks(si5351c_driver_t* const drv); @@ -107,10 +133,6 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv); void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val); uint8_t si5351c_read_single(si5351c_driver_t* const drv, uint8_t reg); -void si5351c_write( - si5351c_driver_t* const drv, - const uint8_t* const data, - const size_t data_count); void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable); void si5351c_init(si5351c_driver_t* const drv); void si5351c_set_phase( From 478a520d65f2f822bac6baf9a7387c19720e3c12 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 18 May 2026 14:42:16 +0100 Subject: [PATCH 120/161] Add script to extract font data to an image file for editing. --- firmware/tools/edit-font.py | 107 ++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 firmware/tools/edit-font.py diff --git a/firmware/tools/edit-font.py b/firmware/tools/edit-font.py new file mode 100644 index 00000000..9e03467a --- /dev/null +++ b/firmware/tools/edit-font.py @@ -0,0 +1,107 @@ +# Tool to extract font data, save to bitmap file, and apply modifications +# to source code after editing the bitmap. + +from __future__ import print_function +from matplotlib.pyplot import imread, imsave, imshow, show +import numpy as np +import sys +import re + +if len(sys.argv) != 3: + print("Usage: %s { show | extract | insert}" % sys.argv[0]) + sys.exit(1) + +font_name = sys.argv[1] +mode = sys.argv[2] + +# Where to find the font data - may need updated if code has changed. +source_file = 'firmware/common/ui_portapack.c' +data_marker = f'static const uint8_t {font_name}_glyph_data[] = {{' +data_offset = 1 # Data starts this number of lines after start marker. +end_pattern = '(.*)};$' # Indicates end of font data. +def_marker = f'static const ui_font_t {font_name} =' +def_offset = 1 +def_pattern = '^\t{{(?P\\d+), (?P\\d+)}' + +# Image filename used in extract and insert modes. +image_file = f'{font_name}.png' + +# Get the literals used to populate the font array in the source file +lines = [line.rstrip() for line in open(source_file).readlines()] +try: + start = lines.index(data_marker) + data_offset +except ValueError: + print(f"Could not find glyph data for font '{font_name}'") + sys.exit(1) + +data = str() +for line in lines[start:]: + if match := re.match(end_pattern, line): + data += match.group(1) + break + else: + data += line + +# Get the width and height from the font definition. +try: + def_start = lines.index(def_marker) +except ValueError: + print(f"Could not find definition for font '{font_name}'") + sys.exit(1) + +def_match = re.match(def_pattern, lines[def_start + def_offset]) + +if def_match is None: + print(f"Failed to parse definition of '{font_name}'") + +width = int(def_match.group('width')) +height = int(def_match.group('height')) + +# Eval the literals to get the values into a numpy array +packed = eval("np.array([%s], np.uint8)" % data) + +# Reorganise into a monochrome image. +chars_per_row = 5 +unpacked = np.unpackbits(packed) +bitmaps = (unpacked + .reshape(-1, height, width // 8, 8)[:,:,::-1,:] + .reshape(-1, height, width)[:,:,::-1] +) +indices = np.arange(len(bitmaps)).reshape(-1, chars_per_row) +image = np.block([[bitmaps[idx] for idx in row] for row in indices]) + +if mode == 'show': + # Display font image + imshow(image, cmap='binary') + show() + +elif mode == 'extract': + # Save font image + imsave(image_file, image, format='png', cmap='binary') + +elif mode == 'insert': + # Read in modified font image + image = imread(image_file)[:,:,0].astype(bool) + + # Reorganise back to original order + rows = image.reshape(-1, height, image.shape[1]) + single_row_image = np.hstack(rows) + num_chars = single_row_image.shape[1] // width + bitmaps = np.array(np.split(single_row_image, num_chars, 1)) + unpacked = (bitmaps + [:,:,::-1].reshape(-1, height, width // 8, 8) + [:,:,::-1,:].reshape(-1) + ) + packed = ~np.packbits(unpacked) + + # Replace lines of file in same format as used before + bytes_per_row = 13 + split_indices = np.arange(bytes_per_row, len(packed), bytes_per_row) + for i, group in enumerate(np.split(packed, split_indices)): + line = ("\t0x%02x," + " 0x%02x," * (len(group) - 1)) % tuple(group) + if len(group) < bytes_per_row: + line = line[:-1] + "};" + lines[start + i] = line + + # Write out modified source file + open(source_file, 'w').writelines([line + "\n" for line in lines]) From dc255468b1ecaf8141d68067addb1e5c03f452ce Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 18 May 2026 17:26:27 +0100 Subject: [PATCH 121/161] Fix up IWYU issues in cpld_jtag.{c,h}. --- firmware/common/cpld_jtag.c | 3 ++- firmware/common/cpld_jtag.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index 4d313d01..e6413ba4 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -21,11 +21,12 @@ #include "cpld_jtag.h" -#include "cpld_xc2c.h" #include "platform_detect.h" #ifdef IS_NOT_PRALINE + #include #include #include "xapp058/micro.h" + #include "cpld_xc2c.h" #endif #ifdef IS_NOT_PRALINE diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index b9a4b52f..c1f707d4 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -21,6 +21,7 @@ #pragma once +#include #include #include "gpio.h" From 50ce90908f6a6cbbe03778b21f9c0a564242bf09 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Sat, 9 May 2026 06:21:44 +0100 Subject: [PATCH 122/161] si5351c: Wait for SYS_INIT flag to clear during startup. --- firmware/common/si5351c.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 1f27d8db..f356f320 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -583,6 +583,11 @@ void si5351c_init(si5351c_driver_t* const drv) selftest.report.pass = false; } + /* Wait for on-chip initialization to complete. */ + while (get_SYS_INIT(drv)) { + si5351c_read_single(drv, SYS_INIT); + } + /* Cache all current register values. */ uint8_t data_tx[] = {0}; i2c_bus_transfer( From 4c69adb9dab1f0df34d93a6ea2a4715fb92f1ec1 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 8 May 2026 14:54:03 +0100 Subject: [PATCH 123/161] si5351c: API: change argument of si5351c_clkout_enable to bool. --- firmware/common/si5351c.c | 4 ++-- firmware/common/si5351c.h | 2 +- firmware/hackrf_usb/usb_api_register.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index f356f320..d053d601 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -543,9 +543,9 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) } } -void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable) +void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable) { - clkout_enabled = (enable > 0); + clkout_enabled = enable; uint8_t clkout = 3; diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index 5be31788..c357442f 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -133,7 +133,7 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv); void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val); uint8_t si5351c_read_single(si5351c_driver_t* const drv, uint8_t reg); -void si5351c_clkout_enable(si5351c_driver_t* const drv, uint8_t enable); +void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable); void si5351c_init(si5351c_driver_t* const drv); void si5351c_set_phase( si5351c_driver_t* const drv, diff --git a/firmware/hackrf_usb/usb_api_register.c b/firmware/hackrf_usb/usb_api_register.c index 1a9f0238..626c7508 100644 --- a/firmware/hackrf_usb/usb_api_register.c +++ b/firmware/hackrf_usb/usb_api_register.c @@ -211,7 +211,7 @@ usb_request_status_t usb_vendor_request_set_clkout_enable( const usb_transfer_stage_t stage) { if (stage == USB_TRANSFER_STAGE_SETUP) { - si5351c_clkout_enable(&si5351c, endpoint->setup.value); + si5351c_clkout_enable(&si5351c, endpoint->setup.value > 0); usb_transfer_schedule_ack(endpoint->in); } return USB_REQUEST_STATUS_OK; From 9423133c4b94957665991c3dfa30c93ed474e045 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 8 May 2026 14:40:28 +0100 Subject: [PATCH 124/161] si5351c: Make clk array persistent, setup in init, include OE flags. --- firmware/common/si5351c.c | 365 +++++++++++++++++--------------------- firmware/common/si5351c.h | 3 +- 2 files changed, 161 insertions(+), 207 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index d053d601..50d39583 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -51,10 +51,12 @@ si5351c_driver_t si5351c = { .i2c_address = 0x60, }; +si5351c_clk_t clk[8]; + +uint8_t clkout_id; + static bool input_initialized = false; static si5351c_input_t active_input; -/* External clock output default is deactivated as it creates noise */ -static bool clkout_enabled = false; /* write to single register */ void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val) @@ -263,146 +265,6 @@ void si5351c_configure_multisynth( void si5351c_configure_clock_control(si5351c_driver_t* const drv) { - si5351c_clk_ctrl_t clkout = { - .mode = SI5351C_MODE_INT, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_8MA, - }; - - si5351c_clk_ctrl_t powered_down = { - .power_down = true, - .mode = SI5351C_MODE_INT, - }; - - if (!clkout_enabled) { - clkout = powered_down; - } - - si5351c_clk_ctrl_t clk[8]; - - /* Clock to CPU is deactivated as it is not used and creates noise */ - /* External clock output is kept in current state */ - - /* CLK0: MAX5864/CPLD */ - clk[0] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_FRAC, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_8MA, - }; - /* CLK1: CPLD */ - clk[1] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_INT, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_0_4, - .drive = SI5351C_DRIVE_2MA, - .invert = true, - }; - /* CLK2: SGPIO */ - clk[2] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_INT, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_0_4, - .drive = SI5351C_DRIVE_2MA, - }; - /* CLK3: CLKOUT */ - clk[3] = clkout; - /* CLK4: RFFC5072 (MAX2837 on rad1o) */ - clk[4] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_INT, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_6MA, - .invert = true, - }; - /* CLK5: MAX2837 (MAX2871 on rad1o) */ - clk[5] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_INT, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_4MA, - }; - /* CLK6: none */ - clk[6] = powered_down; - /* CLK7: LPC43xx */ - clk[7] = powered_down; - -#ifdef IS_H1_R9 - if (IS_H1_R9) { - /* CLK0: MAX5864/CPLD/SGPIO (sample clocks) */ - clk[0] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_INT, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_6MA, - }; - /* CLK1: RFFC5072/MAX2839 */ - clk[1] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_FRAC, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_4MA, - }; - /* CLK2: CLKOUT/LPC4320 */ - clk[2] = clkout; - /* Other outputs not present on Si5351A */ - clk[3] = powered_down; - clk[4] = powered_down; - clk[5] = powered_down; - clk[6] = powered_down; - clk[7] = powered_down; - } -#endif -#ifdef IS_PRALINE - if (IS_PRALINE) { - /* CLK0: AFE_CLK */ - clk[0] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_FRAC, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_4MA, - }; - /* CLK1: SCT_CLK and FPGA_CLK */ - clk[1] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_FRAC, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_2MA, - }; - /* CLK3: CLKOUT */ - clkout.pll = SI5351C_PLL_B; - clk[3] = clkout; - /* CLK4: XCVR_CLK */ - clk[4] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_INT, - .pll = SI5351C_PLL_B, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_4MA, - .invert = true, - }; - /* CLK5: MIX_CLK */ - clk[5] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_INT, - .pll = SI5351C_PLL_B, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_4MA, - }; - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - clk[2] = (si5351c_clk_ctrl_t){ - .mode = SI5351C_MODE_FRAC, - .pll = SI5351C_PLL_A, - .source = SI5351C_SRC_MULTISYNTH_SELF, - .drive = SI5351C_DRIVE_2MA, - }; - } else { - /* CLK2: MCU_CLK */ - clk[2] = powered_down; - } - } -#endif - for (int i = 0; i < 8; i++) { set_CLK_PDN(drv, i, clk[i].power_down); set_MS_INT(drv, i, clk[i].mode); @@ -417,72 +279,19 @@ void si5351c_configure_clock_control(si5351c_driver_t* const drv) void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) { - /* Enable CLK outputs 0, 1, 2, 4, 5 only. */ - /* Praline: enable 0, 1, 4, 5 only. */ - /* 7: Clock to CPU is deactivated as it is not used and creates noise */ - /* 3: External clock output is deactivated by default */ - - uint8_t clkout = 3; - - bool enable[8]; - -#ifdef IS_PRALINE - if (IS_PRALINE) { - enable[0] = true; - enable[1] = true; - if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { - /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - enable[2] = true; - } else { - enable[2] = false; - } - enable[3] = false; - enable[4] = true; - enable[5] = true; - enable[6] = false; - enable[7] = false; - } -#endif -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - enable[0] = true; - enable[1] = true; - enable[2] = true; - enable[3] = false; - enable[4] = true; - enable[5] = true; - enable[6] = false; - enable[7] = false; - - /* HackRF One r9 has only three clock generator outputs. */ - #ifdef IS_H1_R9 - if (IS_H1_R9) { - clkout = 2; - enable[0] = true; - enable[1] = true; - enable[3] = false; - enable[4] = false; - enable[5] = false; - enable[6] = false; - enable[7] = false; - } - #endif - } -#endif - enable[clkout] = clkout_enabled; - for (int i = 0; i < 8; i++) { set_CLK_OEB( drv, i, - enable[i] ? SI5351C_OUTPUT_ENABLE : SI5351C_OUTPUT_DISABLE); + clk[i].output_enable ? SI5351C_OUTPUT_ENABLE : + SI5351C_OUTPUT_DISABLE); } si5351c_regs_commit(drv); #ifdef IS_H1_R9 if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); - if (clkout_enabled) { + if (clk[clkout_id].output_enable) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); @@ -545,16 +354,11 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable) { - clkout_enabled = enable; + clk[clkout_id].output_enable = enable; + clk[clkout_id].power_down = !enable; - uint8_t clkout = 3; - - /* HackRF One r9 has only three clock generator outputs. */ - if (detected_platform() == BOARD_ID_HACKRF1_R9) { - clkout = 2; - } /* Configure clock to 10MHz */ - si5351c_configure_multisynth(drv, clkout, 80 * 128 - 512, 0, 1, 0); + si5351c_configure_multisynth(drv, clkout_id, 80 * 128 - 512, 0, 1, 0); si5351c_configure_clock_control(drv); si5351c_enable_clock_outputs(drv); @@ -620,6 +424,155 @@ void si5351c_init(si5351c_driver_t* const drv) gpio_output(gpio->h1r9_mcu_clk_en); } #endif + + si5351c_clk_t clkout = { + .output_enable = false, + .power_down = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_8MA, + }; + + si5351c_clk_t powered_down = { + .output_enable = false, + .power_down = true, + .mode = SI5351C_MODE_INT, + }; + + /* CLK0: MAX5864/CPLD */ + clk[0] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_8MA, + }; + /* CLK1: CPLD */ + clk[1] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_0_4, + .drive = SI5351C_DRIVE_2MA, + .invert = true, + }; + /* CLK2: SGPIO */ + clk[2] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_0_4, + .drive = SI5351C_DRIVE_2MA, + }; + /* CLK3: CLKOUT */ + clk[3] = clkout; + clkout_id = 3; + /* CLK4: RFFC5072 (MAX2837 on rad1o) */ + clk[4] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_6MA, + .invert = true, + }; + /* CLK5: MAX2837 (MAX2871 on rad1o) */ + clk[5] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; + /* CLK6: none */ + clk[6] = powered_down; + /* CLK7: LPC43xx */ + clk[7] = powered_down; + +#ifdef IS_H1_R9 + if (IS_H1_R9) { + /* CLK0: MAX5864/CPLD/SGPIO (sample clocks) */ + clk[0] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_6MA, + }; + /* CLK1: RFFC5072/MAX2839 */ + clk[1] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; + /* CLK2: CLKOUT/LPC4320 */ + clk[2] = clkout; + clkout_id = 2; + /* Other outputs not present on Si5351A */ + clk[3] = powered_down; + clk[4] = powered_down; + clk[5] = powered_down; + clk[6] = powered_down; + clk[7] = powered_down; + } +#endif +#ifdef IS_PRALINE + if (IS_PRALINE) { + /* CLK0: AFE_CLK */ + clk[0] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; + /* CLK1: SCT_CLK and FPGA_CLK */ + clk[1] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_2MA, + }; + /* CLK3: CLKOUT */ + clkout.pll = SI5351C_PLL_B; + clk[3] = clkout; + clkout_id = 3; + /* CLK4: XCVR_CLK */ + clk[4] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_B, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + .invert = true, + }; + /* CLK5: MIX_CLK */ + clk[5] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_B, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_4MA, + }; + if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { + /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ + clk[2] = (si5351c_clk_t){ + .output_enable = true, + .mode = SI5351C_MODE_FRAC, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_2MA, + }; + } else { + /* CLK2: MCU_CLK */ + clk[2] = powered_down; + } + } +#endif } /* diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index c357442f..9691b896 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -97,13 +97,14 @@ typedef struct { } si5351c_driver_t; typedef struct { + bool output_enable; bool power_down; si5351c_mode_t mode; si5351c_pll_t pll; si5351c_src_t source; si5351c_drive_t drive; bool invert; -} si5351c_clk_ctrl_t; +} si5351c_clk_t; void si5351c_disable_all_outputs(si5351c_driver_t* const drv); void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv); From 835e7b618a55865aeeb7846d00b3f378b74ff3d5 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 19 May 2026 06:47:45 +0100 Subject: [PATCH 125/161] Move LPC43xx I2C instances and configs to ipc_lpc.{c,h}. --- firmware/common/clock_gen.c | 5 +++-- firmware/common/cpu_clock.c | 3 ++- firmware/common/da7219.c | 1 + firmware/common/i2c_bus.c | 19 ------------------- firmware/common/i2c_bus.h | 4 ---- firmware/common/i2c_lpc.c | 20 ++++++++++++++++++++ firmware/common/i2c_lpc.h | 5 +++++ firmware/common/operacake.c | 1 + firmware/common/si5351c.c | 6 ------ firmware/common/si5351c.h | 2 -- 10 files changed, 32 insertions(+), 34 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index e91e3822..22f461c2 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -25,6 +25,7 @@ #include "hackrf_ui.h" #include "i2c_bus.h" +#include "i2c_lpc.h" #include "platform_detect.h" #include "sgpio.h" #include "si5351c.h" @@ -35,7 +36,7 @@ void clock_gen_init(void) { - i2c_bus_start(si5351c.bus, &i2c_config_si5351c_fast_clock); + i2c_bus_start(si5351c.bus, &i2c_config_fast_clock); si5351c_init(&si5351c); si5351c_disable_all_outputs(&si5351c); @@ -119,7 +120,7 @@ void clock_gen_init(void) void clock_gen_shutdown(void) { - i2c_bus_start(si5351c.bus, &i2c_config_si5351c_fast_clock); + i2c_bus_start(si5351c.bus, &i2c_config_fast_clock); si5351c_disable_all_outputs(&si5351c); si5351c_disable_oeb_pin_control(&si5351c); si5351c_power_down_all_clocks(&si5351c); diff --git a/firmware/common/cpu_clock.c b/firmware/common/cpu_clock.c index 37445e51..325de76f 100644 --- a/firmware/common/cpu_clock.c +++ b/firmware/common/cpu_clock.c @@ -30,6 +30,7 @@ #include "delay.h" #include "i2c_bus.h" +#include "i2c_lpc.h" #include "si5351c.h" /* @@ -113,7 +114,7 @@ void cpu_clock_init(void) //FIXME disable I2C /* Kick I2C0 down to 400kHz when we switch over to APB1 clock = 204MHz */ - i2c_bus_start(si5351c.bus, &i2c_config_si5351c_fast_clock); + i2c_bus_start(si5351c.bus, &i2c_config_fast_clock); /* * 12MHz clock is entering LPC XTAL1/OSC input now. diff --git a/firmware/common/da7219.c b/firmware/common/da7219.c index 7ae916cb..de85d2b8 100644 --- a/firmware/common/da7219.c +++ b/firmware/common/da7219.c @@ -23,6 +23,7 @@ #include "da7219.h" #include "i2c_bus.h" +#include "i2c_lpc.h" #define DA7219_REG_CHIP_ID1 0x81 #define DA7219_REG_CHIP_ID2 0x82 diff --git a/firmware/common/i2c_bus.c b/firmware/common/i2c_bus.c index c0d5b79d..6d8dcd25 100644 --- a/firmware/common/i2c_bus.c +++ b/firmware/common/i2c_bus.c @@ -22,25 +22,6 @@ #include "i2c_bus.h" -#include - -#include "i2c_lpc.h" - -/* Driver instances. */ -i2c_bus_t i2c0 = { - .obj = (void*) I2C0_BASE, - .start = i2c_lpc_start, - .stop = i2c_lpc_stop, - .transfer = i2c_lpc_transfer, -}; - -i2c_bus_t i2c1 = { - .obj = (void*) I2C1_BASE, - .start = i2c_lpc_start, - .stop = i2c_lpc_stop, - .transfer = i2c_lpc_transfer, -}; - void i2c_bus_start(i2c_bus_t* const bus, const void* const config) { bus->start(bus, config); diff --git a/firmware/common/i2c_bus.h b/firmware/common/i2c_bus.h index fa1211c1..7519ba14 100644 --- a/firmware/common/i2c_bus.h +++ b/firmware/common/i2c_bus.h @@ -47,7 +47,3 @@ void i2c_bus_transfer( const size_t tx_count, uint8_t* const rx, const size_t rx_count); - -/* Driver instances. */ -extern i2c_bus_t i2c0; -extern i2c_bus_t i2c1; diff --git a/firmware/common/i2c_lpc.c b/firmware/common/i2c_lpc.c index 05631f05..9d6499fa 100644 --- a/firmware/common/i2c_lpc.c +++ b/firmware/common/i2c_lpc.c @@ -23,6 +23,26 @@ #include "i2c_lpc.h" #include +#include + +/* Driver instances. */ +i2c_bus_t i2c0 = { + .obj = (void*) I2C0_BASE, + .start = i2c_lpc_start, + .stop = i2c_lpc_stop, + .transfer = i2c_lpc_transfer, +}; + +i2c_bus_t i2c1 = { + .obj = (void*) I2C1_BASE, + .start = i2c_lpc_start, + .stop = i2c_lpc_stop, + .transfer = i2c_lpc_transfer, +}; + +const i2c_lpc_config_t i2c_config_fast_clock = { + .duty_cycle_count = 255, +}; /* FIXME return i2c0 status from each function */ diff --git a/firmware/common/i2c_lpc.h b/firmware/common/i2c_lpc.h index f5b8230e..e71cb362 100644 --- a/firmware/common/i2c_lpc.h +++ b/firmware/common/i2c_lpc.h @@ -42,3 +42,8 @@ void i2c_lpc_transfer( uint8_t* const data_rx, const size_t count_rx); bool i2c_probe(i2c_bus_t* const bus, const uint_fast8_t device_address); + +/* Driver instances. */ +extern const i2c_lpc_config_t i2c_config_fast_clock; +extern i2c_bus_t i2c0; +extern i2c_bus_t i2c1; diff --git a/firmware/common/operacake.c b/firmware/common/operacake.c index f50c4f7e..bce51b76 100644 --- a/firmware/common/operacake.c +++ b/firmware/common/operacake.c @@ -29,6 +29,7 @@ #include "gpio.h" #include "gpio_lpc.h" #include "i2c_bus.h" +#include "i2c_lpc.h" #include "operacake_sctimer.h" #include "platform_scu.h" diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 1f27d8db..77d77703 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -40,12 +40,6 @@ #include "si5351c_regs.def" /* Driver instance. */ -// const i2c_lpc_config_t i2c_config_si5351c_slow_clock = { -// .duty_cycle_count = 15, -// }; -const i2c_lpc_config_t i2c_config_si5351c_fast_clock = { - .duty_cycle_count = 255, -}; si5351c_driver_t si5351c = { .bus = &i2c0, .i2c_address = 0x60, diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index 5be31788..b99bec14 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -30,7 +30,6 @@ extern "C" { #include #include "i2c_bus.h" -#include "i2c_lpc.h" #define SI_INTDIV(x) (x * 128 - 512) @@ -141,7 +140,6 @@ void si5351c_set_phase( const uint8_t offset); /* Driver Instance. */ -extern const i2c_lpc_config_t i2c_config_si5351c_fast_clock; extern si5351c_driver_t si5351c; #ifdef __cplusplus From 62cdff34483c34dadabae43850f8b99577444820 Mon Sep 17 00:00:00 2001 From: grvvy Date: Fri, 24 Apr 2026 10:58:55 -0600 Subject: [PATCH 126/161] Jenkinsfile: add resource locks to HIL USB hubs to allow for concurrent builds --- Jenkinsfile | 160 +++++++++++++++++++++++++++------------------------- 1 file changed, 84 insertions(+), 76 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b41d6714..2d856531 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,29 +41,31 @@ pipeline { steps { sh './ci-scripts/install_host.sh' sh './ci-scripts/build_firmware.sh HACKRF_ONE' - script { - allOff() - reset('h1_eut') - } - sh 'sleep 1s' - retry(3) { - sh './ci-scripts/test_host.sh' - } - script { - reset('h1_tester h1_eut') - } - sh 'sleep 1s' - script { - // Allow up to 3 retries 5 minutes each for the HIL test - runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) - } - script { - allOff() - reset('h1_eut') - } - sh 'sleep 1s' - retry(3) { - sh 'python3 ci-scripts/test_sgpio_debug.py' + lock('HIL_hubs') { + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' + retry(3) { + sh './ci-scripts/test_host.sh' + } + script { + reset('h1_tester h1_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) + } + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' + retry(3) { + sh 'python3 ci-scripts/test_sgpio_debug.py' + } } } } @@ -81,29 +83,31 @@ pipeline { steps { sh './ci-scripts/install_host.sh' sh './ci-scripts/build_firmware.sh UNIVERSAL' - script { - allOff() - reset('h1_eut') - } - sh 'sleep 1s' - retry(3) { - sh './ci-scripts/test_host.sh' - } - script { - reset('h1_tester h1_eut') - } - sh 'sleep 1s' - script { - // Allow up to 3 retries 5 minutes each for the HIL test - runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) - } - script { - allOff() - reset('h1_eut') - } - sh 'sleep 1s' - retry(3) { - sh 'python3 ci-scripts/test_sgpio_debug.py' + lock('HIL_hubs') { + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' + retry(3) { + sh './ci-scripts/test_host.sh' + } + script { + reset('h1_tester h1_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) + } + script { + allOff() + reset('h1_eut') + } + sh 'sleep 1s' + retry(3) { + sh 'python3 ci-scripts/test_sgpio_debug.py' + } } } } @@ -121,21 +125,23 @@ pipeline { steps { sh './ci-scripts/install_host.sh' sh './ci-scripts/build_firmware.sh PRALINE' - script { - allOff() - reset('hpro_eut') - } - sh 'sleep 1s' - retry(3) { - sh './ci-scripts/test_host.sh' - } - script { - reset('hpro_tester hpro_eut') - } - sh 'sleep 1s' - script { - // Allow up to 3 retries 5 minutes each for the HIL test - runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) + lock('HIL_hubs') { + script { + allOff() + reset('hpro_eut') + } + sh 'sleep 1s' + retry(3) { + sh './ci-scripts/test_host.sh' + } + script { + reset('hpro_tester hpro_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) + } } } } @@ -153,21 +159,23 @@ pipeline { steps { sh './ci-scripts/install_host.sh' sh './ci-scripts/build_firmware.sh UNIVERSAL' - script { - allOff() - reset('hpro_eut') - } - sh 'sleep 1s' - retry(3) { - sh './ci-scripts/test_host.sh' - } - script { - reset('hpro_tester hpro_eut') - } - sh 'sleep 1s' - script { - // Allow up to 3 retries 5 minutes each for the HIL test - runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) + lock('HIL_hubs') { + script { + allOff() + reset('hpro_eut') + } + sh 'sleep 1s' + retry(3) { + sh './ci-scripts/test_host.sh' + } + script { + reset('hpro_tester hpro_eut') + } + sh 'sleep 1s' + script { + // Allow up to 3 retries 5 minutes each for the HIL test + runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) + } } } } From 6c01a078d52c76d58619881669d84c94ad4c5194 Mon Sep 17 00:00:00 2001 From: grvvy Date: Tue, 19 May 2026 14:44:37 -0600 Subject: [PATCH 127/161] Jenkinsfile: ensure device resets occur between test runs --- Jenkinsfile | 111 +++++++++++++++------------------------------------- 1 file changed, 32 insertions(+), 79 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2d856531..dcfd9ad8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -39,32 +39,14 @@ pipeline { timeout(time: 20, unit: 'MINUTES') } steps { - sh './ci-scripts/install_host.sh' - sh './ci-scripts/build_firmware.sh HACKRF_ONE' + runCommand("Install Host Tools", './ci-scripts/install_host.sh', 3, 1, 'MINUTES') + runCommand("Build HackRF One Firmware", './ci-scripts/build_firmware.sh HACKRF_ONE', 3, 1, 'MINUTES') lock('HIL_hubs') { script { allOff() - reset('h1_eut') - } - sh 'sleep 1s' - retry(3) { - sh './ci-scripts/test_host.sh' - } - script { - reset('h1_tester h1_eut') - } - sh 'sleep 1s' - script { - // Allow up to 3 retries 5 minutes each for the HIL test - runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) - } - script { - allOff() - reset('h1_eut') - } - sh 'sleep 1s' - retry(3) { - sh 'python3 ci-scripts/test_sgpio_debug.py' + runTest("Check Host", 'h1_eut', './ci-scripts/test_host.sh') + runTest("HackRF One HIL Test", 'h1_tester h1_eut', h1_test) + runTest("SGPIO Debug Test", 'h1_eut', 'python3 ci-scripts/test_sgpio_debug.py') } } } @@ -81,34 +63,17 @@ pipeline { timeout(time: 20, unit: 'MINUTES') } steps { - sh './ci-scripts/install_host.sh' - sh './ci-scripts/build_firmware.sh UNIVERSAL' + runCommand("Install Host Tools", './ci-scripts/install_host.sh', 3, 1, 'MINUTES') + runCommand("Build Universal Firmware", './ci-scripts/build_firmware.sh UNIVERSAL', 3, 1, 'MINUTES') lock('HIL_hubs') { script { allOff() - reset('h1_eut') - } - sh 'sleep 1s' - retry(3) { - sh './ci-scripts/test_host.sh' - } - script { - reset('h1_tester h1_eut') - } - sh 'sleep 1s' - script { - // Allow up to 3 retries 5 minutes each for the HIL test - runCommand(3, 5, 'MINUTES', "HackRF One Test", h1_test) - } - script { - allOff() - reset('h1_eut') - } - sh 'sleep 1s' - retry(3) { - sh 'python3 ci-scripts/test_sgpio_debug.py' + runTest("Check Host", 'h1_eut', './ci-scripts/test_host.sh') + runTest("HackRF One HIL Test", 'h1_tester h1_eut', h1_test) + runTest("SGPIO Debug Test", 'h1_eut', 'python3 ci-scripts/test_sgpio_debug.py') } } + } } stage('Test HackRF Pro with BOARD=PRALINE') { @@ -123,24 +88,13 @@ pipeline { timeout(time: 20, unit: 'MINUTES') } steps { - sh './ci-scripts/install_host.sh' - sh './ci-scripts/build_firmware.sh PRALINE' + runCommand("Install Host Tools", './ci-scripts/install_host.sh', 3, 1, 'MINUTES') + runCommand("Build Praline Firmware", './ci-scripts/build_firmware.sh PRALINE', 3, 1, 'MINUTES') lock('HIL_hubs') { script { allOff() - reset('hpro_eut') - } - sh 'sleep 1s' - retry(3) { - sh './ci-scripts/test_host.sh' - } - script { - reset('hpro_tester hpro_eut') - } - sh 'sleep 1s' - script { - // Allow up to 3 retries 5 minutes each for the HIL test - runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) + runTest("Check Host", 'hpro_eut', './ci-scripts/test_host.sh') + runTest("HackRF Pro HIL Test", 'hpro_tester hpro_eut', hpro_test) } } } @@ -157,24 +111,13 @@ pipeline { timeout(time: 20, unit: 'MINUTES') } steps { - sh './ci-scripts/install_host.sh' - sh './ci-scripts/build_firmware.sh UNIVERSAL' + runCommand("Install Host Tools", './ci-scripts/install_host.sh', 3, 1, 'MINUTES') + runCommand("Build Universal Firmware", './ci-scripts/build_firmware.sh UNIVERSAL', 3, 1, 'MINUTES') lock('HIL_hubs') { script { allOff() - reset('hpro_eut') - } - sh 'sleep 1s' - retry(3) { - sh './ci-scripts/test_host.sh' - } - script { - reset('hpro_tester hpro_eut') - } - sh 'sleep 1s' - script { - // Allow up to 3 retries 5 minutes each for the HIL test - runCommand(3, 5, 'MINUTES', "HackRF Pro Test", hpro_test) + runTest("Check Host", 'hpro_eut', './ci-scripts/test_host.sh') + runTest("HackRF Pro HIL Test", 'hpro_tester hpro_eut', hpro_test) } } } @@ -192,15 +135,15 @@ pipeline { def allOff() { // Allow up to 3 retries, 20 seconds each, for the USB hub port power server to respond appropriately - runCommand(3, 20, 'SECONDS', 'USB hub port power server command', "hubs all off") + runCommand('USB hub port power server command', "hubs all off", 3, 20, 'SECONDS') } def reset(devices) { // Allow up to 3 retries, 20 seconds each, for the USB hub port power server to respond appropriately - runCommand(3, 20, 'SECONDS', 'USB hub port power server command', "hubs ${devices} reset") + runCommand('USB hub port power server command', "hubs ${devices} reset", 3, 20, 'SECONDS') } -def runCommand(retries, time, unit, title, cmd) { +def runCommand(title, cmd, retries, time, unit) { retry(retries) { try { timeout(time: time, unit: unit) { @@ -222,3 +165,13 @@ def runCommand(retries, time, unit, title, cmd) { } } } + +def runTest(title, devices, cmd) { + retry(3) { + // reset() retains it's own internal retries + reset(devices) + sh 'sleep 1s' + // run the test with 0 internal retries and 3 external retries to ensure resets between runs + runCommand(title, cmd, 0, 5, 'MINUTES') + } +} From c2262ccda1a73e00e153e35f5bb0b2902d0de92d Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 19 May 2026 16:57:30 +0100 Subject: [PATCH 128/161] si5351c: Add control of MCU_CLKIN output. --- firmware/common/si5351c.c | 51 +++++++++++++++++++++++++++++++++++++-- firmware/common/si5351c.h | 1 + 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 50d39583..353a4d2e 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -54,6 +54,7 @@ si5351c_driver_t si5351c = { si5351c_clk_t clk[8]; uint8_t clkout_id; +uint8_t mcu_clkin_id; static bool input_initialized = false; static si5351c_input_t active_input; @@ -364,6 +365,40 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable) si5351c_enable_clock_outputs(drv); } +void si5351c_mcu_clkin_enable(si5351c_driver_t* const drv, bool enable) +{ +#ifdef IS_H1_R9 + if (IS_H1_R9) { + /* MCU clock is shared with CLKOUT. */ + si5351c_clkout_enable(drv, enable); + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { + clk[mcu_clkin_id].output_enable = enable; + clk[mcu_clkin_id].power_down = !enable; + + /* Configure clock to 40MHz */ + if (mcu_clkin_id >= 6) { + // MCU_CLKIN on CLK6 or CLK7, integer only MS. + si5351c_configure_multisynth(drv, mcu_clkin_id, 20, 0, 0, 0); + } else { + // MCU_CLKIN on CLK0 to CLK5, fractional-capable MS. + si5351c_configure_multisynth( + drv, + mcu_clkin_id, + 20 * 128 - 512, + 0, + 1, + 0); + } + + si5351c_configure_clock_control(drv); + si5351c_enable_clock_outputs(drv); + } +#endif +} + void si5351c_init(si5351c_driver_t* const drv) { /* Read revision ID */ @@ -434,6 +469,15 @@ void si5351c_init(si5351c_driver_t* const drv) .drive = SI5351C_DRIVE_8MA, }; + si5351c_clk_t mcu_clkin = { + .output_enable = false, + .power_down = true, + .mode = SI5351C_MODE_INT, + .pll = SI5351C_PLL_A, + .source = SI5351C_SRC_MULTISYNTH_SELF, + .drive = SI5351C_DRIVE_2MA, + }; + si5351c_clk_t powered_down = { .output_enable = false, .power_down = true, @@ -488,7 +532,8 @@ void si5351c_init(si5351c_driver_t* const drv) /* CLK6: none */ clk[6] = powered_down; /* CLK7: LPC43xx */ - clk[7] = powered_down; + clk[7] = mcu_clkin; + mcu_clkin_id = 7; #ifdef IS_H1_R9 if (IS_H1_R9) { @@ -511,6 +556,7 @@ void si5351c_init(si5351c_driver_t* const drv) /* CLK2: CLKOUT/LPC4320 */ clk[2] = clkout; clkout_id = 2; + mcu_clkin_id = 2; /* Other outputs not present on Si5351A */ clk[3] = powered_down; clk[4] = powered_down; @@ -569,7 +615,8 @@ void si5351c_init(si5351c_driver_t* const drv) }; } else { /* CLK2: MCU_CLK */ - clk[2] = powered_down; + clk[2] = mcu_clkin; + mcu_clkin_id = 2; } } #endif diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index 9691b896..dad74a17 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -135,6 +135,7 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv); void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val); uint8_t si5351c_read_single(si5351c_driver_t* const drv, uint8_t reg); void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable); +void si5351c_mcu_clkin_enable(si5351c_driver_t* const drv, bool enable); void si5351c_init(si5351c_driver_t* const drv); void si5351c_set_phase( si5351c_driver_t* const drv, From 62229fdf73e96943f95c623144258d9708982beb Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 21 May 2026 18:40:44 +0100 Subject: [PATCH 129/161] Remove sample rate callback, move radio instance to radio.c. --- firmware/common/radio.c | 29 +++++++++++++++-------------- firmware/common/radio.h | 4 ---- firmware/hackrf_usb/hackrf_usb.c | 4 ---- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index eb01b5de..f8b2e514 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -25,6 +25,7 @@ #include +#include "clock_gen.h" #include "clock_io.h" #include "fixed_point.h" #include "max283x.h" @@ -41,6 +42,9 @@ #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) +/* Driver instance. */ +radio_t radio; + void radio_init(radio_t* const radio) { for (uint8_t bank = 0; bank < RADIO_NUM_BANKS; bank++) { @@ -231,22 +235,19 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) } new_n = (n != previous_n); - if (radio->sample_rate_cb) { - afe_rate = rate << n; - afe_rate = radio->sample_rate_cb(afe_rate, false); - previous_rate = radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE]; - if ((previous_n == RADIO_UNSET) || previous_rate == RADIO_UNSET) { - previous_afe_rate = RADIO_UNSET; - } else { - previous_afe_rate = previous_rate << previous_n; - } - new_afe_rate = (afe_rate != previous_afe_rate); - if (new_afe_rate) { - afe_rate = radio->sample_rate_cb(afe_rate, true); - } + afe_rate = rate << n; + afe_rate = sample_rate_set(afe_rate, false); + previous_rate = radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE]; + if ((previous_n == RADIO_UNSET) || previous_rate == RADIO_UNSET) { + previous_afe_rate = RADIO_UNSET; } else { - return 0; + previous_afe_rate = previous_rate << previous_n; } + new_afe_rate = (afe_rate != previous_afe_rate); + if (new_afe_rate) { + afe_rate = sample_rate_set(afe_rate, true); + } + rate = afe_rate >> n; new_rate = (rate != previous_rate); if (new_rate) { diff --git a/firmware/common/radio.h b/firmware/common/radio.h index b785c281..821c7fea 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -225,7 +225,6 @@ typedef struct { radio_config_mode_t config_mode; uint64_t config[RADIO_NUM_BANKS][RADIO_NUM_REGS]; volatile uint32_t regs_dirty; - sample_rate_fn sample_rate_cb; update_fn update_cb; } radio_t; @@ -263,8 +262,5 @@ void radio_switch_opmode(radio_t* const radio, const transceiver_mode_t mode); /** * Driver instance. - * - * This needs to be configured and provided by the application - * binary. e.g. firmware/hackrf_usb/hackrf_usb.c */ extern radio_t radio; diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index eb7f85c5..5d71fa80 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -96,10 +96,6 @@ extern uint32_t __m0_end__; extern uint32_t __ram_m0_start__; extern uint32_t _etext_ram, _text_ram, _etext_rom; -radio_t radio = { - .sample_rate_cb = sample_rate_set, -}; - static usb_request_handler_fn vendor_request_handler[] = { NULL, usb_vendor_request_set_transceiver_mode, From 7cce66a7fa8449a3c64e55d62ba1bda8005d5c63 Mon Sep 17 00:00:00 2001 From: mndza Date: Fri, 22 May 2026 08:18:17 +0200 Subject: [PATCH 130/161] Fix reads with hackrf_spiflash when size is a multiple of 64 bytes. --- firmware/hackrf_usb/usb_api_spiflash.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/firmware/hackrf_usb/usb_api_spiflash.c b/firmware/hackrf_usb/usb_api_spiflash.c index d490eb0e..c698d599 100644 --- a/firmware/hackrf_usb/usb_api_spiflash.c +++ b/firmware/hackrf_usb/usb_api_spiflash.c @@ -111,6 +111,7 @@ usb_request_status_t usb_vendor_request_read_spiflash( len, NULL, NULL); + usb_transfer_schedule_ack(endpoint->out); return USB_REQUEST_STATUS_OK; } } else if (stage == USB_TRANSFER_STAGE_DATA) { @@ -121,7 +122,6 @@ usb_request_status_t usb_vendor_request_read_spiflash( ((addr + len) > spi_flash.num_bytes)) { return USB_REQUEST_STATUS_STALL; } else { - usb_transfer_schedule_ack(endpoint->out); return USB_REQUEST_STATUS_OK; } } else { @@ -141,13 +141,9 @@ usb_request_status_t usb_vendor_request_spiflash_status( 2, NULL, NULL); - return USB_REQUEST_STATUS_OK; - } else if (stage == USB_TRANSFER_STAGE_DATA) { usb_transfer_schedule_ack(endpoint->out); - return USB_REQUEST_STATUS_OK; - } else { - return USB_REQUEST_STATUS_OK; } + return USB_REQUEST_STATUS_OK; } usb_request_status_t usb_vendor_request_spiflash_clear_status( From eb2e6a1c05a2a2080bec6964242c7e00c0734e24 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 28 May 2026 16:37:18 +0100 Subject: [PATCH 131/161] Move SSP0 and SSP1 bus instances to spi_ssp.{c,h}. --- firmware/common/spi_bus.c | 21 --------------------- firmware/common/spi_bus.h | 4 ---- firmware/common/spi_ssp.c | 17 +++++++++++++++++ firmware/common/spi_ssp.h | 4 ++++ 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/firmware/common/spi_bus.c b/firmware/common/spi_bus.c index a5332699..1b05c298 100644 --- a/firmware/common/spi_bus.c +++ b/firmware/common/spi_bus.c @@ -22,27 +22,6 @@ #include "spi_bus.h" -#include - -#include "spi_ssp.h" - -/* Driver instances. */ -spi_bus_t spi_bus_ssp0 = { - .obj = (void*) SSP0_BASE, - .start = spi_ssp_start, - .stop = spi_ssp_stop, - .transfer = spi_ssp_transfer, - .transfer_gather = spi_ssp_transfer_gather, -}; - -spi_bus_t spi_bus_ssp1 = { - .obj = (void*) SSP1_BASE, - .start = spi_ssp_start, - .stop = spi_ssp_stop, - .transfer = spi_ssp_transfer, - .transfer_gather = spi_ssp_transfer_gather, -}; - void spi_bus_start(spi_bus_t* const bus, const void* const config) { bus->config = config; diff --git a/firmware/common/spi_bus.h b/firmware/common/spi_bus.h index 23b3107b..07d5787d 100644 --- a/firmware/common/spi_bus.h +++ b/firmware/common/spi_bus.h @@ -51,7 +51,3 @@ void spi_bus_transfer_gather( spi_bus_t* const bus, const spi_transfer_t* const transfers, const size_t count); - -/* Driver instances. */ -extern spi_bus_t spi_bus_ssp0; -extern spi_bus_t spi_bus_ssp1; diff --git a/firmware/common/spi_ssp.c b/firmware/common/spi_ssp.c index 7c288d51..868556be 100644 --- a/firmware/common/spi_ssp.c +++ b/firmware/common/spi_ssp.c @@ -28,6 +28,23 @@ #include #include +/* Driver instances. */ +spi_bus_t spi_bus_ssp0 = { + .obj = (void*) SSP0_BASE, + .start = spi_ssp_start, + .stop = spi_ssp_stop, + .transfer = spi_ssp_transfer, + .transfer_gather = spi_ssp_transfer_gather, +}; + +spi_bus_t spi_bus_ssp1 = { + .obj = (void*) SSP1_BASE, + .start = spi_ssp_start, + .stop = spi_ssp_stop, + .transfer = spi_ssp_transfer, + .transfer_gather = spi_ssp_transfer_gather, +}; + void spi_ssp_start(spi_bus_t* const bus, const void* const _config) { const ssp_config_t* const config = _config; diff --git a/firmware/common/spi_ssp.h b/firmware/common/spi_ssp.h index 822772b5..5e754dc4 100644 --- a/firmware/common/spi_ssp.h +++ b/firmware/common/spi_ssp.h @@ -45,3 +45,7 @@ void spi_ssp_transfer_gather( spi_bus_t* const bus, const spi_transfer_t* const transfers, const size_t count); + +/* Driver instances. */ +extern spi_bus_t spi_bus_ssp0; +extern spi_bus_t spi_bus_ssp1; From b86b13930574a1ff97d82901bb8d55d489783404 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 29 May 2026 09:02:01 +0100 Subject: [PATCH 132/161] Express all delays as delay_us() or delay_ms(). --- firmware/blinky/blinky.c | 4 ++-- firmware/common/clock_gen.c | 2 +- firmware/common/cpu_clock.c | 16 ++++++++++++++-- firmware/common/cpu_clock.h | 3 +++ firmware/common/delay.c | 21 ++++++++++++--------- firmware/common/delay.h | 4 ++-- firmware/common/fpga_selftest.c | 8 ++++---- firmware/common/ice40_spi.c | 4 ++-- firmware/common/leds.c | 6 +++--- firmware/common/operacake_sctimer.c | 2 +- firmware/common/platform_detect.c | 20 ++++++++++---------- firmware/common/portapack.c | 14 ++++---------- firmware/common/power.c | 4 ++-- firmware/common/rad1o/display.c | 12 +++--------- firmware/common/rffc5071.c | 4 ++-- firmware/common/si5351c.c | 2 +- firmware/hackrf_usb/hackrf_usb.c | 6 +++--- firmware/hackrf_usb/usb_api_cpld.c | 2 +- 18 files changed, 70 insertions(+), 64 deletions(-) diff --git a/firmware/blinky/blinky.c b/firmware/blinky/blinky.c index fdaf3ef7..4bd11106 100644 --- a/firmware/blinky/blinky.c +++ b/firmware/blinky/blinky.c @@ -45,13 +45,13 @@ int main(void) led_on(LED2); led_on(LED3); - delay(2000000); + delay_ms(150); led_off(LED1); led_off(LED2); led_off(LED3); - delay(2000000); + delay_ms(150); } return 0; diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index 22f461c2..8a75074f 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -148,7 +148,7 @@ clock_source_t activate_best_clock_source(void) /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ if (portapack_reference_oscillator && portapack()) { portapack_reference_oscillator(true); - delay(510000); /* loop iterations @ 204MHz for >10ms for oscillator to enable. */ + delay_ms(18); // for oscillator to enable. if (si5351c_clkin_signal_valid(&si5351c)) { source = CLOCK_SOURCE_PORTAPACK; } else { diff --git a/firmware/common/cpu_clock.c b/firmware/common/cpu_clock.c index 325de76f..32fd012c 100644 --- a/firmware/common/cpu_clock.c +++ b/firmware/common/cpu_clock.c @@ -33,6 +33,9 @@ #include "i2c_lpc.h" #include "si5351c.h" +/* We start with the CPU clock at 96MHz */ +unsigned int cpu_clock_mhz = 96; + /* Configure PLL1 (Main MCU Clock) to max speed (204MHz). Note: PLL1 clock is used by M4/M0 core, Peripheral, APB1. @@ -51,11 +54,14 @@ static void cpu_clock_pll1_max_speed(void) reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_IRC) | CGU_BASE_M4_CLK_AUTOBLOCK(1); CGU_BASE_M4_CLK = reg_val; + /* CPU is now at 12MHz */ + cpu_clock_mhz = 12; + /* 2. Enable the crystal oscillator. */ CGU_XTAL_OSC_CTRL &= ~CGU_XTAL_OSC_CTRL_ENABLE_MASK; /* 3. Wait 250us. */ - delay_us_at_mhz(250, 12); + delay_us(250); /* 4. Set the AUTOBLOCK bit. */ CGU_PLL1_CTRL |= CGU_PLL1_CTRL_AUTOBLOCK(1); @@ -95,11 +101,17 @@ static void cpu_clock_pll1_max_speed(void) reg_val |= CGU_BASE_M4_CLK_CLK_SEL(CGU_SRC_PLL1); CGU_BASE_M4_CLK = reg_val; + /* CPU is now at 102MHz */ + cpu_clock_mhz = 102; + /* 9. Wait 50us. */ - delay_us_at_mhz(50, 102); + delay_us(50); /* 10. Set the PLL1 P-divider to direct output mode (DIRECT=1). */ CGU_PLL1_CTRL |= CGU_PLL1_CTRL_DIRECT_MASK; + + /* CPU is now at 204MHz */ + cpu_clock_mhz = 204; } /* clock startup for LPC4320 configure PLL1 to max speed (204MHz). diff --git a/firmware/common/cpu_clock.h b/firmware/common/cpu_clock.h index 92ad6441..04929643 100644 --- a/firmware/common/cpu_clock.h +++ b/firmware/common/cpu_clock.h @@ -27,6 +27,9 @@ extern "C" { void cpu_clock_init(void); +// Current clock speed in MHz, updated on clock changes. +extern unsigned int cpu_clock_mhz; + #ifdef __cplusplus } #endif diff --git a/firmware/common/delay.c b/firmware/common/delay.c index ceb7693e..b74389b2 100644 --- a/firmware/common/delay.c +++ b/firmware/common/delay.c @@ -21,16 +21,9 @@ #include "delay.h" -void delay(uint32_t duration) -{ - uint32_t i; +#include "cpu_clock.h" - for (i = 0; i < duration; i++) { - __asm__("nop"); - } -} - -void delay_us_at_mhz(uint32_t us, uint32_t mhz) +static void delay_us_at_mhz(uint32_t us, uint32_t mhz) { #if defined(LPC43XX_M4) // The loop below takes 3 cycles per iteration. @@ -52,3 +45,13 @@ void delay_us_at_mhz(uint32_t us, uint32_t mhz) #error "No delay loop implementation" #endif } + +void delay_us(uint32_t us) +{ + delay_us_at_mhz(us, cpu_clock_mhz); +} + +void delay_ms(uint32_t ms) +{ + delay_us_at_mhz(ms * 1000, cpu_clock_mhz); +} diff --git a/firmware/common/delay.h b/firmware/common/delay.h index 870ba39e..eed806bb 100644 --- a/firmware/common/delay.h +++ b/firmware/common/delay.h @@ -27,8 +27,8 @@ extern "C" { #include -void delay(uint32_t duration); -void delay_us_at_mhz(uint32_t us, uint32_t mhz); +void delay_us(uint32_t us); +void delay_ms(uint32_t ms); #ifdef __cplusplus } diff --git a/firmware/common/fpga_selftest.c b/firmware/common/fpga_selftest.c index 06f2e8ee..7170422c 100644 --- a/firmware/common/fpga_selftest.c +++ b/firmware/common/fpga_selftest.c @@ -200,7 +200,7 @@ bool fpga_if_xcvr_selftest(void) // Capture 1: 4 Msps, tone at 0.5 MHz, narrowband filter OFF sample_rate_set(SR_FP_MHZ(4), true); - delay_us_at_mhz(1000, 204); + delay_ms(1); if (rx_samples(num_samples, 2000000) == -1) { timeout = true; } @@ -211,7 +211,7 @@ bool fpga_if_xcvr_selftest(void) // Capture 2: 4 Msps, tone at 0.5 MHz, narrowband filter ON narrowband_filter_set(1); - delay_us_at_mhz(1000, 204); + delay_ms(1); if (rx_samples(num_samples, 2000000) == -1) { timeout = true; } @@ -224,7 +224,7 @@ bool fpga_if_xcvr_selftest(void) fpga_set_tx_nco_pstep(&fpga, 255); sample_rate_set(SR_FP_MHZ(20), true); narrowband_filter_set(0); - delay_us_at_mhz(1000, 204); + delay_ms(1); if (rx_samples(num_samples, 2000000) == -1) { timeout = true; } @@ -235,7 +235,7 @@ bool fpga_if_xcvr_selftest(void) // Capture 4: 20 Msps, tone at 5 MHz, narrowband filter ON narrowband_filter_set(1); - delay_us_at_mhz(1000, 204); + delay_ms(1); if (rx_samples(num_samples, 2000000) == -1) { timeout = true; } diff --git a/firmware/common/ice40_spi.c b/firmware/common/ice40_spi.c index 2685092a..880e0be2 100644 --- a/firmware/common/ice40_spi.c +++ b/firmware/common/ice40_spi.c @@ -112,14 +112,14 @@ bool ice40_spi_syscfg_program( gpio_clear(drv->gpio_select); // Wait a minimum of 200 ns. - delay_us_at_mhz(1, 204 / 4); // 250 ns. + delay_us(1); // Release CRESET_B or drive CRESET_B = 1. gpio_set(drv->gpio_creset); // Wait a minimum of 1200 μs to clear internal configuration memory. // Testing showed us that we need to wait longer. Let's wait 1800 μs. - delay_us_at_mhz(1800, 204); + delay_us(1800); // Set SPI_SS = 1, Send 8 dummy clocks. gpio_set(drv->gpio_select); diff --git a/firmware/common/leds.c b/firmware/common/leds.c index ab9b56fb..e1fd507b 100644 --- a/firmware/common/leds.c +++ b/firmware/common/leds.c @@ -84,17 +84,17 @@ void set_leds(const uint8_t state) } } -void halt_and_flash(const uint32_t duration) +void halt_and_flash(const uint32_t period_ms) { /* blink LED1, LED2, and LED3 */ while (1) { led_on(LED1); led_on(LED2); led_on(LED3); - delay(duration); + delay_ms(period_ms / 2); led_off(LED1); led_off(LED2); led_off(LED3); - delay(duration); + delay_ms(period_ms / 2); } } diff --git a/firmware/common/operacake_sctimer.c b/firmware/common/operacake_sctimer.c index 76453e57..c5ec8a3b 100644 --- a/firmware/common/operacake_sctimer.c +++ b/firmware/common/operacake_sctimer.c @@ -75,7 +75,7 @@ void operacake_sctimer_init(void) // there are additional instructions that fill the time. If the duration of // the actions from here to the first access to the SCTimer is changed, then // this delay may need to be increased. - delay(8); + delay_us(1); // Pin definitions for the HackRF // U2CTRL0 diff --git a/firmware/common/platform_detect.c b/firmware/common/platform_detect.c index 7c9fb53d..475ad1e1 100644 --- a/firmware/common/platform_detect.c +++ b/firmware/common/platform_detect.c @@ -194,11 +194,11 @@ void detect_hardware_platform(void) /* activate internal pull-down */ scu_pinmux(P5_0, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); scu_pinmux(P6_10, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); - delay_us_at_mhz(4, 96); + delay_us(4); /* tri-state for a moment before testing input */ scu_pinmux(P5_0, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(P6_10, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - delay_us_at_mhz(4, 96); + delay_us(4); /* if input rose quickly, there must be an external pull-up */ detected_resistors |= (gpio_read(&gpio2_9_on_P5_0)) ? P5_0_PUP : 0; detected_resistors |= (gpio_read(&gpio3_6_on_P6_10)) ? P6_10_PUP : 0; @@ -207,12 +207,12 @@ void detect_hardware_platform(void) scu_pinmux(P5_0, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); scu_pinmux(P6_10, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); scu_pinmux(P6_5, SCU_GPIO_PUP | SCU_CONF_FUNCTION0); - delay_us_at_mhz(4, 96); + delay_us(4); /* tri-state for a moment before testing input */ scu_pinmux(P5_0, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(P6_10, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); scu_pinmux(P6_5, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - delay_us_at_mhz(4, 96); + delay_us(4); /* if input fell quickly, there must be an external pull-down */ detected_resistors |= (gpio_read(&gpio2_9_on_P5_0)) ? 0 : P5_0_PDN; detected_resistors |= (gpio_read(&gpio3_6_on_P6_10)) ? 0 : P6_10_PDN; @@ -221,37 +221,37 @@ void detect_hardware_platform(void) switch (detected_resistors) { case JAWBREAKER_RESISTORS: if (!(supported_platform() & PLATFORM_JAWBREAKER)) { - halt_and_flash(3000000); + halt_and_flash(500); } platform = BOARD_ID_JAWBREAKER; return; case RAD1O_RESISTORS: if (!(supported_platform() & PLATFORM_RAD1O)) { - halt_and_flash(3000000); + halt_and_flash(500); } platform = BOARD_ID_RAD1O; return; case HACKRF1_OG_RESISTORS: if (!(supported_platform() & PLATFORM_HACKRF1_OG)) { - halt_and_flash(3000000); + halt_and_flash(500); } platform = BOARD_ID_HACKRF1_OG; break; case HACKRF1_R9_RESISTORS: if (!(supported_platform() & PLATFORM_HACKRF1_R9)) { - halt_and_flash(3000000); + halt_and_flash(500); } platform = BOARD_ID_HACKRF1_R9; break; case PRALINE_RESISTORS: if (!(supported_platform() & PLATFORM_PRALINE)) { - halt_and_flash(3000000); + halt_and_flash(500); } platform = BOARD_ID_PRALINE; break; default: platform = BOARD_ID_UNRECOGNIZED; - halt_and_flash(1000000); + halt_and_flash(150); } uint32_t adc0_3 = check_pin_strap(3); diff --git a/firmware/common/portapack.c b/firmware/common/portapack.c index d3d848fc..0c3cbce2 100644 --- a/firmware/common/portapack.c +++ b/firmware/common/portapack.c @@ -31,12 +31,6 @@ #include "platform_gpio.h" #include "platform_scu.h" -static void portapack_sleep_milliseconds(const uint32_t milliseconds) -{ - /* NOTE: Naively assumes 204 MHz instruction cycle clock and five instructions per count */ - delay(milliseconds * 40800); -} - typedef struct { gpio_t gpio_dir; gpio_t gpio_lcd_rdx; @@ -244,7 +238,7 @@ static void portapack_lcd_sleep_out(void) // "It will be necessary to wait 120msec after sending Sleep Out // command (when in Sleep In Mode) before Sleep In command can be // sent." - portapack_sleep_milliseconds(120); + delay_ms(120); } static void portapack_lcd_display_on(void) @@ -310,11 +304,11 @@ static void portapack_lcd_wake(void) static void portapack_lcd_reset(void) { portapack_lcd_reset_state(false); - portapack_sleep_milliseconds(1); + delay_ms(1); portapack_lcd_reset_state(true); - portapack_sleep_milliseconds(10); + delay_ms(10); portapack_lcd_reset_state(false); - portapack_sleep_milliseconds(120); + delay_ms(120); } static void portapack_lcd_init(void) diff --git a/firmware/common/power.c b/firmware/common/power.c index 1256e595..70c8c0f1 100644 --- a/firmware/common/power.c +++ b/firmware/common/power.c @@ -132,7 +132,7 @@ static inline void enable_rf_power_praline(void) gpio_clear(platform_gpio()->vaa_disable); /* Let the voltage stabilize */ - delay(1000000); + delay_ms(35); } static inline void disable_rf_power_praline(void) @@ -147,7 +147,7 @@ static inline void enable_rf_power_rad1o(void) gpio_set(platform_gpio()->vaa_enable); /* Let the voltage stabilize */ - delay(1000000); + delay_ms(35); } static inline void disable_rf_power_rad1o(void) diff --git a/firmware/common/rad1o/display.c b/firmware/common/rad1o/display.c index 91813aab..c0c090b3 100644 --- a/firmware/common/rad1o/display.c +++ b/firmware/common/rad1o/display.c @@ -10,12 +10,6 @@ #include "gpio_lpc.h" #include "delay.h" -static void delayms(const uint32_t milliseconds) -{ - /* NOTE: Naively assumes 204 MHz instruction cycle clock and five instructions per count */ - delay(milliseconds * 40800); -} - static struct gpio gpio_lcd_cs = GPIO(4, 12); /* P9_0 */ static struct gpio gpio_lcd_bl_en = GPIO(0, 8); /* P1_1 */ static struct gpio gpio_lcd_reset = GPIO(5, 17); /* P9_4 */ @@ -85,9 +79,9 @@ void rad1o_lcdInit(void) // Reset the display gpio_clear(&gpio_lcd_reset); - delayms(100); + delay_ms(100); gpio_set(&gpio_lcd_reset); - delayms(100); + delay_ms(100); select(); @@ -115,7 +109,7 @@ void rad1o_lcdInit(void) (1 << 12) | (0 << 13) | (0 << 14) | 0); write(0, 0x01); /* most color displays need the pause */ - delayms(10); + delay_ms(10); size_t i = 0; while (i < sizeof(initseq_d)) { diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index 2a1866d2..df25987c 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -163,13 +163,13 @@ void rffc5071_lock_test(rffc5071_driver_t* const drv) rffc5071_enable(drv); // Wait 1ms. - delay_us_at_mhz(1000, 204); + delay_ms(1); // Check for lock. lock = rffc5071_check_lock(drv); rffc5071_disable(drv); - delay_us_at_mhz(100, 204); + delay_us(100); selftest.mixer_locks[i] = lock; } diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 77d77703..6672b3b0 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -224,7 +224,7 @@ void si5351c_reset_plls(si5351c_driver_t* const drv, si5351c_pll_mask_t mask) set_PLLA_RST(drv, (mask & SI5351C_PLL_MASK_A) ? true : false); set_PLLB_RST(drv, (mask & SI5351C_PLL_MASK_B) ? true : false); si5351c_regs_commit(drv); - delay_us_at_mhz(2000, 204); + delay_ms(2); si5351c_enable_clock_outputs(drv); } diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 5d71fa80..f9a4e40b 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -433,7 +433,7 @@ int main(void) if (board_id != BOARD_ID_RAD1O) { clock_gen_shutdown(); } - delay_us_at_mhz(10000, 96); + delay_ms(10); pins_setup(); #ifdef IS_PRALINE if (IS_PRALINE) { @@ -491,7 +491,7 @@ int main(void) #ifdef IS_NOT_PRALINE if (IS_NOT_PRALINE) { if (!cpld_jtag_sram_load(&jtag_cpld)) { - halt_and_flash(6000000); + halt_and_flash(1000); } } #endif @@ -503,7 +503,7 @@ int main(void) #else fpga_image_load(&fpga_loader, 0); #endif - delay_us_at_mhz(100, 204); + delay_us(100); fpga_spi_selftest(); fpga_sgpio_selftest(); } diff --git a/firmware/hackrf_usb/usb_api_cpld.c b/firmware/hackrf_usb/usb_api_cpld.c index 391b9366..38103c81 100644 --- a/firmware/hackrf_usb/usb_api_cpld.c +++ b/firmware/hackrf_usb/usb_api_cpld.c @@ -76,7 +76,7 @@ void cpld_update(void) cpld_xsvf_buffer, refill_cpld_buffer); if (error == 0) { - halt_and_flash(6000000); + halt_and_flash(1000); } else { /* LED3 (Red) steady on error */ led_on(LED3); From 4f9f6a4c7425144ea43348f424c52d150ec67fa3 Mon Sep 17 00:00:00 2001 From: Dreg Date: Wed, 3 Jun 2026 02:41:37 +0200 Subject: [PATCH 133/161] Add SigDigger support information to documentation --- docs/source/software_support.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/source/software_support.rst b/docs/source/software_support.rst index e8487652..66594e6b 100644 --- a/docs/source/software_support.rst +++ b/docs/source/software_support.rst @@ -43,6 +43,11 @@ Software That Has Direct Support For HackRF * `https://xakcop.com/aprs-sdr `__ +* SigDigger (Windows/Linux/macOS) + + * `https://github.com/BatchDrake/SigDigger `__ + * Supports HackRF through SoapySDR / SoapyHackRF + Software That Can Use Data From HackRF @@ -74,4 +79,4 @@ Troubleshooting Recommendations Many of these tools require libhackrf and at times HackRF Tools. It may help you to have updated libhackrf and HackRF Tools when troubleshooting these applications. -It is also strongly suggested, and usually required, that your HackRF Tools and HackRF firmware match. \ No newline at end of file +It is also strongly suggested, and usually required, that your HackRF Tools and HackRF firmware match. From 5d9b6bf4119228c65ea2b72991926e956bca5c4c Mon Sep 17 00:00:00 2001 From: mndza Date: Thu, 28 May 2026 14:05:13 +0200 Subject: [PATCH 134/161] add gateware documentation --- docs/images/gateware-basic-block-diagram.png | Bin 0 -> 195902 bytes docs/images/gateware-basic-block-diagram.svg | 4 + .../images/gateware-ext-prec-rx-dsp-chain.png | Bin 0 -> 70790 bytes .../images/gateware-ext-prec-rx-dsp-chain.svg | 4 + .../images/gateware-ext-prec-tx-dsp-chain.png | Bin 0 -> 51198 bytes .../images/gateware-ext-prec-tx-dsp-chain.svg | 4 + docs/images/gateware-halfprec-dsp-chain.png | Bin 0 -> 79385 bytes docs/images/gateware-halfprec-dsp-chain.svg | 4 + .../gateware-rx-only-basic-block-diagram.png | Bin 0 -> 138600 bytes .../gateware-rx-only-basic-block-diagram.svg | 4 + docs/images/gateware-standard-dsp-chain.png | Bin 0 -> 130043 bytes docs/images/gateware-standard-dsp-chain.svg | 4 + .../gateware-tx-only-basic-block-diagram.png | Bin 0 -> 137152 bytes .../gateware-tx-only-basic-block-diagram.svg | 4 + docs/source/gateware.rst | 118 ++++++++++++++++++ docs/source/hackrf_pro.rst | 2 + docs/source/index.rst | 7 ++ 17 files changed, 155 insertions(+) create mode 100644 docs/images/gateware-basic-block-diagram.png create mode 100644 docs/images/gateware-basic-block-diagram.svg create mode 100644 docs/images/gateware-ext-prec-rx-dsp-chain.png create mode 100644 docs/images/gateware-ext-prec-rx-dsp-chain.svg create mode 100644 docs/images/gateware-ext-prec-tx-dsp-chain.png create mode 100644 docs/images/gateware-ext-prec-tx-dsp-chain.svg create mode 100644 docs/images/gateware-halfprec-dsp-chain.png create mode 100644 docs/images/gateware-halfprec-dsp-chain.svg create mode 100644 docs/images/gateware-rx-only-basic-block-diagram.png create mode 100644 docs/images/gateware-rx-only-basic-block-diagram.svg create mode 100644 docs/images/gateware-standard-dsp-chain.png create mode 100644 docs/images/gateware-standard-dsp-chain.svg create mode 100644 docs/images/gateware-tx-only-basic-block-diagram.png create mode 100644 docs/images/gateware-tx-only-basic-block-diagram.svg create mode 100644 docs/source/gateware.rst diff --git a/docs/images/gateware-basic-block-diagram.png b/docs/images/gateware-basic-block-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..58f99e7c6a7622edbb820923cbd6d4834d2190e9 GIT binary patch literal 195902 zcmeFaXIPV4*Dk68(nLT61f}%_fTo9fz=a{41;~ry9pKGYyq@rM^ICSU` zmC`N6JBJRDi5)sb+(LE~e52XjEPdz@#~~%ftM}XtmnO*TY#niO_-@gtcOhRi(rMlw z<2>^w^;#8=*H?}cpEcf@-{BF7O}ld^l;U=fNRRxPM_)9~yt_8*3B$k%1jkX&WbdvK z&t&-E{bYFZB6@8HFX@`!JqJ%(-Kw2iDL33J%~VY;KF@Oa2r(HAhrI8hzx^S_|Hxfp z7M@}H^R)l3*FldEJ=}Pd@yOpk>epK|?jLRp`ciSjuVff{p+s3A7pl(ud#xe zewx5PPDZ|{QS|Sh^J{+hwB?IZOY${O{o_nHhQBfYy6f-7$ivGqtT8fpG5qhP{I5yU z*gT>7`{(>xsc3o{n`|5FlQ;fRG-P5`M}OV*x5D=OLO~{WuR@gK>_3WznB@V{-#+J; zwB*f5fWnM12mZ6d_)``9y6f*{lTvcTpDNn+#m9e~2~fYGzkkjzY5h^Zzmw7be)TJ> zI+}SqUV9IvD%|;UIo!R$U)mW$iiEQ48H=G~S^*}VjC*dc_P zj-k6*&2q(9F9N?+IOnLD#rL!mj$qTc^#h zH?!jH>m_XKcV|&WO~)4{IcUpSeRig~yBy(LgDN*!o$q;kRx#{FOGy44_V-I`S#mgT z-26>BW;|{jRox-jcDj7(vvncdr76{Q!da(g?IUHM93CC%5FB1Kb0+N4>$eY{elBZ1 zq0*1{Y~eK&TyaQSrs4duBd)N|at>dMne@E7KM7m-kYyE|SA`61BH%+Lh74RMM)%>P zNuDd!qX@yv@Xg+`{q^=NB5G*?_=EO72oja_MGhNn-hoUbYGdEY=&p4T&_Mh;n*!>iQ5`dUv+Df>P>L$8o~KSoRjx$ zI7fMs*=DCdOIB;oOsecUnMoU~>(Xa#IsDFyWq$b}v(F)#03cu3ZS>mv#O0ORRW+lX zkBq$-1Oeyb!aixf3dg8{K{&?2@VokKl~X=@8LM7%QcW-hYE^B0+wzFnQ=_|OeQLRZ z=YlHks!5!;EpA~jc=3bdz@5KLYlu>peW$H!5=b7y*4V&eSH;;WxtP~v3db$m7Y0(+ zOo&ao&733*K0oxRc5D;X*VhfhZ#eCv+lKs;6K&#cN|dm}ZL->wGOA8e+ry@PItc8* zw8@!lhY_Lq6xS z@{3O8|JvN)AU6+R^|p7v9{U`alnevVR~pWs+=3dbs_$;uYqe9j%bhZqaXQ%--9Fo; zsP9qgcBkfmT)g$C9!ujk`>b=>RUeJP2srCpMG^Zko6_6Mr-Lh?;aleBS1efN=uRUS zjR@Ga;=Pqx`%$n!wao2T4@S#z(@$5^GAq$#K?zkX)VEaHZOFoVW;&$}xHG!PuT_>5Rl&!reV>M7|vHWyJpaXpGZhrEc^-c>PKwof88w z*|Bu_4~;=I_+~Cy&Ap0g#R}aV_pJdPcEM)1#vnI)=S#Jx5h-n>40;x+qfP!`rpcx& zj8hc}lFn95vi?+bs6110NPp$i{_5}>-2R<|SeYurpU%On<6f6T4(eg*M zj@_vX+J+%N81-#2Q!tpqN4R@3`ot=FG>pumnV0Lv*qKAjyj+U5GdPw_guL2WXc;eQ2$-vE5Aw^&$*SGiYc7h zRV&*nC+TFb5^X-nivex@&ZFT`#F_TxN=|z0(Vj*hH0h)cnhi?e!Q3OPX*hbVek7$$uGSytS*Oh3iZ8~ zx8J{@rCqE%a7Tqt6uG0zddBeVSDw7Jib^;i(9_jixM41yHMm=j;1w!xz3%a1yAl3r z4IaD5(mG*N+G!=X>f|~5&fJRKYB7d0k9{Vuv`ZP7n@LvFX!36%BimPAx+z4*e$XB> z?x;?}bNAyTvS#bP8AJ7PQhng`tfynIGa5JvDGPFe0Pk@|fV(k>j8^eVYW%W2Y;P>P zo%02~O-L|(z9D%rZhLI#Bl*mE+N|60Zz0E~Cvb+%s+ocMB(m`u?!s`-Z5>vH181v8 zd5?Qo!)Tfxw%blyXd41EZ|2Qjs6_Q;Ww|*wtNW}+1ts5X*swO*(;F*n3fV>Fc+gswsuZgDeIr|$iX@E9<~z7|HK@%5@=e=1t}DpY1Xa|C3`eQcgfUp1-FzGqtB znMO$5$;~JtVUa|Z2_N8`Q+HEx8iOukV@)HJvXVR&p5XGeqe66~nkm$`^!N9syYnOU8T*vTsKW+Kfm+kIjqU1?CTBG_Lx1p?DlJw%OBtiX+EOQcSb09^Lo%b$A8D4 z7j)!{OrGL8)u6$*P^jt^aUMC8Z%8RPh`N!I;vV=6i@QlF*6f*8F=kgAlscqu4}V)& z8hf$QxJi+ORsCSfcrJ|}#tOv5NUalFW5AibeVpvQJq$qf6L-%Frm~OJmSn zGEA1Qv_puB3v%NbLm&p0Cr@}R=37SH7PH*hcwQG_?Ui4Q8Frsb?E4X8BwKekPmFeT z!V5(BKAzmX-Y09TKA749?KiO&FCw^pGyfXK*jq)M+8%WqT~d7y!}#7BuD^~6zla2m zG&lcM;Ze&MwQKLt#K3vnW4vSyOE(Mn{c11aJlzW!5jT_7{kZ! z%-#mgBzbR-j4nh`ORvf2M+7n&Ug4|XK~fosS$e)_l4`A<0?LB|(On6=(%!Zly*rnT zh~m7l#3I_7JHQSQNF0jSX!ncAVqU3>$ZQyYeJmcg3LCNtLq9CuB1ICQl0g-Ab3#hf#&am63C!>~G<)ZdkQsiPzJda^NeOQfrbO06F zK75w?4x^!)iJoowfEAZhm&9DU!f}c8>F)(Vt19G{w$^K^fJXENvDjUae&nV5V(nwk z3qQ!dQTkKZ!L?+__o;iX!d>}Q1Q>o(GmRqo%Eln@_|)wm!Kp(!KDb3Ab$j=AeJkqb z8I{vhyIrnU7}}tJhgqvD@nj_XYr%4=Q+tz`UVyVik*a4e6}K{5r;8(YJGtwd^$ zQig$;%iLuA^O;qLP>f%#gjw%5=LLGLs%i2JH=-# z>RLA;7pBrC4oBTC*9p7fZEA}qnTq8<9iJR_o#xvF(A+iNarGwND4p!O^zDxAT%ObSd-3Hx= z?p5JuwVODha!GTKLzZc*k(7OWjiP|)_!W+pP;CYM@l(T2eYb-Nu3l~g;9_ED0S5hi zaVX(G(V4G26D8^L&K?Lg_Jq9!YXaMIXmfZxh} z#?2i3+VQm6$a3=+aJF-MabM)UvkqQ?+YPC@#YnM%&u)M&5870 zGDS^#uCm%!jV8C{c18lcG3_baN!pTg(r6#a*r!v!9$nEI=BX$+32C*#QhU!UG}S7e5Wqi-On$*=6H<%;x*0T`7WCD}M6C=C zz!kld^ntPLBtxsy$t%<>P6~mQX{71>R_r+}(1b>(o~xwMlasU5@tX3v)UFOhY4{6@ zSxUHG^^Rx1uS5W3X}pDXLdyWYH<`8sV5+jd^7PY4wd9bq*c4s0CCrj`Emxdldjy+d znX=gB@h1~itTM3(v;WqX%QZUjFtF3jMFim97gx?1_AxbvYwJs=urDWwgReG!@Wb4j zP0$Mgy1OX@JoGaIEJkrmqYnqd$-hCLV|gb-JGR^-*tD*_ z(4DDK?e)0pl(#Eiz7e8D$bXBy#&iupxOeIl z9XiH-Vy`9UZj6D;$S4J=gAu3pjS!6nbr`lUD0N2VEP50@n=dh}L6`&J+s-y{)$Hy4 z%>+-;9=^JH%^95CH%xb{r{ zU3FHr%H8_zo~q)f*QIB2rjU58ant8oXW!~seK=_(`CRJ^lI=Dw#u$n3eZzg%Dmdxs z7w)r$b2O>1+>VPmMRF(Ew(wq!0BL5B!wvlmuRLRg4yHoR7aGky3=GA3SY7>7H?F*w zwdl2B6%{LtV;gGWl0`p1Y2*^YXQ)YA7$Y|mF3g<77~wLNv-=qv?iGobGTt};n zqI`!k;E>-wi88fV2T+)DMEXLEXsFSXSJ&d*XW!inX|0Wa$eoy)@AEtjEH2u%X(v#i zOcWMhGQ@Y%X1+Wd4IxJ3mn3lkR1><2r-XWUO1mT%*QSAII`d=~U|cCy#}2`>vI0Y} zQ;|PtXPS(0g9}8?xf{E{>&VVl(mYgZsCq82+30#HYA(sMVB4esui#kcTI5sRkUBFn zu5IMK733W}`C;Tmt?BRR^eKr4t4)X}&q11Pm76cN@Vt4E5;?sI9IOH5X%X1hGjnqmZlwpsaxX{a~P-umjL{yjCAEyw$ChU|FJkGJ}RR)te>MhKejFyv&PKjr1& z_5CET_0M_@oZ8wU(Z@cf`B?juoA2Kr4}MPH#%Sb)rZsKFy@!Ak3POINDa{&d@3p3o z?Skm@@3i&v?rE@(hHQlJ6I16e2{?6tl#fim*@|v!%mVWwXxq|o;_PgMR&Vf1ZHqDu z?Za#YBfZgONy7eye*L=0%2z1wrz#{N--y=M`;c1j)t`zTzZTrrCWm(@yvv6vWG8(&wDh2mWZD;sehW3xQ?+hy95NWOP-N|&n zLGdWAo_1pe>7sgMC~vRVM(0o|UFe*YgSx2&i*2BGuceF#L=QX zAm!m|_hbOmU7EkpZUwZ)M9xquD(HI&?xwNh=GO>zhO{CDX*(mjg~5?~w#85PdKeDB zi&B%c>~MT8x4)Ssmw0B__^iC~6XFYe!#(?qT6Mx)J|Ft|sp+^8XCtMbRfu+&AI2|Dre^;i;e0kQrxfmV`q@v zGonWo^mB{jb?734S7;Sa@yZK~?%s|U%db$rj$`O9bs2YPQ@dqVSRWECx~LN$Vs2)d z??p=|QphmMv-4H0D0NpwP|zihOc3a4VjqE*s!uhIanHOh% zI7VuqknHR2YxdbiZik*u2QnTXVxne7GUmNFAVH3W$~zWu_6`>xLn>ARBPQcH2B$6> z*eQNwcn&>-*rCA>R@RrB(1*;LF33G-O)lTVhCtbf8AvD{L#P83#(=@j!nj&^xk0>A z4_Cne{`kYTS_hU-yDmiI`Omlf;&91mvJ0Sq?m*44LgG}@qC8PK>0w>7TEBN^yfS2u zAEctHp+gaEUx@kZcOEu_ znN-IGY|To*HsYt!mjNfc)0UPmE0|A;jBaCBhv-LzWqZ%DvDUgNu`)+ob#pgT zLVrA$Wi+n)$1^&$Tj2eUSpn&L*`&v!Xa~JX;H>@iaBWrZDv@nVAESpu zT+{LgDe7NDK#G8$Aqk5foWeNoJ(R=^YI}zBm%7xH)7ga33(SnP>+07w2Hj-$E@L>C z4cqRu8YgpYUX-5MVwnC~=MCg)m7s?OS0O$WiRM8}!M9=GkBdkX9*lk7Ht zq^B`OgRLKAgJuL2%dC6D1N|ZxQdNDigE(U zxU=ZcdOQErxAlQSAJOvo{za>6KiOWtQ1b7mZLR=T_3r*k!PE`Unf@MZPS%)j#5mgd|Tq7J0K)nxSR`t?9Ef_Jz37@Ou;T{^TfL#>;ns5Lf85^Nmz)cq@3oC4egbS|WUY&*EyIVYuzF-==SjJYq)5 z5|q!>o3w4iiU6~)*(xM;=_%bdiMLW)DM#WP%UZ5Ee+vU z0o^cALJMJ*Q4mam33#& zha_RT*Xw*(JVY(MG`U)tN0ChWd2eOgFO`Zc5!RZhvj9m|K5gz`gX86El`0;t9LW8i zKP-@xAjc(e>PR%1ShLjI$^BXJtQ`<&Sr*G_myfbMWJjVRxfj4%SSxnkrlkmF;e@*!Dkp z7{z2`asG5+Srv96_ikP024F(%htFT5RxtVq(z&;(9P(U=)@)g0{OuKSrBTKTr>E)& zexz)=lm^YEW%G+Y+aJ`udBEfB&Su)sKR~snCps2CRN&Hi=S{vDDSzIds&QoBCu!$? zuIpxhUnPr8$j}4GAz8WQD}>Ye1>d6DermnN_xVbsu5IeXJ-yr^N5!?XZ^cS}2Is3M zJ^^0dJM{b>$kDn*%K(usrjnKLR5{Kv^PQxX8PG&sWrsxPK})i`&7|nrW^XAvxwQr& zs?|H;rbvaQIO~Fuu4%xx9T2bohWg437o~9vOev(pEG?CGbnIFx!8HE?t@g5i5}+BT1MdM!n$F{p}` zuw99OPsbQPV}%NKnk--Kia3v2l%Kn^FRgt;7lzv!v)YDgIZglp3{KXbRM~583f1Nn zY*wZew8ObH)80%^bQ!fAcZbTCCt+WWo-~qB?XiLQUj8HA=0G2eUlKp17K5r{88y zoKSio!=ja%#Y~7Q+(~cM^OMr97&nVC=mm`Mq_4(+vBJq}hs_ zucO%(iYwIosRY&Pe2_QguwO$sU58DAH|sHQd&L?%i#A;}r#}UH`O6NB+7_x3VgWRk zdRV8;d%0PSHU;wBeW0E`xn72BY?9qu5=Kt9L8LXZv1{D1d!mx@J>AdNj(kYO%@Py9 zuX1Z!T9mW8e}4s0x#h06BPy=%2+JN4gUzH4kP>v?apGkJ%UTe;$SLg!JHb`3>;q4B zqIC=WMhjke#Hc^+I;Mv+M3o1y?*eXRjUA@Iqw_| z_2dj?xkKUOJoC%NuRj9i8(Mows6^mwn>G?5_80|~Q=25NUz~>doHCr^F3O1od9fo_tQz0&ZSD*`0p;$~WJ#j}*Kj{k@h&oX73Tu@<-# zh~miZ?tO^Z@qhB{>_T!z%NNGt_oyB##{7aEH(9qy_qhyE-%4fhwXBZ;b*5SbSGXdF zxt}tq*aR+#46l7U8`R8xi|0&~fmj zy~MO7&>J&R`6Ic-2#HQg*MTo+p#YehE$j^e>_cJ}z zjX_2DS#iXD{a2P{eE8c%pe{IoXDtCu16crEhyeDQpbQl_L#Zpb*pCLcTHwF2`vHWf zrug0muu6#y>vSZEgSh9!xF!x8G=vt6}Y_hIp zftdDf_S8d?&YhCap}YmUdrv=^M#{X!Low|dkpl07W=6e2(48^egfvvibJIldmGxXJ zAD~A1Xok}jioW1KItB7!(Qd4{GJ1*EkmL@+8K9~?O|%sTkk<_F=VokaeJr-H&(*z< zbmbaK3GLwW7^up!2^Kt`!6L-dS3y6)wQl8Mo}rUtv&wkTGB^fa>~?%BWK{Cjx>G0S^bw2A16x%_Js ziQ8h64sM`iEq;3Yv$~Iv_oDt3Gk&CYa;spJboRL&ZBh7n+Qmw;N1$qvK*u`zTl0Xg zJT)ZhW=3&SGe2GORQE7`hvtAq>>dZY0kKs@0|e&uA1S-z%czu0Qs#-Kk@e#KWq zYc^gf8_AkH%%&DSx-U$j3RhNwoMB)npw$s#~eZxyh1w_hFknHu_CrMEB)OqqDJ<7Nn@ z8MdHw$S1qs4Zze4t7NKNB=sg}ym7+Z9a1$|XY_H-+04R#r@U!Dqg2vAue^anLp zP2d11Yt9B`Fvd@DOMI0${jQ;_`(Gs_!Q1q=t)4S!zA}h(S~OQ%Vv!5>oH}Z$}1L5Z&XqZ2%Q@pf((zw;}3tA7@1C2L96`o3^Ml5OQ6X_t+?g;VA#_}5r}&g~OC@_eOd?#|yiT0E*G%xH zv4b+wie60@i5Eno+gUSg~shqnAE@cThVflr`5Cf(j5MtvLw>8m`# zN5=Y|Fq1LnndxBmf2Q}Pc_+&OrcB>K6rVQEg@rYHG6wf9p>3Z7W3I^8 z8y(@EjA3dQK5mP?{7Lst@9+JF_f$Am;*aI8pQ5gt(KK;ya_!YWh=h`wz9PI0Yq~SpT4|1O%03H*0an&A7??&LDg9-s3#bevenebxl&A3iDO^{3=9=r=w;R}@i2U+ z#L))bak&6}-#oE=#^W^TRGB;yc$CHe0=_pCqj*ywC4_GhvjM{Kpbw|oJE4q4ZX~09 zKhsugQ_v2j;0Y6S%sl9pncS+%KORmz1FSm+s^ezq8d@}xFy_J4rSL3^G56KO1b%j( zep2L+J=I$BjCLn%3IiVxlGP~Rn68T17>uMRM1b&p^Ui+!EX3*JSk84h&rcmiU#&^x zOIQJVC<6Rq<(3H|QE(CSJcE8_>aylgk0+NDYZLzW@PIy2?BO!5erlQ1A(sNl^cj@k z4W#TqbXm^%bn5I%d2?uVamy8eG5bUKEtL(P8#^^{*dxuef*38 zd0(>@<8e@K1~;}hGb$mI+JH0+J?;9Ig|o6{l0;iz4u}he4#gln7{uFi=@YBHFW6cA z%$1zSid~OE)NLEpcE5iA7Q$@JRt(Giw3rXbVS1eh4j;Qef8<=@1^0V5J65_4cs`RE zg=6TuSCDL@fcl<5Grk;?FxsQx(D3&A7(g|^rSW?U!e4;p7uwd)0=>vNE;kH*6UlG= zE>K6(37aRhf6+3(alQY({ZDi5-^u7tbMC*D zXZ@w0WKQRbKAj0mhx&5!K~D@TtNGk#x+lqx)|Wa}p$DEN$y9y!8tK{x`u#+>rDZ6^ zE`b)lYgAFa*P>2f_4Q;ZX!RZyCQ0v`u#DtH@ANI;GbUk!X+B&vzI|n z6nUhnFOp=Oi+ghT7&MTc#0HSyCqZ4lE60->G@(~ie}7GPu_^UnoGGXL?mvEaegbrQ zwH&kSPol_!Z;zr@VFXY0Hj&@=aIDGsf$PL40Bvwfh~`Q1xqu7YufQ~jcm$jm`k}7j z7@uu_s*I@kmrw}!a+1d8;$6>&j!<()9Bc@`3Z>XP*v(5wv6w#Ts{5!l7 zp-gZdypIAr9y`9+SV49uWbfSWrg8?D^t`Jd$S3(H0DNf5;vVimRj-UfSQrX&}#B1+^cO3%(Tj)*afu0g^fnnLVdmDFGy7K%-6Q7?Iw5g?(fBD`IStTju z1Afg<2u%2kh8QU{wPOijT=jeyKbfh?4+;M<+Z=8niq877yU}~+mM@MRi02CpPy~gM z(~wLAo`C7@R*aKwdwFI({jXx8$<72%72WGq(^`otOMAo){qdp#5Y;g=M@SY84di5E z-^ZS^4{t0demQ6n`St3QIPlazll`-@|JA$vQR6>0`^Q23@y7qZ3_YKWzP-B1rgS$; z?KZT}EzY)Lq_;!3JAt4iL3D(;7qp8P!=f4owqyIo$XE=rhI9|BKK_OM60L0De zNq9mh>9(O2bJ(w-=qqo21lqv`(dSZ9pC*aP9zh{ry?vbPbCwPd066jpfFL$KgA)&S z2dDx7)RbO?d31+;*z=c5mZN2s2CR!9z@ipqN z)eG+_gFpI!eIxUj<5F_a<*_FLQc*Hl{hTgagn9Or<6lnzkE%K0259|cP%tALV`nMM z)OG9>{kgO2p29a!e7X(w#3%>?I52-(N~`LB2K#(60^5Y1%%=utg5t+{0-fVg;-k7L zyKG7t0CzZEt@@uIDwt7d=T%}JJ^+eP+u`~Nd*^0OjQBxPQa%Ad5&5VHG&2ER2o!CP z{U;~_EtbSA>!6;?(Q^r`8%7i$$VuE?8E`XJ3BaP{8q@QM(b;QDXRU4yQS2I`Ql$}4%fnRGkiGobaL`5lbR z99$-oCPL%)g$y9dOnL))q^y#T%|W%%?+8GY6!N0y4lV`?g<)R%Pe9251FzlpoIge; z0`&B|_ieBOu7rDl1NZX*(won+lVpTmlpPLcVy5@ifm61~Rdx2zFMXR2FA)6I3jnql z(t%r;GW^Ky-2hS%%Ae1n!6?-{bfkgaf06^N(4id815^o~RzKuRy|lZn_Tyk4(1doO zAxtTl%ctB2io0t4(ff7XUX};AcQXpyAm`jy4Jo<28JHDMUuXm++7E6jM0%k32Q!?1 z2QKTrNTv8|y>tSBcK!?TryK%PJ)%LSbRavh?C_&r{|U@L8G+Xl*d&RLbONEe8_@yl zn~)oTa*<>x7x@t16$r_l=Pa1XJ8h>2A=FE(Q4;@N3t#zeaGA#qOO9XbRm0Ql{kmZI z^uZot60krgeXVXC$PO&~s_3`>Oe*B@kd9z_m}o4ZTxc&2q-?AB4@uyixN{uCJ?axq zmk-qHU#d29&C7T-w&U=>1xW010bC}mOho%@y|g%2qMTZ05B92Fln0A)=$-2y$}h+1@#KjQSRM{yZ1B!Dk`U5o!eZrE08Wuh>B+k33`oBq_|VAsGT$eD|cd9h*mQ-bs zsV&>rs|$g8YbLZ8a5d@Ne!UJkqAyx7@X|dJ|oYe;s>x5S9=X<{ydqG zYO<_%kJ{MoiBn{Lpzh8)0rtHRITTbUdwM1Y8b6x`I zL~_uk0UB;yz3YfPxxTHAPu^#K4yH^ZHf$W=v^`=TBZofJF?`zQl;<(?L)MGvwsmke z!}%ZT(uag8#E!{FC7$tPf`Z0_qn$ARZV61CWw8!_ayNN45QpE1s=3+4-mq>taj!0b zB?f%{8WMYmK6{b3$KrAO_MPxIYQ{w*D|IW|o4c%$*75*p;6=agzqI}GhTAftdM?jr zng{66JwNj#j|;a8Jtty}6`U`>eG8kbf0VRvGW!Hj=YDc&Oh+H8g)xa8|J+!?Ato4n zRNT0raUSRqkF)8LUL3*DXW)xtFnw}KfliL0ZC^`I!>eqTiHQM_YPbuT$WMxr}PQxu%!ZOzxIm_{g8FkxR@I_Z4Z{FBjviAWBDMf+n=6e$p`wZs0pK_ufhxG`EDrE| zFU^feSk{-~bI8Nb1JE(Cs8Ezz1J`7NtQL@kwLIXXRE*BA1OSgOuP;AlYYck$;DxfU zydW4@OO>|SJy1S$y2eTbaHhao%pqS{0MR~`rb2VLJpgW6uR97ppcfp=0jBJp$Y^u1 zdsLP93xu6jYGU2Nrb!1;9iLOt}QED@hM~ce5z< zlup_S4h;yS3KnQFX%(gNBx(WBNAgFAe}wo)6aSd=AD{TgC;ka6f1>jri1P<0{=td=M}ciZazmLVIJEI`9Merd zkV=ps-wKo&E0BN61PT97l05IbJYex{!rmX%1mzQ&+LeSoh-mI!`B(CCO6A+yZMkT+ z8jMj>tt#MmSi!MGJv*ir!tFn!#F1IYGQ3QOB>fhndv=8l$U z0bkp4Xgy(U1lihkCE^PgK~8wJ;RrQI`DliE04MbTq-b;LX~!=PYa}`2RB5m6d@p!EKiT-R;<` zNqQ0_tu;QG`aXN5PAjSGrv!Hmv+(EOHmD&Nt$y_`Nm+y4Wqwy@8{AdLZiL3DO{GFF z4@+I`OmPxy`<>JD97Y{hv;RIKl2wx9$nn+5#re%qQL!bD@T+nfpb)_eBna{F4Ji=2 z?Ak`cf~`e#T|Ntj7ao^!?}#l(jm!M*H5!1j#PG@Uw6Q55HzqzzDgEFlna0uM2rf;E zijYhJ8TR0oaAHlj7-}WnNBrTsxNVL1sMYFB4bQ^sz6fO<7-q8|!q&NmIUXTrD|_~v zK)X$8rRXe-uwlWM=g@707nsFUUanB|uxnksnB=uIEr^M~^(igif6omZ$5$86JNy!= z-x&9RoEKX=11W-$dCV)28Q!kNf;0|DD}HA*h-tX6>@7TmVikz9i%H9+!@VsRjxF0-3q}m z+IH(DG(bms&p ziSPh!;eVfwZ}q4$CagVon&_^VT4-j$P1RclU{*#POu$A{J*xc%gUWRF1TT&D0S2)o z&2Ig+4|6e$32~QYaIv%s_SKkT%dOi}QY+Pf`huBDhZ8(%_F0ThAHTBX+BLwf?ONmv zsb}^5=m@~5-4XA<=G))~OmK$$2W}4VFQBmE4OMk+b~^D8KR#f(n1De*EXpZ}E2$X* z9GbIdn$QvA8xZf|3e4q=Ijb;kZkn{Oc&t!X8a2Mb|HefIU$@ypZ7;N6Ix^>_BsuTD zIOapr+~Y?F4k9@PcJ#&uA2FLMG>B%<^ZM{s7p?p}tqeFsTy&K0sa6?rE;!#jpu^{^3Ukm8WB5vg~0#|{;lOJJl12*kf6?8#9We!{_!^P?>50N*fnaRnX z53w!bTI4|$FI=#Squ;#QRpw6DIo52Awg`*Z9& z9hJSgI_{2RB-=d0G<1_Y?d(|_5mpZzOoE%#RN4`{u)>ZI958==)b|N-+iY|qyNpo& zRu}77!B_+wY9xSLzB@}H_U41_WzdiSh%^DsMk{cTsKzor+s+sGnWNbwYR()7+*|2S z?#-_l{FdYC4YZ_EoCh4~k`5;Ct`lgiATbCuRfTJ%#srI*T4hdpQpx`y_U!|o@@WEY zEUZh35r%pReykCldg~&M>;Qr`D>A`uqELPDmj(ndlKS2pwiRQIRC0UEEpwS+>X!Q> z^+vTkd}VGk#kR@MSUrIhd+1t~SQLv)dgpJeLdUfEQyHX~IfWc}Mk^T9rU7(%;v4Ys z-$xw(MG%^T-|k_sz<}-S?|~hBx4`187f>_?J=G3-3H9F`q zq=riLJpEPs75unZ0&KjkH~BKc!fhqcocNNg;8_bzM55zb7i!R&pB?|zda}xJbT`&< zEN4iq1xV(kxqj;>e=4(!@!+&7!x43wfM2};P@q&UiJJk^E9K8&mIq=xY*TZ0#Txt| zJqCgl#O)eG4DNy=Y(c1#U|YuZ0!DIC1-n$@bZMMPYV!*)uAh^};w@ zq4+Vv5|#ZJk8Lf15W!SwA$1Zcg9e$kWdP9L8@6WvjsOs^hp3r?8oYXX_M&YOq`kRQ zT@?hV47~)pn<3`-@3j{_I7RrF)w_CG!rtxuR@O?A*vJoGO)lBRs8#h!BMf3lqHZQ2 zS<6W+Z%B(Pd62hW;@!R%rxGr@TV1=Gp-@PPtLP(5I()RsC4 z9nGI-|1}LD9zGi{Fj63*9+l387w3mH;7QpQ@?d~R%g}1I_RvUe^8ab1#e?dy*j1-Z zmt0ZRk|_zDED}cjhyBm(t7qp>;|eaU_>Jm0ql@0_b=HuBl6+hYv|@kV|88GK7JHw7 zxnohPdrOdby?1TdmTWRN!@&Fu2uNa5*?$Yn4Prw#)YRl~ zqvpX}_wk)9S**vUygQV#;5*l_9mf~b+X%SsXcl3QpMtF2=+m__;6RgizMy*}dW4A7y zOO;PYXTFEASXJ$)1gB3KI;q`sj__P}i7f(SeMZjZdA8{Sq_EW>ks;}gNY4nD zi%^a8%9zj%hJSUPZ-RiDs`Gl_G^iuQd20?F0;4`=+?V*&2G5jZY`YC`&Hg99929|_DGoBd8L zumNh;m*yw>=h3bHN_jRKF~C!oeS4@g%-%8U!(gJm&^Wt)pb7Wda>#0i4l z&a3&%_)FT+!!vK#GeP2vNbD<9j|401aZGRhi9gl(fcS_*e&itDsD>1-lTwP^tG`Zh z8ftC4iYnVU_(58V?;qk#kLO|-!s!-v`TE>3>bj4k@#z?L4?P{amRz<7&(#I0s{x&I zPc8I3+YN4ni@!2%Dq}RdHH6=(+l&&9w06r^RPF7`J@50R*AF$yE?O{Ow8m=~jbF_! z?YkDnKQLG==S7e~eX)meFDdU6%v}1!Ronqz8utsC%#peygKpq#! zbiYkQw8vxVGL|qV@xpEukk=PBf*y!+{3=~!#@R~4YLg!7q_ySEoamtikN3O?+>gzu z(g=20pZY*Xl|2|v2fWza@>D4}iscy-8v#RvYOJ+ak&~KpMB%0Em^DqG(QRMZMT)ek z%rceY&69n2ynTkK%)>F=K{$xVu!Mi8%>qYAT9$RTC94F0c+vToVEbm&mdPH+ww>ZQVQCD6M1gQGHUdYpvi=*%6r%|=8P zyAP_zX)1px%S_vV3%+OCmOe5USGyP$2^*U7=;ZG;g1KJzz*n2!VCA|-BG~<4F=glgtJU-x}w=2ChQcKPlZ32$+1b?4U300Ua?GBxs!7KL6&8ID(Cv zz(}(n>sVy$HsPLyrhO{r(!(g1 zFjBE00`lX@qo%f{U1B%m9Fumf#81t=L$7h!3ADPagI(QYA6Ozhv7g)esB|7A;Bk?N z4A_*bxyLtqSiX#JC`(w_<*m5U7cGI0J1`{~G2nEi8IQAUvE@9-0+s?Laoob|&5pb3 z;rcTj!fXwla%st5hj#e8mO)B*WyK+?SV3Ao-2NEv5KiF?_FPJy8#Zw|7^=cUKe8J&O?#HDN2Ue>^x* z%(Oh@_UOA&+1p37sKgQ+TA8@;;hos0EQ=MDbs_uP6hv0N0FV zS@%0bT(G%hpQx^3*j_2MjqV$g!Pdht9+S|BuY6*dR5RqUo?&xQxt`AwC07{@R5a%P0ftTl_}bTZBk> z=^;<#{ODTa#6)P!nd@OEc>|BMOytXRZ%?nBbalOWCzR0@{L8A^V3_+cr9^mU;}o@I zed{A8H-+N>Q-%-xHn50IfRhyAb_EjE5X7Joa!Z{&8~ z2`Z_tmAzDgZCw8OwhBoynSP>AccY`q>F6{<`>j6Y=C`ts*D0FDoviE(lQDZxJ`>_Q z`?+-Eg>bNi)t*%e=jd&M`WM>W-r8Y}oO|3J*iG&qU5W7*RAV`Wto(;l>~@3tyST>6 zQ=X1-zNsxEh{xrP@Uf0OkFa@Qqw*F9AGQeDpC`Rp%=z-&kDU)fS~jA-XRtfO{8DPj zZrK-RF!uk4z4wf3s@uLsX(E;=NKpyBgA`E=y*KFy2+|>d2!!4{2-2lD=?X|Mp?5^8 zA{`-s6hV3y2p#UqQ~u|^_dUOR&im#5?uX}tF(f4Hz1CiHjycAdYoEqlJ#DPj3!lZk zBXM?roW*;e306KZ9e$VVV-lBPgA@h7gK2}??uTbPSA}keK(OgYz5Vu1c@=y$FV^DC zQwyMsUyh)yzs=#CQ$Lvp!{zMFQVu?DNIy8CSZi;rtwK1N3m~m?2fRxtljl^CePSm} zw%n~IS=lvsG_Y5_y6Z#WKP8Fx+lPbcZpy3mw#(XY+Csbg>VRII*OQZuacvjm6@z`gynvA zvFPc1$-%D!pG?az1iK;mD^7 z`_d?Itau+Vz$iq#l1~oSW%HENfgREKb7040h=({rwaj6IFL`JB)eVn*# z>(Qi;IfHfT;Vf*iG^ePRmdj!>Fe5A@-fZ}aAUcHdT0D>bOZjta1BGwN!&a91mz4fh zvA0Uox9!K7(5|xc8uvpshojjPsH%f_ozuXj&1*Z81FoBq7Vp!v4!EmyDEX7Z?t#sh z512aeO|*V|Z`Ix~|6wxpER2e|Wq^>PGw6&IDla&@?;N)Ga~j%9B(~8V*T}oF_1&>z zdTm4SA+kX;D%8Mrz*yGm+t!R9C-u}O57p`8jK$d1)A`}1J198!?#a9O_Gc8(_7kDLs1rK0K4uBDK1ZBPN{zOB=jD^I0fP)ty4c)-Dwa0t0@RcmJoSjFMVo5V9D7?{RkdF5+Q02y;A-OF$#uLut(Mvrzp-wc9_eeOqO2d+ z-##s59#0&cxS&N8C$+)V4P8^zTz%-+D9f=CYs%xk|c8JYv5ff#ZMc&K+2z@vi2YI@nYyYy3%qAAHEkEJh z)<7Z1HQFWgbZV1fV!7@^weImv@dDHw+e5)ecAZBVoUXbow2OP$NnS1-@8s8W(>*B4 zU!=|rTJ+n%FJ0PleL*1JH_R4$6o?`u5Z=FnO?piX zp-RLg;qP~g0GyjR(b-dMcmVa_;1xV4Wttc8_MM;Y7Bfxm`x_IFI|JdJVTZq{qu0k?u4@zhkdj==TksC0l}F7tuGY$ROYy`v zch}r48}{LojwEYeh`-maAnP)WK3?E3w0UIw{@|B039TB5HrUD%-wF%XHCCT(QMA99 zd;VNn;N`Ez*zdUFRCb0#9YKD~#Uog4B+!p4naUg80atI64Edw72#Cr3u?Sv06;nVO zx4pi?V?Qs|;C)&%`#6G1xWP~&a29v1iM|6r{Vt2&zJZ9lSb&xH#JeExkG4WiB5$pA zhRG5(^G6)N*muf`y%5BwgO%S?tP*K4s~LND+5t;T>SgE*a?`qxX<$0Uo09tBXGbzqo9HZWX=sj z@SGIItdQ@4o4U&5z|vz+#^d)u_YHVJiz~v8eHnd)IEO<%c*@|VArAV5*Eh58?-U=F zt`6+a?+Q&OSnCF$~ zJ0|4IJQs|#d-pj}RQ5}bf6i(J;gmO>V)DZowZ!Jj?QrH;0wM(uHohkHRQ%j!X+&k znWSTQ%st`pe4QuEfvjwvI7F-yf8A(fR&Qh00Ijt(T%8Y466c=rhexSN=g%vi-DOj& ze4}!ljjY2Av#ucHJ4uA9#5HR%FE%0#l$2r&jL&4RSBUQfuX82S;ROxArO@e zq4&2a1qMs?8&~40NGn_KcHkpB@Lev}LkxYQ6B-XI)RntgUqF8d`05GheE3l8eUkQO zB}v@TRCm8}fm#N3Chqdls`yAQzpv`8jmc@n(kLMm|QvtQFz;Op@U(UUzi8JJ~WiOHt2T+|@aL^hn^ z5WT6}%5)Xk#)VkH!=d>UgQbFeZ#7=p5xM4meo}4u_~F#DgY6s*%#EI5>2xE%+5fxa z7m)#1UArDS13{k)#pFlA)#)`8%&giRNPkKd-X?KcxF9#m5OMcfw?0X7{*N*T&51OW zjjeT_^_{gk25zl*p9(OanL0Z?cyQd?g(25)dZ-#uVq8mJp# zD98H9)Ky{nyLK5P)HyMO%F<%By+${Edf{#_@&;OK5BXrFD1u+3;G+&4SK;O5SN(@K_uYGPS!2&bJeL)hHXt z@1pMZ5HaEp!8>DGekrq4UJ_~B_sullLV6wdoisM0cA27V3=?V&m6n}8o_8N^vp1$S zxj7kna8XD{5of}gpRVn}dcug&!_l=U!aH{)tS779Qwq8ac1Ka;%0uCjVn>7b@x=Ua zv8y|S047&EMQ>Vwf7qArRxKM|p6YFt{33)l5#8B4Z(qMVTrs4N_+rkb;}0;2`SFv} z+Wwv(>6O)H-I_yR${N4nmjh*P6_%k8#HW$QvEei9-;{UP4$@`b{r2U56j0shxyb(3 z_L)wi$?@eu-gfV}RvIsPm$v&r}KK%*_C|Vw#S$v#6kHmWSJ1L8ax)>L^ z7r;e5EzMlRnWru|URRj%>Aj&&OU=$-Cqav!Hg-N>M>TbXQD#g-k|HCt(P+`IK(W%GVjNv|5~ryY-8yEE zR+eK(JbW&6jfW%_s{^05_amQ02qf&;ds>1WMl7U1m!Z^E>=KYz22<(;768T#rvKpQ zmqC!v3R6ts34Pg@B7K`$;}`BcN&>Q5meS0w#T9)4*4L&y zehXM!^T*QYXv{Ty`Lc>W4|V^qZ>PJeW^t0`Zg^{^ z+3R!*k}1dk3O25}_gygR|R+< zWG2_Uv_1yw({<|}g_GvrwH4!&13#3nhY`E zv8R;mJBr>wV;2o7PDfB-8Etq-A|ASUa|`+d=tY?S;wdQ}2?(rNSh2TK!892^i@rmE z_f!mme6NxzQsuO&=5et0+;75d>lTuh>A=7x<$;OSj+pIMC3Dcyft9@Zp-t6dZwAjc z@+kSk8D*59-S^N=D?#tdZu;vakflME!Fs{SFD{wM)7kzTW?ECDTiURIG38Y*t0X2} zkwVnV@_8=V<~}V5vTb+CIWm)txbf%KwzJseGvmdh3=!Ab*+=8M;pUkEa+CrC(+c)? z7+u#q_I^A|8Lp`irTqf#ilP{8BpZb?Jl8aI66ks@2T`;sH&CQAmQ`VCPNo=nxh8X+ zn)bsmCw8^E0^&uT9j#LGLw}pu7D`o-3;{{RTo9SRI2PU+!(16qhJ1a)U!1LuFpJ8f z%XC3Sz3O{;O8o``9s5n6=EM+ta2Y9jh^F$KJqhR}pcZN3$T(OYJJdg)oD6R6_MwWn z0hfH3zenk)d3aK>uwCdvAzm-9;Yc1&DcFtoV*SAT4ZcQ;nW*%a0uC5E`EZIdLe~PC z>P@ym6H102Pcwk5er~g_Nou*+x@yU{q9T3#A;um|(nDnq{XRT+0CGKi;+hl)oMN34 zg)~`yV7*<5c0z%7ZiT;&7o!owxd2hE>&fXON3dc4{>AwzR;B9LG(sgPw7~>-s={~q z!%H!gr3v~UHMRnl*K)tc%GwL%LcWthsis@ltW9(^ zU|yhLV^yF%o~YSaiR5lgh)y4|+U#ht`*!4QFCX`Wx>F@1<#W)DGADrw7k|K=$X=_d z3_O31JP`Y;TT^OyEaba$8&b0Db&Go{lwP4tEuQ5$ei$MDb>ZXXpv5m00I*MV8BeWM zY&uUY%7Kvnp0s#4@J{Yo3Y~1YvdY#>ys5ujMc%}Vo34fug7Po~b70fr+Or0VD=ak( z5*;CwnHS#2@uispPpz0PIj6qUx(6Xf9nO;U`HH`sK7gSll`|^|&Eopqm80p>7Z*Lp z`-e^}aMN89cB-In45jx5Syhq7XGgm{o@j)}*%5NGfWI>U^@)-j9}AmsC&o_{j(h6p z*gJ{KnkRue=f)$*SzeBpF1Zk~jixm>nY*`f`>SKc52hM6 z6<5xTD)u;p*WX@%X0rSogr7sI!*xPvgucsvlP{0xVZQ*SH)T(p@v%MQ^Vc-~$|`-& zd%d^OQi*xsh?i0Q$g%_P)A15f*TamWnhlFbr|7Xf6vgFi9deyxZF9Md7wK=^OfIa9 zlXr*v%Nt7S-T7T_52y7CX-fk43#qY4?}D*slk@SL6m# zC&-#J{KnzBnMrV+d$}DZdL;GIKx@>d+T!z^tn)N%()gv+rvyc>Euo^XQL?stk@?cn z&-R6~K2-Qi*{|PP2)Fa!_c!%~-nht#Zg6RiqkR$C$-Bf4?#~pwU3hCZhG8ZFwn|By zm5m%vSIApg>FV6yJr`s<(7io{WZLQo@{09SEtGQ&yT@P1KAIOr9U}b3^`Jv!Y8_$i zV(2t#q31$!g_t&Dy45;H;nA<-n9pyb<{Vl|BMny{xn?qmF~n8h)^5B&B#_5@M zosN1Lv6e1^kIq}!K%gQH@uMx(=Btlz#ZXkm!YpdYVuRp`-Vz-s)lmmQAeNuNU2s59 zr%^>lvk|b+0G&|1CDXl0Tq+xKg04w39YJCr z&s#&SShPKkk1OTp(=Oca>fcTRr-N|c@WOiaI67ua8vV`WN;>P8B?0T?sg>M~>Y zZBm$dsP;|o(YIvhI(IaIl7*J^SglhWl}7%gtw^^cBaA^QNqUmQICpBA16&*IatzPS zOq_M?Lf~BrzxIyMloYL~(#zAE;e^J%SLk3Qg_D8umrEBbk()P|20v=ITen-d+f<0c zB?~nQOjYXe@n*aGOMl(BB2d|`hjwL(l)|}*N~J>Y@K_8p)IN4N^9g*yK2?01H2+7B z?Dbc-aA>mb;#`D4HtvPpfBbUA?0QeTKNd0V)#64R&euc)?&xIvu^z_WhB$^-l>-`E zoZSN`#pF$a%PigdW8|{dNaOL?f-0XA8ts%wcmUWSS~0O?*eBWOxkQ6MU@!?;#JvxL zQCZt3;7(DP{QCiJQuq-wQ{Ns*{tu(M^BmC=$9!AZZ3j#BISnBCs&lIcE1KT`f{2!^V8xaka_4A0rJLJi?b(y={3>&7CuV4Ov{lU+;9- zW*7e6Q)7cZaA1V-Kr18EUdH;eT2qfd*Pm3Jej2LoyRg1ffwua%pWWPhQQq9u!t&Da z-jpH8Jc?4QH9HTHmyL}SZy;K_gjadQZ>M{cLa&ptKdrT$T}p@?w13B7zt9D#bp)5J zlY5L1K^?w9(g%rVdguWCvCKw~z6aCL^+ow!jDGWZIG3Fc0u2SW-iD=y_(o#illQDMtgZRU@ z#q(3%&iSk<@lQybG*J5C`;FrhS&&D4e@LxfL5j!A)2g(Ciqh!lj>&5#dd$?k{$HlX z4ZU&SY#@UYOii?qyTfGT_&K_YE*!%8*jIL=E@V`D@0gLgoU3Re$z~aC?VvE3N-<{} zI!Xr35N%SBT=(Hpko|HueeBjoa#XLZ>&WH}=9RHylZwao(|t&Qr!~rdmnqv8(Zb3n zD>g0T%zKu&;0mpz3m$`QPkblfXR6u!=RfL-47v3~A?M=;A9sl8JR+9s;$%ji`RYcZ z34X!5or^QMQDmRLZ&84Wu&7wfW@^9duDA_qG#*fCYp41HVN&)m@P?^Q*vSbS4BU}1 zs6UNAo2BMHbh?RkR$wv%y957mDZ#gi6+A%r7VD95=tuzonl0$M_3Aa7{d|Yh&YZYX z*Kf1v!9i8HsEE(mQ?MyP#aa0!|7rEY>6i=pvevfUXX_|qq3m>jRnm5*37K2?Ya3y> z^rmK#(5O2dlAkE)QmnN=yT_v6|Jr~Ub$;js|M2<%-WXh+QZ;5G3Tn~Sbj!i&xT^@0 zih*^G?y}g$a!_+mwmL+rxO=1vkrQAh&5m)8)i?wq@>9Zua1OkHD_RqB6Q>QVDB63Y zxYWdO$+??E*m(OgpR|zviN{Fd!GE3mP7t9kTN$;E9skkej6#jnp0f|Hde)w3s$0mt(3Doy@rQ_i6%y(rh7|$7U18$c*FbdQ=m{0Q3^PP z96oFa1+eCQ_lIl}VP&r*ujF5!YD9z^EJ8QV@z2fWy6H#`LRU9$bX%ychgFDJK2_~U?KohRY$OBmT{bGl%;o5ND)I(rjZo<60MgPg6RUzU(H)CT#V*FeNA{^HcuYj znFBk$Y`inIF>5YbYin|8wLY-vST&n5ebUfO)8msNSbL~1Ndr65bq74}Iff4??~gv8 zn5?xm6!1RbNs?n$N{L5r&kiuwgox9^LS8&^@88&-v+q;*k~1!5o{+1`S~y}AHuVs& zCAS%N-M`r~nwtE@h0=L${vOijsnivou&VY@%qkX*zv0xd!u2PS(;KBh3p>Jw=!^MX z1Zr%sc8tre^AnpaFE#_L#GxCIIl?+X_m~N{Nm^cN*?vRpUIU+ zsg)}BonHXuS zZ{rzmzux#et+*fqKp5kP_uDhW^H6pR-)K_+75Fajr$Qtlbd0sj`4nxv_M^UX@tRSQ zQC3}L6zE}6d>VY48jVcyNs&Yhs&GVKBgmH)vqrY1j32Cz7bqlglJURjOCO76RFDDe zn!44==Wc%#jn}o+wNP)K>`Gs7^XC-rJpRTn)3+?Z5VO0&uAUZE?r)d#g{t=$oH9_3 z47bW_PMA+A9xJFew~Tah(Y?${2J4|SKM+BH6z{2^wV=nSq#cED<(Ge|r2k4>nCXrj z0(>ak{46LiF#nc8nfY;{$k63&mf=WTuuEWF}vD+vX8if;T99lDqT(BnF-%pu4`IIn5Ced?rU-& ze}3C?<|%7ETX~Gut{#eR)r)L&@KO;d_bA{kiF{pc|^0^)(vST^_!=$153F9U;oFtBL)&OsF_Ks1O6pJ`PG78Ny-#u{iBXMEBlvP z&Qozu^u|zhbW1Rh;I~iQdbeV#7JlT*h<;6}pWOVUOMz~A@Fgp%YTe-13U_(~KrBsO zzx}!t8kgP(`O<}7PW5!gpG-Azs<*gru_#&SYNM&9o_|;j{7&*j2EjNY1#JUFAYqfA z!Eo?@QM+Jqft54{WJ-(QzrHwKUYuh+W_&cI-SQy}QtEPxMYWzg!Fp_Jlf2Gxmq7{f z+yE^SN1_v{EBBLL#$oMn1^t}-dtLs9GkX8*lBpCaBuw3tn}{km`6)HYr{3~dG6iv* z|7AzoKWDjyD-JK2-KaVF>Ac&oHyIs4e^DZc5-oH~L zEDoh*Srn7YZPdwM2?=CjiObS{m9C%1MjS(a+Jo z2k7B1b(w3md2r_`x75;+)25CfbwRC>*Z!B4Zzpn|P+FLmw5(?$2_ub3&tIqMBz6qwZT&Q$uEByk4Zx zK-^j&;W4Sm3o;h^LPu)`>Kf#=wT;OTpFI+u6{&jwihL;@SQ1nx-Yr@we{w=6G`pD?hGkC~VblmQE=DqWt z#c{Gt#i2t|sq~wi$L_>APuGcXr)W*4tNfMeq`%*4XVv<$R?*^d2l@i8&TxMLFpCuw z530z;j=z3)ehi8Y=OaH5Emi?ypaDo#X z)E-q{;`arOABB-U;6zeVq1+o3uh)6NGu1_zVn#Pa+E#C9H-|z>BT03eYziMv;*ASoXB~ITnQCX?Tv)BWg-IiDg*+XpM5XhL^+3|8FhVyA~ zRcT30JZCWZL+j@g<)C^urhYJCB8{eQ+K~?1sB8y=D^#l=f zT1y@&pZx$(YV{-|mK(xiX()!eO^%cK&df8r_EqmN2&QepF>`nG`88~h$a zC%lfp+_KfeNF=oA*Nm=GqhPB zBMZ-8UH~_Y#mELxjX!ET9bsFa`_;Oyhg%7iWY|*| zSQ<#SXMJJ)XoQEX8E)eC}ZD{ z3^T}iD>;N|!+xkw*VsS5_A3J~9Le9@6c?MtywxbORROZ_zr1B_DN~? z^Mpm1adRz2n4|9Bdc$t9SG~^PX>YJyz6teC(=A?XpnV zJ%8!4YFDiw9&Z1DrIVN6i#*`T3?MjG`GT-3e3~yr#GjdT?edQ;54?|!j+tF;(l*3F zx|0q`UImhvvRN)9q_^|Ne*sr%|ueKt?7wb_*F>@N;^$jQKOf&G2uCo7X*aBEyj zm`bv9Rv!r-mrs}pNhJce>*PH0$*||K#dRnw4Y2|I zrn+MR2W%r%%%t&=%)3s=ygARLIA5gDs|Dv(h5d2^cf85DvRU6sA%ENEN9O&C>th!5 zuovB?Q+c&k>oz(a%fTHQLl{s`byZ%tnhZflis$k8jKEV8oP)H15Y z+f%3VC8XK&P&v^93GWO!=QmA!kPOE)X+kJ!O^sb5dVOmSTFdU9R_p-lXs9s%S5B9O zT94(S#LUWU68!O#-_kGowiZGukLLzDO#{EjoeWdHHTu<>JxmYhk&K zO$uEYStAjdu#H3*JteX6@oR1c8~MZw-RpNnnncx+`}N+tc>4S`vI1~p=4n319uo?5 zCp__WE{kucA;bH&Jtcn4sF=4tRl;pWW15XVDx;HwbHz_~_+8g}o*b|9Exy^Crh;!til(|Mtals7 zX%n6MYCO%gK!vNH;n-xh^=eY!uys~l^t-L?>b4s{)%PKvB^P;`#nnx?$_26>8Sed4 z59p(|`Wo(rx8VWjQaaU^(oh&83j|8RJ}!ws0;k2Vq|cWqBWM6Iu4mmkZGL<|(`DmX zfbgks^D8^6-fis~CAaoA$2T)Fsgx?9#@-`aGc@~VT#rpAEASwlGBk|{K+UOh7-ek}JO1Ts%{vJiZjXmamEcW7B5gAwEJ`62!538uY3 zgHpH@38A^kf<8V;a0(pi?LJ&RpW>Aoj(lOCyRZwF+arMXU-~W+;C`A5l>jQcKka|P zS!Ky0+XQk&yiW!JQLr-Wds*$}|25;&TL1z49+TyHmUrUIgiU`lONvb`*y^dU{z-l5 zs6V+$8@6v|44;6|qn(bYij>j`t%P3{2YQ?H&S>K~~scKtK z8ekVi#T{;J{NM|X)o$@SIFLIV98_^S{Cz)+BlKQz>kKDTL6gVHY*PM)%!2u9NQ&Er z!YH1ckWc8>rGtURkyxYHA9?Obq=8Ti^A{Fv5~QgvndtGQGZ{>ePoBZL5&9(PY=9Ac zg};|kE+vA>mEZkhr<5npj;dA4_Y(bNIxZtwAq5uKyCD7Jdz2i}a3Ib}^ZNSaU@CyG zty*OU7_UL^EaO;_>U)X(al;nd+KmUq2AgkbfLm5L^<`8%Ug_p=Uj~ClmKyz6?WXHo zrZx-xTSK3iW|1;mE;d6#+@koVyzjeUr`j&c0&z8#E>Lr#ExXlJKLX%cyoS#!2#Wc| zs3D;CdjXQajo08qbr6b1r!%_D7}TW?5fY>r&&7Qj zSaLIdvM7O{p978u;Kv^uEq7!JIETTzBIFjj&TBcU@{@=<@SDpsQH4U&^=`w7{biEN z>XQ^#_G2?t+{-C_KGu{mt&dzpS;x~z>txPn+1r7@L2%2pTILNZi%hNq8=G+OHGm5o zELUxBf&(>)iV+u+0rWN*9>KkE@CNt#zv?tVaQKXa`u=f7&ZCsaS8i6seE0el@7ljJg*=t78phN>={+@5J)4d zLz_j3XY|scOQYKp=TrN0OJ-pd9QfV|ta&38cW+xObk}m$9$=8+BB#E>?$KD#_H4^X zz{HFcG2doYL_HkVn^*hh7RutVpDfZ{u}w&R5(*?`$pPvg?_VM3)zTOY>(laT#c>ul zIDE=yf>DI*ztU##x@}i-+`gjp7V~^AA2Lg?*xRev+$w1>ZT{mpr zdnHfTX&}&WL%6|NdPwhH_*nw^P*n8=b8oU=kw7G+uvz8X~Fd|x?Pymw`N{c+=1 zE?_gJCaW(^pqqL;?;>q<=1JXt-<)87Ef&9mL|DthPGYEL>zAs_Oaiv$HS^dggL(~o zSgu_GmX~KFT!25&C|Z`1f5)2Ci?q!UmQbI3j%@Dk>eVf!>RZtm$@Pk9g-iBw-t|mW zK6K{<%3sYULz{~m>i05>hsSo}JRhLBAAGkg?$(DTv)d!~W~)5l)JeyJx4KMY=OF^B zz@?V{TQ#n7UJm~~ZATD|n3nxh%sP}}0k!iMxNz)bDMo?k8IM#H|EfP12GBhDH-H8J zW>;^p>TRu~?}GsN7<1W{T&E9#00}EgTbLY`mHX|C@(Hwg^xB7oXGDsGZo5YraFo0J z_(#wfFg3|_f2Pf$eR{kIu_vM-HmUu|LtoT88c@hvk0SCYp!=C}0JAQQ4#ddnttJC$ zB(R8*@x?MmlG%QPa;a1mpsop!N@azz_lbI+i3uy@fpHq!$~)Q2)n}Qk+iWy4QV^(i zd^>>DRj5a%v$)x#`Dr17;D8BX!2qt=`GrM;h4KT0+qodkyC5a6e7FhjA`t>km-N3r z-N)e&bo{%jXk~#VT7zMY;TQ-PN4FXxb-8I>a@_rtRr7UDV(CUt@YHY&(MHHsmy(o0 zg&JM#DQ+75$k!F{F6HF-J9!sf=*d>DjKO54PBU59!*}nUMyTTJ#aIDU6KeK7O|eNA zlwqzZiQeRF3w#5tM3XLx<@nAdPzo(#L410pnzMLzZ!&p?#aJ45as}d=)V9j2uI!?- zBEC&dzuJj($8B#Uy-({bn)IRMcF^dxCkISuMH3pd?X2l<^){ib_mzTxWNJ!(QWzy7j57oUjq#~IH>f54H}-O2%HBUwMgJ%T8reP=}K~M0sF@X zeP0#&8qeSZOMc@gjF$pMi%CL!f{eM@cfmE1f6pfe2ibP})d~o1gghXIy$&*n1H=95 z_5A=pKUeTV(_@JngoL3WnZ*6a#SkFy7u}|;7Z)|G5ndl5! zK`uN?pHXLhp7=v~u#93#($}ek-m_W$)Ode-;E5htkG*V&VQ_z575{ZN_KyoTcpLZL zTq@}(?8fD~tQ{UZvT)ox5*28+D%orXzD+^O`Z;BU5uMZlr^yd0V`I@Q69dqAkfq01 z=$_VFkY2R<*}o)S4gMuExm>iSy@j5bBIx@AnmB%xJqaLM;yZmWmkh~gvZCYmpA~iE z!5ba;3?c@O&r>PDjX%Fg*_|t%-d3BHeT|wEd@Otbc)UDIn!8}l*@7ho z4D#qxkn~EaD|j(l89p1+M;x3x;2E3~n4$mUh@6~XWJMhNINb1(S=LFMh8Al9jRl0~ z3(l!iwm$p4c-})B`QFP!Z_|s7U8dVF-ZQVS?!!86O&V_vk%l{HAwomd{*d`cn(o5lg!6~pR zw@<);%udog1_NRaK3Jto`jQMfyv#23Ht1}T2o0*gS0lwgYhW8sgMf3Weq*5sRc=0U zQ%7(y9E3k9BSCt%#+oz!u$qe}V`IhRv&<}(-}q__0Rv*`LahRl#39)vTErIlb7SUh zhfk(%8AT_Xwo6=j>k`^p0{Am3n&bZxHs|?YK!49~IMR{3fMcRX+>#VW%wdtz0)$ux zj{Fm(gcAoSd(!v8trNfrkz-+J|8YXn|HTP~@vewTuUDZ7PLC4JszA%HZfe!U-bh(H zSsmt#=G+!qKu(P>(-SW1G?x`Ar)MqJ>fJ430q(^=&c%aEFxRaXpDd&nWw(B2pYme~ zFa`G=?!Gs~?~mzAr!?MUg032mZl3Iyx{fqF{KB zkl!VN;Odc&)11^{!P(lHX^cCGQq&ZUR14Fz7?~yvfYxuQRu$5J&sF^yiMpJi-m$ zZvaSAQpu&>HV7UJuC-k`Z{)=|6!P4C&!=yX+6`vbP>4L;^hi3DNmOsVugm&VYt<{| zs@1pf%7M^VD9|SsRL&1@t3R$rARad190nuvRy7a+y7;b{*c}W@ZiR&x0bUMBCjC!1 zHdz~S$e*A# zta2POooqHO9N2Rpfl~e1Y@M|D!}j1x7$Iwz%AV0%caVgL{);38auCGNZwOpngq0uu z#UcFrlnbImI0V5P)Frt@6nD2+3pDcqS-tYJuh)me0JH@EEP23lG7hhJ;hmWI zzGn!2L}3V=)uiT`#6#t{9rzGq$9vb|xL5NDu7NI|w;;~iymBVG2M^%<>c=1kMsf^` z;2!2>?g!WZM->7i`M*{hV6jXW0&ANP(%303)07TtN}SH(gk6L*b9&`AF;j7|3OKaC z{7YYc_r$m2nX$eIi+wsQz0Y@zMLq&W!s+FvkgB^JHS!BaYQ0I>88lv&p?=9WVY-Vd zB}U@}tEo2eJ)ji5w+YZmK!b+~0CQ1{bfDDP!GJl@1*1p+=92fhVFV~5yEDHR(iLBQ ziw}wKf!`pI$ISejfAM?&9@POfzzjQkWOoZVH9D-hm-j87-o0g0*=$%l!1Glv%k)#{ z?I6@(iIlwd4B*IfV&4ihruPDV=v60btL6|K@j#}i`eAH=>G}E}y)LKy!FQMaZgg^k z!HcweLI6RE6|kH; zf+mE?2RqvZ5uHWDKKC%L)Y{G;k6d{$0u9hTX_9Xet>jjtc0jKjPnM3d_9SRk&jG?< zefn3x@~s|?EnL*j%`&WJ&%H8^TMO!Wha~a|j8O_Ay!{j(Dm{6h{l2NqsvGmW1}4O3b=Ww1 z7y~%$enk~w5F84~d#(VSgqfllg3xoICL`B_Hz1ha0ROPi0zW^@v*cdP!(ZKTfGa2k zn=PD_KpuhdvBP*p8lQtQQW*IY)V@5$EtSzbjtixjZgLRA9tr!pC5iaBsqlwA|7iEK zeFUh>7G8U56{&~CCmRIe7|9w`g{+!q9UZ1Ym=YY*`E`--dx1@$Z$imF&dW@fM+XEe zqwZ`*l+~gxI$OwW?ZwhDkf0izFZecfgtT4sf}dDu!Erse|4NW~vxIMoc(X@mmvW^F zT0Vj*5>ug9nXle#0OC`L^|I)pYFY;?d+So0cH?X=*oO+)=K9iO4V;C_cQHnN+?M_t4D4!)>pE-MeP*b!|3F zG35o-PzH4fGht>LgbZNs;;=e#0b1c`3u?ZiNW8k4C$$ywmV?3}WL_#G` zi?ju_3R4RJ_{1-s25zvUFHkXp_5koAhyF%|d!YCEzIe*m`-T$8l=OR>ObeS+!23nN zbSl_%qMt>!1B`-U53IQ^LBj5%9(MyS;Yh*9j z^oO+{ke0Z^ysf*zIQY{pivxeh3yt#yrh!}u7FgP4#MhINPqyakY&wx z&(Z9>!9W?y{~F3*jMQ(p@26W01Al4ArjcspDIVB3UvZ(@Z<2}W>OvnAuk-}H;dFf%k|_k?E}(O$x>v!sJ$t5* z=UNtb!toJmeyg4$pl^HqhjhN!Vi%_SFv%J^#3m4iC6aNQB8l zkHH$?k)(|G2RZeMApVs<(04i8zu6z<-#(K8r*Siw$-KB`M&7Rpds@ z&}>g$1Ox{8d}vG>P~8ap0+_g?u<&-g0C3JDMdnc}j;~nWT!W6?X}@s8b8VXmY|7G7 zp_)9A2_x^LGm#1>RT( z1R7s+2`#pW=w#pXg{nPuRACEm3yk;F8qiKYiCqA_9xJ&T?MW(BgivOQoE|dR&;mmi zsLGDw&9ly|kBAf`=-c2GuevoxHo!Y;;J7De zfUg|k!p3@d4-+lO{*D%yeD?(NfU)>I#Bj5d&w&u_Qp|ma;1y<{4b7b62wbH9Z7r!Q zAahEB%t>A95encRSArPTM}W85_7$+OZvFwNulx zB_8+?52j{+JeqP74=GOk?H(Rp6X@!mODEFt^LvUZ2{lT%>i-`F{m&!wQlH1t7%L6U z9I)%-wm$#4hOYxQTt0$?^k94g*SucRd)1tcyL3j}k5kSR>GMY1B;@mtxc_)~eY_0! zb|3-fvxIm!xcE1-z+O35j7@KT2>|2pN6Xq7(4+p;#04;%Yj@{vH2L49C^5)5C;HT& zdew!R;)B1RE|$h;ztRy(JOlmkW&m%;w0xjv5F0NLW7bNmcwVBDaEn@mUHNkl3Fd4s zGR1P9#;gqID$E)G&2&m5z_?(v<*V4Tm}h##e^=8gU;u!RBQP)!v?txa3y2Sbkf&L4 zaZH%)qlnbUz59VYVF(xzoI=cQK-XoIUIWK=z%2?sBj^L|3R4XvTw1BC3$wEn%^a6M zgIugp9=`9-)jH}9e9F653;r+C-U6t~w)-2Uq>=9K?v(CEK)PFy5D)~UyFsKwx`J8BmUz+dOFlzc>_r zSO7Jp{~LSut9cJ@bGFUxM^Sc^1a33&qGweCIdWh(AsqPVe13-o7!)X4A`~R2k2OsU zK0uf?GQu5{-h6L<%@oMR^k=~EK=lq_q3Gb%aAtZ28ewozpoeu668I6C-RmLzuj{H$ z3B6J9Lqz~PbfN%`Da#y|kZ^!ca7Fnj`Yth3(3v9(yv_9|z!*OoQfvMFZ0JYI*KqAJ zLcgRp88s?6kOtfqp2b%az<2q~ih=_d6bP^jWFl%fuz=8?z!C(qE=q9l50l@1PH~ou ziwOMfGXfBls!pYD3II&5K#kk|hbf@1#6rRT4;4{u$ZFtX z`n$|8s?ERne+=CJDuYj$&_@J6SWrpT{S0%Il0*TIsm!$Bg7IG}VPN1A0MWUO2Dr`E zp!mzmS1N9-Q|LRoYM*rA_J`-C!Jq^N{sebd7LNxDupQ{_1^z?%qY*+z02UxT&s@(5 zpf45qvQ&S+6IctdW>{38LjnzX%9^5bijkbcQ-PVoM_ zUV@9~KrE(kbTDug(3XS3p!qoL8m`yfH{zr9fOHzUXvN|f!Wq9#MJxcYJ_q-!3)q_f z2lv};jF2Pl^L%!ftq9Z?jTk7{?eXl_E(qPEx`DV{z)c}=Qhh~&B?ONV*}(>WfjS4k z8UbDjZ3?3}-)|fWwKIFasNZ3^{H=A#f%?e5$NQk`iG-XU(+y#6*uTsYtXB$1+c9D> zEy`r5`7(jRxJzAd%D6=`DpwSiUMVdZr}()v$_s`9 z^mkqP*UUJG2!|sQ@Atpvqq+M}5x@H6bMfm;#ryK2nEqw2b`+CqW>K;R39ElJLNbGb zuyUq&Tfy0bABA*;?n$W#;p-1N$)|JNhKGB}o0Xw>f(E{0qD$$@{lPj`X=T{Em!p0*1Id6fnqrmbsQYws!b(`c!_Mk5fBTF zOZ;`8#f%OfSmR8vKR%&jaZy0Gz#_nXA(t{&&`o3xEqldktBuRqfs0TM%0whs=*H+b z#_NIhr}!dYImF(uYOd#vUv>qba^2MUeYc%+QG(EUHuG$3{~|uA)4d@QetL4HO;bsZIXK35h>&ItmKNfRdU3gQ{MyzLr8y@2P{!E|8`3qSBsMu&=TEkTPuCF z6glAj_|s&-ubyB+O70*n&wazMsuh!D6lMIFCocYzn4M4E$)6rrG~pZ~8OLr^1XwpL zulqk3aajTp;sFoNi_}A`a`klDE#7i&-Q8^eyE_1ztuE-uj;m|RakB@%FR6&AbqBuyFviJ7sNR~Sv;EbQ9)auovE#K$HH45UUrTnepW{W)D7@dUXCFJb z39)^xHLAmc$xpUEx9=AK*x0r;c5lw>>PZjcgEFYF>AkhzJLyw4%gC+;#W7iai?rmp zrn14PqgLGHWR5@OG(o7zz=o=K{#e8}scz_>xC!L|BVj1(T=&hPSJVkBevub5js~RG z!bws_MrMD1jj_@8?D;Z}TL(HHsBSJ532asD) z#gMOEiLWSaf8QazxW1BE?(m8@yoOnbJbKE#y(ycv1G;x~#lKQo3K-q@u<-qJ4fx#w z$`QIpg>RE|pEWt~6)=Cld-neDEm)Nd@{;MgxQOg+UZs4+wOppR&5d{rhXL(ewKo!N zx9P_w4?{cwdeXy~Z=*GvUN;RGpGu9AfYYJfR{B_6TtPBJ&%Z8Y-{MWwN>#BW^SMgP zIn5x0h6h4mi4Y=0X6`;e9cw$@$Vq#$4ynBLGO*MinA?*xxJlyKP#s z&zrv{_MB(0R|=MKe`n9#SkXxRePY(3a!~rIr0@XKz<Xm~WZ|xVo zUo!p?o)U~ED!>L6#ZPd+yf>k!<~)0!)8$ppByX)Em@3{@WVsAcB1t`k?)K)7ekhGv8) zyY)>2IbpIQ##Od>=cD3@X;;cov-zX{a0Av)!{K*~5my0k&%I?f@QoQLL|grHzwWB4 zZ_$Q(j!0nD1w9dMIHyy4_3Sx;&vd*d9j2rA#a%RnUIVoqEFlwdiff;u4jCHXjMwm4~}u_W9i#0&uhHv7Q`kd(^ZK0 zrIZzT{v+T@r{nqM;b5zvSJ8aAu0YmX(PjTcLY>eoMuhhQ29^WpfJ;ww-hcSD;(0+$ z1f*o1Smy0G=*!QddqZzdm=Kz(F|-z^fd#kOs(4!?BQeKOv4Hj56%LvsY~Uqj!dP5j^u!7qI95Jv2u*CZB45>NuvAO>c> z5)?QR?DU&O9PiV;I`zWuqd8&9clyJn;&$ku)=3Ul{eP&aL6bSCczkWcDX6-k86t;1 zHXcGSC@9KIPb2ZwE?D;E42alSbhR~q)Ox9;mQ8SlZuc}kTSg-6#gDrx>tM-fRp#>9 z;2dxOL|twX&3b zpQs4Irdsp2W3)=E(tmEx(_H*hRKo9Caf(vBl<L)R1=z=u8bWQF*`mq`Xox>AO!_f z8-oNq=>~)YPjgMUeC*i;KCXJlmb7HpWxe@k_uWnX_u;r`t^Z?ouFOO-UeKfBXy(+j{G z3_+CfPw{ZLUUxk`Rd$;?!%A}H*oLQzbRyq8va^gP-f}g`b z2*M>RfiQ?6_hP!uV)Uku0*-G|v>!yD?MJ}9yZ;&U9Bd+Alt?-O)f~}`aCG7Vl7y|k z2(8PX^ExpRlItjqb~C{X)g}%OpkWfd3c(5aOfNJ>^6f2d2V4PnJsdjF@EiuN1en#H z#^Ry=OK2f{oh8^;l8OubKe!#jPwRl{^t)!g7#81qq{PTml6F+DF3=v?gB>CtgEW~m zKw9gBFz~$WFubWNs*GB|optqj^H}iC47TzBq8>Wp37Hyq(HhEK$FWJ93Owtm!=mF0 z($N<}m0i^Ag12c;O%zpeG`#6=g`e@~eqC@9%kb@?LqE_7IIX#Om=IZdPJKLRNh7=hzd93x-YX@UTxL$uV=RT zNfb0zsvd0k=ObhH-jc(U|E<}riTL4BzCj&hcp-S2cmJK!r9>EkC-;0TzS%JULlvEvMiipi}6)eWPTW4-L4@%#2sZI%65MEsqT2m%r-{F9j6cC835lQH7u$`)P*^(CZzCgxj)s;n6)tQ(OQbjchTGO7E#5?`yi^- z@2Z3^8v#x3AX&0vy!->74o`n1F?h2ZbRnqW0Q`qpNt<48Yx?cl@|r>^*a>NcLcHD9Rv+9sPYxy+#NQ0?IjKap$m;; zZV}@2u3nH~6CFVxvWad`D~d=Upg$RJ%LB{M4TR)`0+z5@bcqzVHXR0hqWlnUMxYk- zZCWJ*b{aF(j@M^9NV}-pA>CGb7=sGZ5#vu9Xk?CKPe-y)h<|~30&IeXKAB(L(T`2y zS>jYusC**?1%*$nAPSoLl4tw%>91caGBz?z|EB9I?hPk>G#yGEIXL^aV6{}Qv%DbL zvEDUe%But68WEIS_|vV8I7qd$%&Ai@)aaY5pHiS$WvTdqqj(*Q;Ob-C7!s%AV)+XM zo!DtUzus^O`6k(@a2qUIdGw{^+O&Kk6Ng&WRgEGKlt!|R=K{yKEhj`T@@rD{rE-zC zL8hWFV2AzsV`~XMEgEU;+AH5|us~J6o7a$B@4JiyV701V7~qX(jWKjWP=IzdQv*F( zak1u9?aqLzSw_8uKO37PS1$1sP(A44-eQ4O@@uWvV=xy0>X#lD%g6;=$^hP0`uzR+ zW`PVuvS*t5?4irU>ehWWSG*V%(Va_8eXx{}MR>1arU{Amn}4mlL7Jqb8;4k%#PQ+> z`J{9%1Deh)}o?T zDoJYIgI}{{u}1#4tz-9gr<<$m$+K7KRV-3OMrqX>9qve4S#e3>KOMLWR3IAVI{bWl zi~1)#K7_}~Ycb4Uy)D1xoYkL6{cyH097CyJEHiwfQ#EayyYAOtJ9^*5A`PBBIrf|g zXp1(2X@`Hvld*+mbj9s{l$l~R-mDud1+WYKI%0-+HRJM)!#iYdwukGr+CT&taa zq&Yb`pj~Fi^I<>;F8tIRZ-^U1Dr%-Uxb$gBh z9&NeWPXnesD5kPy%p|<(q7?U)>3yvgYiNX}B6G+dHIwPn zm)v?J79NIi#o%c6$mUoJ2H*j^1p|a%f(t~u|H+FEx0V%%7J|m3>D+{vAV$|?4?c+4u*1Jd4NO6mq|y7sPFzU zs+PibeaJ=lEP{5KCAB?tw!(-Xyb6ANg*{!Mj^@&Vn4`dJZD(&dAu=JR;8*J5Obj*h z+koChfEXEKwzQr|iSt=iT+C>!rldA#@CjKa+#jRdSDgX-zdBxh6(%!mR(~V49f93G z7E2e1R? zGH?==wLu6NVA-)TA?+SV(XjA{n_#901oXHRa9(G_?tJq-nu&m_Q09?VA>Mo}l^xRN zR;o+z=Er25{d`5fQOoN{V&0O4x4$;NytC$Sp;t-GbKn1}|Jj{+?>CKbo}K|{WTsgl zivk9;VuDt~b6HL}W~v!HPtrI{5+_Pg@Khq6l514zvUku#fZqESpofN#%O=e0)mCbw zh<*xCW040fS>Hw@+D(8U(rTsIss0@vv|HEKj~_^X3}+)fK~N)yEe9I2P6JTi6*&sC zLbocq-SZ^64tEDqXGQvD_Wu69&TfWAGt~9hHzj8E3Z>LRyO9oyK1!vmR!fTzH7@xq zTmk`EQg73Au(^S{^MP$249-jQy@=8e#r1S45Q-c;rapocQwM+ zaoCC_X;eGm2@$@qMrAbci?53H#ngT-0z&=ZSg&R?+)jY%z(H*uPr#Zzkz6bdovXEfY>1Pq{twksIcHGY?-dD>>%dA@}4s_P506Rv%BN`5#6F-RfJZ-ygNL}DbBCXNOxf-4M67eNs zum@6cuMNU5K=(!&@K4SqgU3d?!}}6UndM_o04-y}TUuJYHci_-oHUUw#xwa|1%pmi z_EDtTNN0Rq02B3kxO?_+|J$tNSkV|dvaaq;Xi1ep17Zl`$RlR?81|79kfzAw$ESf zWwL=j<`K4k#;I@ z9k);mH;-C)lss9#xOh|3U-$M=u}1SrsH;Mg1l4@g?ojP}L(bggH)L51)k&s;IuJ?q zdmp-#G!Grm9I7ZJQQjiiQS^1i50kh{F+XH2r-m74E;7n{!!fM%wuXPo5O8xfitDIS zVMmVsys!(jC%4b?+t|-MJ)G1M?YJibkp$kdd+4Us9#Uw)Q^H4C-y2kxv|FarAjb;k z-tad8+OmZ`Y8Zox4j4@HG6F9LZKNQZ1eeLVFMTlWout?~Vu=UVXq~?yU9nhMgmByc z^?YyPcnR5td~lzhjpwI|CB7EJ{@A%X*yywC4rd{&Y@9vnP87H!2(dz%Lb ztWWbM+7bqY2ofF-X7Rw^B&>vsG8sgK3zNt}jT@>|aUgFwFh*uibfHI#6u@G3OR8O&p8R zVA40y!B~EN6bq+=X4Eu<<}+zNTXZQL#yUurMdMhuRLo)TQEoe(0+hjj;Pdu*#8hG~ z$GJDH?f&YZG+vDsKk{}d4~S4gep8AMW_Go##43E!e{Hu=CjR1GX=8@E zM?Z)N--6A6HBjTS)k`G!nylVttmH`w0z9pJEEeWNc_NJKY~u%7g=lt$;o(Klv4x!! zL={{P3v6@cS_&kD*w{f}_(qHlj`kIJab#JF&8(BGFB}7xGwC{b*`PlfU!|EKiV$=# zjR2m%u0EvQN86$CdA6{{@8h+v*C0JDe0#7^kBUo|lee)5rZq;K?F_}-GW5uOA5AHK zr~Niq7L!E^*5vx?h!o5RjF5?-37eerrlq2i%(PO3NJ=B>IG|?M+D=g<*vZV6(IcZ! zYR~fycU25TR9R_PIqj|29y3mhHvkD!R>%Ju?L}1hu>1C}&sXgx0@5U+?O)6Y41tqBicBGtzcore{E9$Ss5Sx581UI(WH9osO+b?!ds4+K9+pp(dBfOHk z!qK$Rndm8#wSd=YOJDMjcFfu(>p$3*(cvm13I81TjPFU576Ug@DPQXH`@8u73XWqj z+Zo?XyzE{?`y(lKaaHaKWkO(`!${s+z^O`RcPH8Q2>CtW;COpcDOtYb&ujlpvE5V1 zc+C(AGqUGWY2In9n8<`}*&plsM%TtQ)O25bf0o9T<{m#RT@)Dcco4IMEADE$2KFOl z!oj-n6d0fcE$g+mwJCA%g<;HPf(kQ%p^Dv~r6vc$H{VC;;~Z9+*+J=>%3obPt|?5z zEUF;m_WNpRJ)a+GElH-uQ~(m9ukFenLZR63jO)BL%rrr;`^ASaV_IFTVGP0nTf2)3u)0)W*M+ z(hx5l^yF5BT+<1`>!={J2p^WMGHKVX6bor%E>d;|AyIP4{rQwco-PK97NLL!DWW}b zzPjQE;Ook1V4IA~^f)U}iJICpnlkfrq1{wvS6fSTA`4lb0`3KtOaT#~XRt>-ML$Yo zQTjHKr5F9`;qIo)wBz$S^IUic0HHc{^L8b7~)CZ0XMWrnW~s_(*fsCNkH$IhgKQRIWVF&WX)%!b{N>pACsY`AJvPv z{4LzI($#uiYRO+@Vm}JxIhhliAa&7SwXJqE<$YXWTF+}->eR;lPuh15jiF3ckZ4Mb zRI|l%*WN{QU($}W^$v)C@klYD zSOFXyr&$N~Xi+f0rHnsSPW(M>h2f2>{C#SvH+3A+d*`F`(&NT|< zG0q&Udc!w2%@g!xY;YN$Bar{9h^^qA`(&W?=VAB*?djPWc2|o+?6<{_J~EtGNM>L^ zgaoa4Jp=BCh^gJ`pM*39n#)_di+(_Xqyiu1IF{7G9kd61z`e;pn*&x&2v`1@ohYg= zl~b#MBq?5vnKI($Ra;j2x_!YRe+3Med!@vx*mlMf6%d*rEgoAWI%{*9Ec5Be?zMX( zZ?|SW;GGOjW1iOV;(-(FN~^!9j_5UL6A?Q*Y@RQM@{ycE6x}qd!hwhU9jiwoS~f_ zhEXs;vc^mqU1iR>oj!DUJx$We*S3In($Jenw4C?%*7LgYBDFc)y>S?K6eOnp9b#kU z!$BVSDXf7s+u5#r_-DxJuQRk)k@@yG68?AHch$TuM|=1gDnLZ;I;PX~6`Gi*1ASPk zc6`bUIHA=IwLKL^5sd$p%rmHDRLkr79B`N~j@OJ>AIo19!|ABHczP19D1Q`wt4w0W zX#vkJ`KVB=i&?2xlUh|yym`9SH`DB7v~Tm)LI#8Y+#qvEI^GAv(4(bMQi|1cO`SO~}lxAFWpu;+#b_ObDdJpX`R7CCcCw|B?0+ZHcQ?|*oCA8@ZDb_Mys zd4jfvB_296_%2=xt)uH#ZAi=@&!H>J84)duy4^;M!Ltf^l(TSsK9eW=>wG_0Gs7o1r%Q^Avl2O-(fTHkpF z6n_CSZ6yE#mEE09s0}hy!(-FO0)P(I(2Mp6X!Kb#5QVD~#|BWKg`A$8Uc}4xgr|4# z@bIo6{?Kp;w;KB9bSB^kp=$Dkj>BE}WI+3zQzH+itSXFI-bqk7CAvae0qF;`RtcX? zg<~vpFJfk%XiDrv+)6Wu*QOO@%?w91d2PC38p@q4?T#VKp)Jzv-}Pwnpu^O`#PjQ$1J z-la3MD;IbzqlnHF?5H3{#Nj(MY6X+GPk3!wg~Fe{VCAa`=22sz%E_QVQZtneg3mUA zaDi6BXG$c(pGq*hv~Bpw!!pGKv$!naM8(7g5{oA-0X`mEEvElNinF8U`r8CubY^1d z^Pr;gM3hK>D!y;*W%k|sRgXV?b4hCbg}(k6&m;VC&+5rtd5_m|;(BrZkwXu$2&aN5 z!D}A?55cv}c+N8Go0CgMh7o@n6B0u)@Hm@_(w98yO%aLDZKS=W%B0vj<$U{C^P_uH zWC-8JmvY}Ppko^tID9r#Nq%i?iuaLDaT~TG^Zeq<>f`zq=Tr9Cdx|4P3;3ho%OA$l z8MWi58!L|NqkCJl4Za5U2YYg^_vGBH*hmvzJ_Fd@)$!KNKbD{y!8it3Wt#I>iVznt zx}xqVXjG9%quyGniZm$n$sC-tO+1c(9panE2fyn-f3}wzY`2R+2GTP%rGDoPMsI_( z=Of?u-Y_)F0Te3&3W}%puKD$#xqTk!$vIf^$^@kMwV+q-#omlern=KJ5foXVtKSTzrC9BL=g*_U<{S7itO{1jZIm zAT%`8e6u@9$oqn6Z-o49p?Ka;%Gf1dfTxPfg7jUI@>c@D)70-I74Q4le5##?TMEN4 zpiuDZw0(ld)$)_#iEr^tOxoUDF9CO=-^}kv6w+NY*CTvV!dzA;;hTnwj^U5#<5iHn zUux{wyB;@MrDgqdm-}KhE5V(XHsnS`C@^IcPoiM>;uZ>iO8rdX+1r4-Jb4ulzNT?E znmR@ijZ?Gkl#F55{mO^xdMJsg4Q zKVb015Xd(NH0&3vIX4j2Nvs+1^TJmk(tz*aTFkMC~ zLC6ND!Q}mj1=EHmCt90v1XID3*Ar2qjAEVvU_(Xj)Jm$mPw1c_yShNM96G`ld z^DI*l_Ohj2CVfOysQ~5BsD;W!wE~i+DY4d8-^y=cmDWmzXq#bMcbR6(XO6$z@~s&s z=^XRwbF~?gau#N6FRY^qL!VqEqlq0AyaMCK92KjwA*t{-|M-sYGEG*5RqQmb47TXTd^I3YUW(A8J#0Y*j z-1vm-P8pQI8BE*CeM>SMJe+NR58-nqe~x+*uxpso%rB{fvQ0l{CN^Z8%a_A~R7KLM zZRg{7`lCV{+J_qVVzH(g+$p)j{aUm-k@hoE+D`(kQO3??tyLqSB7Coxc+)~FbU1N zG;i|YTZo+)Ux_s-vomDI+EFJf{hqNOko^ADIrm;&sBDEr*Gxmu&*tlZ8DhrS~Zv&MWmwbiWgVLIQ#$TROE7dyRw|-kpNZ$YCpGahkkqGzeFFg2N zXSMKhwVljhUBiaXey_`e$Z{Zx-e=9&qA$tly_6eYTt97!p^i-5XL@W2*Eubui_p^s z5p~x`WvqvsT3yC=O&>-`5nsF8eR3vY94|6yDu)Rw%h1iY5!}KaLFaYjJD6c?0We-e zBMjM(K*Ylln%>+^56=*g_ae?yNQC=$#|6~vNNi0t(dwZ`mua)pmSK!@1}7+YysvyV zz6W#rxprZMeF)p@uwtg1ZgA9qMX(-kH2jlX1F0xPqT!w{^_CsS@9XWz@Y+t(dQB{c zoiz-3#rtZ0eH>#={;U^WDg0yXO(-<3eKY(K=( zXo3QC4;P8mbAH{N-k$C%Ch{;py=yYULrF-vEiBM4R-6q!5eo^(F2jtAI3j}Qq;xKf z)qQ%8R@(fLYtLwHWFjd@h^fEoM7(0TlVMb_imlnPz(Z+HAS$*(6MaQMzSOSQE`i}G zNF$>1yommZb^b}I0VY5MtlQg5Wta=UhL;l>;M_rIczE-6d0-KAE?KooV%dyw6!BPf(Lv=5djK7f zZj!3C=uvfI;-YXFmFFq#V06^-Qt{?Wde_#-LD2<&0Ie~6)Z=J5%(#uF(kGJ^^yqym zKU1)*^J~u{81Ntt?osM(VnI6e{8u3xT1XhiWIFz=6##Mm7RvqxZICqJc@M|ZI(T7| zKr%{=ir*xg&Y1jCujNh$H6N72`p+Y66ZYRzO1SJ?^B)@2%HYXQ2aV*#(P9sY#V?5B zV8l9c86Rv$=3F~TbW}pj*3v&9hy2MtNv|^y4xQ6e>cIgZoZs?d^|O^)pJM?7#r`7M zkgxTNH{ljKGb>8>nd{|6KW5WVxPY5&_+*;!le>hlU@C-9hVCGBWGYZsor;M$t`q^2s|MerY1pgz9(j7JqF=MEOOr z;Y){l>3;81=1dbs6Izr-7pieX74$UfQ9Rra=7>7VWr{&PTUupK`nP^r_cN1tbdSfD z;4qAr8~&m}|G;kPl8rbn^cH;qkN1oe^_7R?8N8>zetjKDTV~Qof#VVWh>4EQmyRTS zcmk=T1=P2~CP0H>Z}y>DskcEx1)w?xqw6h*nFdZk*Ye0$vnBhNI$i#LN*^~*PTqr& zijh|3a_}hM)w6|dzWT|eV3hR+l|V6i?GdT@pHjKJmJQAu;+jHI^7E`^KPjWgL69q( z5Sin@K_}wAhh^CEn%y6eMBZo^VahDDdpajb%G&g&Q!sH^FAUlpcUYhzDZIC^2q}zO z@el);NGhYxznI7v6cc&lA)15&Nd~%E;PPVr$tAq6FjgVY{I2*1)reqihE?{|tVv_1 z+jHkbqec~vDwirtyWNe!!_w$3IbYmL2|BE&ofl##4so?X!9|QorF`Tu03A`{PIE9s ziMxA9IO-v@=mSA*UEUDel5fRVjV@=}q<6Ep8*^6o+v{#T5}m!c1KO9{w)_fcr0ZP) z??K1&NXk*)`# zVk{ho`~V60e?hoK2FYI0KQi)NoawhBAr7-!YuvClP!YnD+v$~QNPQwmpjCJZW=JVG zzu1%PGVclAz64eyL%Zh~gg@zguM&0T;Dql^7OJu!IhFuPDeww1EtK)8u*~DR^uVXH zr*aJu&wA#@03Z}L(-OaF|GBtO6yQ8(mpl>%KiG8uNQFteEMXm5)0d@GNoD)6>{y1V zb*Eii0K~s-$`@xS{>iuv2Owz+`?*&O01BwmB|cg2fcKMpeuvSNtnsY40%H!0QgVo9 zi@onRAStB(9bTtT=9e9Y5zp{?!Dny^WSQ#Qo89S{d z0IgJu&$S?6iS5e-!z<{6sa$pb_udU@1tHPkgqPKyi~a2+zAG-EeIYEj)^IZ_z;Dlt ztWUTUxo&B}Uh(I94Ju>lenNPHkG3TY&26ls|l zl@1Zt)x!mVAHR5=Ns~ELYs5V5QX-N(qI^;MoIOh&pLCD^t!4;|mG(^#f@R5F{N>&Z z?M%RSf7B(z-Aaq|cD_VY9#xl8BApbN3*4WIQ$oT``xK9qSf@?#15KFHtkR(rnc|&# zUD+$1 zd-fDcBV^L!kL_U+MrsMtwAh2+W*cnsL2N#ar<9O%90Sfcx!!0Imb`%oJJDWouCtSh zLn#vz`CU^*<((-)lk2^RKQ)!{(PJ-7_*2NmCZK{yf61xuwpfH&R93^BLaGL)mbT4e z#M^>`zH2@2e@nZYROx|gDAQ5~b`X)n)!&#D5`Ir{gc1e_mdl!)7KoH^7) zdNCmg%z|FTjV|$ZffC)|_Yh4#+;YP{NT1fx@{bjQ-f*9^6VxN7!D0RJFP{~!daBBM z5!Dctz9iCYnp+zvsT{5ya9yVyv1b`}SUJrduXp2kM&|-xtq)MJqzb}82n9*TzC@w# z38Iaa#RGzF8F`^yk&-T%t> z?pkTgtoRMRCYST;d{1FLr&?42^_Ornicm#G&P{s>=K zq$ynuF=w%+7()1%0DQwoE@m47=`rp~=S*1%crihTn3-FbFJmlvKOR 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/h8H1cdTAwMWR0qrL284aN9729+/ZcdFx1MDAwN1x1MDAwNrG02VxmeD0x//1kyTaS0Fx1MDAwMrLFmPbAxHR0I6moUuXyfVWZWf/9Y2lpefjcc5f/vbTsPlXKrWa1X35cXP7Tfv/g9lx1MDAwN81uXHUwMDA3L4H370H3vl/x7mxcZoe9wb//9S//XHSn0m2/PuW23LbbXHUwMDE5XHUwMDBl8L7/x38vLf3X+1x1MDAxM680q/bZy8Y57Dw/XFzXXG6lm63rWsttNODMe9S76b0zfbcyLHfqLde/9ITfXHUwMDBiplx1MDAxZC6lJpRcdTAwMWJQIOTo6jNeLVxi4kiqqZLMXGIpXHUwMDA1gdHlx2Z12MBbuGGOJMZQwyhhyojRXHUwMDFkXHK3WW9cZu0tXHUwMDAyb9GMXHSuXHJlknM+uue1R/9eXCKjb1x1MDAwNsN+99Zd7ba6fdvt/6Ou/c/v9E25clvvd+87Vf+emvfx76k1W63j4bPXMr5lfKPLY+2fv/VcdTAwMWXGvk96XG5/sN7ouINBqK/dXrnSXHUwMDFj2lx1MDAxN0WJ/63tXW+r6k3Yf/w+9cttd8vOWOe+1Vx1MDAxYX3d7FRdO1x1MDAwZstXw9Cvdapvv1x1MDAxNrp74LpV74Vcblx0kjL/PfqiRSnR41/vdzuenFx1MDAwMVGMXHUwMDEzQYnxf39QQlx1MDAwMVx1MDAxYnrN1sqtgeu/aNuJtYDw+WO571XLr49QpYxWlFBcclKNrreandvxrre6lVv/V7xv//ozTp77va6QzeKPYbn93H/aboq7X5t3U8szyrCjXHUwMDE4MYJzqViiPFx1MDAwYq1cdTAwMDRII6iIXGI0UOJwbjTBh1x1MDAxNSdU6DiJXHUwMDA2x1DBOGgjlFxuXGZ9IdAjgX6cXqCpJGiCXHUwMDE4XHUwMDFholx1MDAxOInWPFwi5+9cdTAwMDJNJTVAgc2xON+d3pTkbu38+fjxeO30x+GAtqE2tThcdTAwMWKiXHUwMDFkTZRcdTAwMDLzKs5cIizOlDl4UVx1MDAxOM5cdFx1MDAwMEHZ11x1MDAxMXGmXHUwMDAwjqZCXHUwMDFhRjRcdTAwMTeKU1x1MDAxYVx1MDAxNWctUZpcdTAwMDHbodaQa5m3fS6Lqv7txflpY3p5NtpcdTAwMTBuqDIx4mxcdTAwMDLiOi7OIIxC4aNcdTAwMWZcdTAwMTLot1x1MDAwYr5cdTAwMDRcdTAwMDaksLr1dFN5Wi109tpnJXq4Uz68a5vReEKSWO73u4/Loyt//ZnWLu+drJUu9tdXKrtcdTAwMWJseHhr9lZcdTAwMGZcdTAwMGbj21x1MDAxZLpPw0Czb3+bpIU6XHUwMDFmLUzvZ7iPYVx1MDAwNWTEUVx1MDAxYYBTKVxmXHRaXHUwMDA1K1bCYUorjW5cdTAwMDS1XHUwMDA3b4jCI4X6K/GjcV6pojrGmVxikq+6XHL75c6gV+6jXFz87ip3lsGFIPrU+Js8ol3WwIlEXHUwMDE3gu7DoFx1MDAxN6FCfUTn8lx1MDAxNF9fXHUwMDFhrVx1MDAxNOL4i6XVn51mZ+j2a+VKYM5r3c7wuPnyiuhC366X282Wffky1Fix1azb17BcXHFta8vBdzFsXCLlXHUwMDE43dBuVqtBXHUwMDFmVMFGy82O29+axpl1+816s1NunfhcdTAwMDNYiul/+X7Y/eFcdTAwMGVeRzDs37vBl+RuvutcdTAwMDV1QKSo9EGZXGbW9eXOpTxfLz5fXHUwMDFmXFxcdTAwMGW2V1x1MDAxZad2rIiSlcNcdTAwMTVcdTAwMTNcdTAwMDIo/i+1b6rDQFx1MDAxMalcdTAwMGZcdTAwMTGKcP+NRoBcIpNEMJzwWOrzXHUwMDBlXHUwMDE0lWZCcl+5XHUwMDE2QHGk5s/Ta7lcdTAwMTTANVx1MDAxMTpcdTAwMGUnUqLE+Ncjz6qRMFmOO8/Mxz0s3Ynmw01p0NpbP+akqzZLMUz+1UGHpFlcdTAwMWHuXGJASSaaIezgYZjIqEZcdTAwMWZmXHUwMDE0gJJSXHRCorKMJN6qXHUwMDAxXHUwMDE4fFxcXCKLZ1FRXHUwMDA2plx1MDAxZKRVQiiNnJ+agG2bLMtaatfIL3Fb1fKg4f69XHUwMDAy/TK9QKNcdTAwMTFcdTAwMTFaK4g6KG9WXCJcdTAwMDAyXHUwMDAwXHUwMDE1XHUwMDA1XGL0a7NcdTAwMTBoTTNcYvTo6163OY5E/b8t+e/a+8fo7//5M/ZuLlx1MDAxZMlQmNF4Wsn1efznny5cdTAwMDA1XHUwMDBlRUhGXHUwMDE5YUC5MJOaS9SOmTRcdTAwMTfRM7+9XGJoXHUwMDFlXGbL/eFcbkpes1PHN+8j9Pdlw1e/Te7Of1xmLnuuu71XP9m5X19bXHUwMDFmnq5cdTAwMDVg+nK7W/WUpdu/aVx1MDAwZYNcdTAwMTdqzSe3emhnNjSxS+idXHQzdj1cdTAwMGZcdTAwMTiSSuIvOtlcdTAwMGY6RqNcdTAwMTFcdTAwMTbjXHUwMDFmXHUwMDFh/1BcdTAwMTJcImNcdTAwMTjximW3U508guur45dcdTAwMWQlXHUwMDBipdvqxd7dkOzp9lnxcyOwnTSMXHTKuUTYSKVSf4ZcdTAwMDcosftEKoJcZpyjXHUwMDBmT1x1MDAxOYM3XHUwMDBmRWuYXHUwMDFibjliXHUwMDE5cITBa2jqmmOQxG3ddFx1MDAxZj2dXG7hIa/vx259XHUwMDEyx3uzPFx1MDAxMOz/a5/GxjxBNYKqXHUwMDE1XHUwMDEy39dBZG0sRTX8l5hCMd+GxT4/rPSeTFx1MDAxY2eqxk4/znj13Vx1MDAxYVx1MDAxY/fcSrPcilhU7ErkYlxufrgvXHUwMDBmyo9rZ5f9XHUwMDBlb/bO13ZcdTAwMGau8P6pXHUwMDExMaJYh0lBJOfK4IBYmOratSaDKFxu2SxeQs5rokun/lpcdTAwMTPlaFx1MDAxZGhcdTAwMDBU+zBcdTAwMDK0Q6lWyvcx+UDhXHUwMDFiqMHNze9ccoXLXHUwMDA30yNcdTAwMDfFXGZcdTAwMDGUd/83g0umdPxbXHUwMDFmOFx1MDAxOFBcdTAwMDKn5kPAIUFRPfHbXFzbKdS7e9Qt1I6u3EpVN0QhjzWmo+Lx1UOrddXacMU+2dgtdfZXrqZaY0ptdqUn6V79cqO92qn1n1bbJ7/Ory+n625Ej2e6dpU+/pS1K9RAR1x1MDAxMlx0XFxgd1x1MDAwNKNhVoD8V9idXHUwMDEy6+KQOlx1MDAwNCTmXZ2tQbBcdTAwMWYgkiokXHUwMDBlMWtXIPLV4m+zdlU+nF6TcXKEZuhX4jiANMmkXHUwMDE2XGYxYDjMglx1MDAwMyiSQXp9YXxb+TneONxcbtiyr1uxmuBcdTAwMTPHV6zG+p3PSlU6dp3kl1x1MDAxNVx1MDAwNU9RhUK3ySlcdTAwMWJbqEK3XGZcblxiIYogV5CBvbKRV1x1MDAxNoh0iZZcdTAwMWPVXHUwMDFjXbuKW6dCY0CJolJIdPxIOfJep/pcdTAwMGU7QOWjXGbOWUlGXHRcdTAwMDdcYlxu7ptGM1x1MDAxNXHZ71x1MDAxYc3RXHUwMDE2U5wkf4Zy8s3pS0shMczkm2HlobHdbFx1MDAxZWzxl5PTx3Jzc/C0df553zyr7arS3p5unD5etEFcdTAwMGWO+Fx1MDAxMWlcXD33e/Po89Pfa4rPR+rjXGKJqoxcdTAwMDZDIFx1MDAwMVx1MDAwZvt8qlx1MDAxY4VcdTAwMDBdXHUwMDAzKrti1PhcdTAwMTDfh/Dc4dbpcyaIQJTv25OF059sITJsWGmpXHUwMDA1XHUwMDEwXHUwMDA1cUvZLFx1MDAxOb8rbXcluJrFdtXnfP6Pi6XS8eFSpYHud1x1MDAxZVxc/1x1MDAwNLc77vrju59cdTAwMGZcdTAwMDJwz55qvc06O+a7l8eX5uCktlH8kVx0XHUwMDAxKIluXHUwMDFjfbNgwMZ0XHUwMDFhlENcdTAwMDDhvaHUbjLFbELPXHUwMDAzXHUwMDAyqNUqpmJ+c/0+nl69mdSccFx1MDAxZavegieG6EkphcZZyFx1MDAxYlx1MDAwMNBcdTAwMTV1uK86xeLwR+vxviV27s9rjVx1MDAxYzzqzWDQ6pdZof1jyG/qLWJWSi/Dz1x1MDAwM4Dn/e1cbu3tt3bL7ZWD+52Dx63CeUKzmbq7/oOd9n+121x1MDAxN6o9XHUwMDFjlipcdTAwMDf7dyu7c0n6099rKlx1MDAwMJBcdTAwMGWzLp5cdEC+wHzt8XRAaIcobdCUgFx1MDAxMujk/Y0+XHUwMDFmXHUwMDAxMIchnyCIIOz+t45cdFx1MDAxOFsggCRcdTAwMGJxkiFIXGZsXHUwMDE4qlFcdTAwMTFbYIlANHTMj3m0XHUwMDBitERcdTAwMDUgwrwggJM5Q1x1MDAwMFx1MDAxM9zuOFx1MDAwMojvfk7RKpX2rSno2/XLwu456TUua7Xn8keiVSxcdTAwMDQggcjv90VcdTAwMDDNXHUwMDEx9nN0XHUwMDFk2saDxsSBToNcdTAwMDFcdTAwMTZxoJM1/CaDhlx1MDAxYuBGiyjft3aaJ2u4UZJ7QUV5o4DaRfual3a2iqq1Vzu77lV/XGZkZypv/fd6v/R+png/Sjl1UIS1kppcdTAwMTLJXHUwMDAzkbRv/Jda6ouqpIlmLMp/OXNQ9o3RXHUwMDFjXHIs0uRcdTAwMDX/zaJcdTAwMWKVXGaL3lxcMMNcdTAwMTTEXHUwMDAyZClcdTAwMTOVgyM5YYhs5s/7XHUwMDE1S6vz4PQmeJqYXHUwMDE4zbx9XePm/PHa7PRKtfvhk7o5XZVX3Ytsvk4hXHUwMDE1RXCE2qzHXHUwMDExLOJbg1x1MDAxN1xyXHUwMDAz0IIyXHUwMDE23bfKRHdtXHUwMDAyT5BSL9juSJur02sz4ERcdTAwMTh847FhbGhLk8EssSSEm9xd3Uuh9munXFzefNp7pCuVlave3vlqwor3l7q69H5OcnVcdTAwMTI05YZgh1x1MDAxNFx1MDAwZqe6eUxcdTAwMGaQwdm9W6ZETNAnXHUwMDA3R9jcXHUwMDA0IbnhlFx1MDAwN+5YuLqJyuFmcHVUXHUwMDFhZG3BXHL0XHUwMDAwXHUwMDBlVFx1MDAxMVx1MDAwN/iuXHUwMDFjTFBE9EzNJL3tU5lcdKXiXFy4ulx0jmbc1YV6nZOr2yj21l+u6NMludx+qOtOsdIoTaO9fp4q06ihvlx1MDAxM/P4nPVOQmj0dYpcdTAwMWFcdTAwMDJcdTAwMTBccrXiwlx1MDAwMYtTXHIwRZAqxLi4he4m6W4zg+6i4WSII2TcTi5Pic9W0jCNNOTLc1Mjyru3epqH8rbc2jBFdYfdXpLehjo7rqSh3uWjpMVCf3ejfK97PXq2s3lz8uNwcPWUKS5SU2RcdTAwMWRcXFxuXHUwMDBlJLBcXGdfXHUwMDBlaONIw7lcdTAwMDaG01wilIqJv1x1MDAwMPTTXHUwMDAyXHUwMDA0wk1OhM1cdTAwMTiP6qowjkJnTYBLyuxeTt6A9FtER/6aXm+5VNJcdTAwMTKIuKVcdTAwMTdk9Ynbq1xiZJFeasHyXHUwMDBmj9zYPN1erz5t9y/JVePlWP56gFx1MDAxY3ZK+Fx1MDAwNdTF2epq79I9JSdrXHUwMDA3zy/P9Vp8s1+7o5E+/lx1MDAxNFeJwPR17ZOCje/mYT7IiHJsglx1MDAxZcNcdTAwMWJcdTAwMTS3YVx1MDAwYlx1MDAxMVx1MDAwNWRvcYyow1x1MDAwNrFcdTAwMTgsNjQy6Nzt9DonKFVM0Vx1MDAwNJ1jiXGMgFx1MDAxNFx1MDAxZDWOzGJJ53M49/hwa1x1MDAxZXDuXHUwMDA0XHUwMDA3XHUwMDE2XHRiXGb2Olx1MDAxZlx1MDAxN5qeQDRNkSGKfo2gd+NMqfCiLCiB+ol+z1x1MDAxONRjQaNEdVx1MDAxZeJcdTAwMTe+hVx1MDAwM+1kXHUwMDAwvlx1MDAxMnDahGJxpFXqxFx1MDAxNVx1MDAxZCFcYlx1MDAwNfPBXHUwMDEyQ2mO7nCvWd+H++NVpuBcdTAwMDW2f1x1MDAxZLfbv3hcdTAwMGUhXHUwMDAxs4qMmFXI5dlFvchcdTAwMWae6XqtWNi/b9bldVx1MDAxMe7/QThcIn38KThCSOZozblhTKGRXGLbIGOvjVKkI1x1MDAxNsgsynh83Op0p7c6klx1MDAxOC5s2FSc0ZHJy8goXlx1MDAxMslcdTAwMTWbXHUwMDA121aQQXDjIMSSdef9butnp+/Wm1x1MDAwM4RcdTAwMDKDeUBcdTAwMTVcdTAwMTN8elxmqnhcdTAwMWbIUsw48sFcdTAwMTnpxnjWJVx1MDAxMJA9SIVcdTAwMWNfXHUwMDBirnjMxi9aXHUwMDAxaXeLNFx1MDAwMSNcdTAwMTDrZomP+EdVQLghJMtOsLSp5ELE8Vx1MDAwNkheXHUwMDFlp1RcdTAwMTjgdEbpTyYov19SXHUwMDAyIVEgP/90IVwiyX5zXHUwMDExb/6PqTGQXHUwMDFlLvahXHUwMDExRGpcZvxcdTAwMTlcdTAwMWVeuMJA2lxi/qZcblx1MDAwM2FcdTAwMDVcdTAwMWRPT1x1MDAxZv/N8Wspln3/8mFtX93frJpB8aBeLojn1pqJWvaJXGZcdTAwMTLtO1qKaPybl1x1MDAwNEdBXHUwMDEyKVx1MDAxOYnZMMmWm1x1MDAwZVx1MDAxYWFcdTAwMWWhi1XYONvOp7ftRmgk5Do2xlx1MDAxNbuSuH2CRJ5cdTAwMTG0TrmHXHUwMDA1zIpFvlx1MDAxNFx1MDAwZlulW1a7u9u/bT+QXHUwMDAzXtzY2P88K/tEknpqu59IhEtt97eJr0+frzRcdTAwMTbJuWNQMqkhVFx1MDAxYaXDVTKUQJipmOZMXHUwMDE5XHUwMDAzXHUwMDEwXHUwMDAzMynxSoUgXaHAXHUwMDA0XHUwMDBiLnctqORk2yOy4Epcbmh6XGKLw5WGJlfIIHYhkbM5rFxi6aWnL5U71Z+dWqv7+M7H5oFNTvDvsYn2diRL8Vx1MDAwM8mHTqabzzQ6yTWqKaIyYZeKJKVcdTAwMTCOrjLGwW+1QCShXHUwMDE0XHUwMDA0QrlcdTAwMDOxVYaB4lx1MDAxNuDZqkExe07EQSlcdTAwMDNEM4pcdMlcdTAwMDSC9YXWJ2i9zKD1OCH4SmVsJFwiqJRIRFx1MDAwZcBQ9WdcdTAwMTJY/CGtz7egXpJAek9HRNFvLuKXp+SD6ZU3PspcdTAwMDc5Uj5mw+nAmLGigJZq2T1iXHUwMDAwjuPkRIrIIDJcdTAwMTLCdJv2oSFcdTAwMTTI2Fx1MDAxOFx1MDAwNKFcdTAwMWZcdTAwMWZFhFx1MDAxNMZcdTAwMTG/XGbUMCx/+XLD0bV298Hda76+ncF5c9h4U6QpUFv6JkW6PffOOeGKKsNcdTAwMTWTMmzPXHUwMDE5NfjiLcFcdTAwMDQ0XHUwMDA0jETzpzhz8GnCkFx1MDAxOVx1MDAxYYnTomP2IIkjwFJQramWXHUwMDA2mM5SXCL1n2XRi1x1MDAxOSw6cnWlNfC4TFx1MDAxMZqyKVx1MDAwMIpSbmZcdTAwMTNXoD+UKpKvRU+USPspRIXx8zY9PVx1MDAxMOFDXHUwMDA2kThm7Fx1MDAxMzaHglx1MDAxMPpZOz6DpUmSqZeztdOfMKjpyyGp+y12zY0qxDZcbqRcbsa2e/up0kyK6WCOQrFVaJNfXHUwMDE3oWNcdTAwMTAyV462K6eCI9RG6rSoOJ1oT1cy2FOcNqS3sYl3QJKrzTHEj0ZcdTAwMGI6XHUwMDBiWjxcdTAwMDfmNHR3XHUwMDAxuFx1MDAwMyh5mttQXCJcdTAwMDVcdTAwMDBi0vOFZImeUYtcdTAwMTH1+LyBn4WlTNtcdTAwMDKxcHisynKUenw9avfGgC9ZKWortEk/JMB+kJaAsTtcdTAwMGZInFx1MDAwNFx1MDAxMqjvvI2z/djZfJG7W1x1MDAwN72Ty5eSXFw3m/x5Olx1MDAwNE5cdJNcdTAwMGVRXvo157ZKc8hjUPRcdTAwMThGS+RENl3NXHUwMDA0XHUwMDE3yt5dhiBcdTAwMGVFZ8KVRoxcdTAwMWVKXGZcciBwXCKNd9iD5lx1MDAwNEGjWUDwRJexkXCgVUCv3qZcdTAwMTOiYvxW5lAghZWxeW1cXKfkxtiUJ1x1MDAwMfpDVY5mssCaqydJlFPv6aiETm+2x1x1MDAxNTfhSn5GJjeguXVx+HJ5Vjhw60ebp1x1MDAxN9u6eqzPnqe0XHUwMDFiSN3tijzRRkJwXHUwMDA17zVwz+FcdTAwMWPdXG4om2KJnnWi2YhLlbOVz1x1MDAxNTf2hD2cXHUwMDE1rSDnXHUwMDAzXHUwMDFkv5PVSChxmMFq2FO68IO8Po7Qq+TzS5HOS4pOQs9PRmyuhqOQKKre1aiU/lx1MDAxMyxHei3YNMthXGKEXHUwMDBlz1xmXHUwMDA3jdisIMWVrZJcdTAwMDaKXHUwMDEzo6OZe4w5KFx1MDAxN1x1MDAwNGxNXpQ9XHUwMDFlXHUwMDEzMkIspFGIbilcYiDYXHUwMDFjyblo0jcyXHUwMDFkm1x1MDAxOeKAQTGrXHUwMDAzcXHAMcdq+shC2JM3P1Y4af73cFxuiVx1MDAwMuldjZHF6VxyRFx1MDAxMptKP/LvY2wqjIfyWOGbwfFANFMv59BwptfQTDOcXHUwMDFhuZhccn+z5eRcYoWxaDu8YuuOXGK7X8pcclx1MDAxOFx1MDAxMa2ihXKqXHUwMDE46lwiNSijwCiLXHRyIY7dbuXcxnba80jEYvs7yXBuT284XHUwMDExXHUwMDFmW1x1MDAwM1x1MDAxMLv5nVxmrHCmOMOpmMne99dHUieLo/d4RFx1MDAxMD9vNWdcdTAwMTBKnM1cdTAwMWX9Vlx1MDAxMC89XHUwMDFhMc1SKVwiU1x1MDAwZcdAiFx1MDAwN4JcYips4mgoXHUwMDAzeWSojCM5s2XupeE26DTeUKG5XHUwMDAzyoRWqFqC6Vx1MDAwNTtMslQ7XHUwMDE5LFx1MDAxNTFEXGJcdTAwMTmbX1xuJFx1MDAwMvz84pj4kFx1MDAxNCC/adZHIVkkvcsx0vh5czWL01x1MDAxNfNcdTAwMDd5M1nY14iW0V96tetsenm421RrZZNMmEBSgaz890KA6dHYqbu7UjpcYu28SrTaXHUwMDA0z7F5RYDUwUtWXGKptPWpokdcdTAwMGWgXHUwMDE0a4Z+lYNGXHUwMDE1x1x1MDAxN5xcdTAwMTAuY5SR9qBTZY/IXtjVJLu6m2F7V9pVZUhcYpdJrnxDXHUwMDEx61x1MDAxM2aPt5yBYZ2D/d1kibSfQlRcdTAwMTg/b1ZnZLBcYpNApFeYzOixcJnwVVx1MDAxM1x1MDAxOcNcdTAwMWOkxP3uvPqsdCbg4Gylen3SfKhXXHUwMDFlz39cdTAwMWScvUStanJ5W+lwavNdgVxigLHAXHUwMDE5zsBcdTAwMTGM2NPDXGLYglx1MDAwM1x1MDAxMctcbsi+KVx1MDAxMlx1MDAxNkWEttluMYaVKsfYwDBAeGWDXHUwMDFhM1x1MDAxY7VcdTAwMWFrM79rLfdcdTAwMWJyOb1d5UZcImJlJra6XHUwMDExXHJcdTAwMTSwjYQhXCLx1kLCl6eTpMj0odxs883DXfLyfFC63ylcdTAwMWT1e49TXHUwMDE156KUolx1MDAwZSvQiuAwXHUwMDExXHUwMDE0hFx1MDAxOVx1MDAxOFx1MDAxYVtHeWdcdTAwMTLYXHUwMDE1XHUwMDA2Q2Kq49mzlW3YXHUwMDFmol1cckGkMVx1MDAxMme1KM6VJL/1LGG00mLZuFx1MDAxNXVtXHUwMDEy6Vx1MDAxNlBg6DbFXHUwMDFjXHUwMDE25yqWVpcq9p6larddbnZ+dkrF8W+2bFJTp9xcbn2dR7rUjKpf5jCmfDKnXGZpbm1dXHUwMDBmfvXPa/fd44vtl6Z4gGlcZoLN1LZ1OIywXHUwMDAxXHUwMDBlJlx1MDAxY2Y/KmzLiJdcdTAwMWbJosRBwGulXHUwMDFkrpGSKS5cdTAwMTfF+rLYg0ZcdTAwMDZ7YJhBXHUwMDA0XHUwMDExXHUwMDFmXHUwMDA3ynXiWrGHkdlcdTAwMDfPXHUwMDBmn6lBWD/cKM6xboe7l4+a7lZa5cpg82b7onL1eFcv6MrBVUy9nFx1MDAxOL/tR9VQXHUwMDAzXFyIXHUwMDA0RVx1MDAwNcmNlzBcdTAwMTN13Fx1MDAwNFx1MDAxY7Cng1x1MDAxYm+H3cR57oWmJmpqllx1MDAxYdRcdTAwMTZcdTAwMWFcdTAwMTlccrFcdTAwMDVyZDSKxkeejFx1MDAwYjs1c6epJ3ZcdTAwMWUrbvMhmHE8d1xuXHUwMDFi28ucXHUwMDBlXHUwMDA0vT0o7Jw97YrCY6l+L3dcdTAwMTivXHUwMDE0N6J6m8AhXHLRjrEn+FwiIbF+NKS6o/NAcaao3fGKOVx1MDAwZnRUXHUwMDBihWguXHUwMDE0j8u9mPVZYN/hhJRcdTAwMWKa4bxfJrVdkolcdTAwMWX75XVFJfJHe667srvxXHUwMDFm0eK3XHUwMDBisbU6NrZ3bju1lcu14+vm1vUjOT+UVb9wbEhcdTAwMGW/9ISU9H6m+DhcdTAwMTRvXHUwMDA3XHRcdTAwMGZw1Fx1MDAxM2NcdTAwMGL/hPWEXHUwMDBix1x1MDAwNjhcdTAwMThbO5CK4DFcdTAwMWLveqJcdTAwMTZVXHUwMDFmP6FcdTAwMWLn0+uGXHUwMDAxZcNcdTAwMWVlZHF6Ob1MXHUwMDEwTlx1MDAxYlx1MDAxMEQgM8lJ+vRcdTAwMTEpPztNS9Vq5Yqbh5f79GmY6T4n5rSUpZj+5+P/XHUwMDFlaiedo6fHw1bhrrzz3KtcXJfPiyeZ/Fx1MDAxZqNSXG5qfZfSY9hcdTAwMTXVnlx1MDAwMOFGSiOIXHKjjTvm3lx1MDAxZXIrUepcdTAwMTRIyoMxxz54lcRRjNlcdTAwMTRcdTAwMDdGSDBbYaHpYU2/yIBl0Vx1MDAxNiOxMFx1MDAxMMkztC6SJFx1MDAxN3tEsmqPdfzYYUhpbnBzbadQ7+5Rt1A7unIrVd1cdTAwMTBcdTAwMDVcdTAwMTPvXHUwMDA2M5XCOrr/Je5uh8f99v7m1uauW6qyJzmH7jW9n2nuXHUwMDE1jENcdTAwMTQh9uBcdTAwMDV7SlVcdTAwMTiGauIwbrNcXEBcdTAwMWGjILrwq6mjJ1x1MDAxYzPNXHUwMDE3zjVR5TIgT1x1MDAxMIYjXHUwMDAy4rHeNaqIvsrZwzKk/tCGcKhcdTAwMWazZZA/O+1u9b5cdTAwMTXvY1x1MDAwM5nnf4ePneDXUlx1MDAxOObS+CDycbTp1i21Yoo9tkghtqLEXHUwMDFlXHUwMDBmKGDs1Fx1MDAwNiC2XCIn2mUuJFM05iR5wbVcclx1MDAxM7FFKFx1MDAwNFx1MDAwZorRSMGZxJ/QNqLLxt4gzcy5ojKyTPvJVdm/ILufZtimtCVS7SuNi1x1MDAwMFx1MDAxNjKygjTa5/GssJjJWYOBPae5SO6PSt2kx5MkOf/WqHSEMdowZZNkkd5Eyz/NQyWu8YR4qUKBczYjXlx1MDAwMlVeqI1cdTAwMTEmXHUwMDFhS5MxXHUwMDBlJd2ufmZcYoZQLkBcdTAwMDD4SYXvI2A298FcIlx1MDAxNMGViqbS/O1J/Ukw9s1OhNKMX/s0NuQxPUiV3ZD0vo5cItJamiBPajxcdTAwMDXuRupcdTAwMTWEXHLDWMGCSfh2cEsutsrlo9OXe1edNjZpoSdcdTAwMDdRXHUwMDBmmFx1MDAxNq5cdTAwMDPcXHUwMDAyITBcXGkxXHUwMDE23yCUXHUwMDAzzNZTQSaqKYkyzTlcdNf5XHUwMDFly61XXHUwMDE5QC+1oas0elCRXHUwMDA3emky0SRcbmVZqC+Pd0hcdTAwMTHpXHUwMDE3/VLq9s5cdTAwMWFcdTAwMGZ8d4dcZpvq7rh2dPdcdTAwMDGRXHUwMDE2ilj9XHUwMDFji0BDYmYrRVx1MDAxYeWleFGIXHUwMDAyuzmR6W9RTZ2WM8i04XZeoqfOvrK15Fx1MDAxMDRCXGYxTMCXXHUwMDE33HhcdTAwMTXqP96s/XK51ztcdTAwMWVikyPPv/zQdFx1MDAxZleSgfxcdTAwMWZvKmHn2vXKXHUwMDBl/PXHX/9cdTAwMDPhIXiWIn0=ADCinterfaceSGPIORX DSP chainTX DSP chainADCDACMCUSPISPI controlregistersSGPIO andflow controlADC clock domainDAC clock domainInternal clock domainFPGATransceiverDACinterfaceTransceivermodule \ No newline at end of file diff --git a/docs/images/gateware-ext-prec-rx-dsp-chain.png b/docs/images/gateware-ext-prec-rx-dsp-chain.png new file mode 100644 index 0000000000000000000000000000000000000000..6dc4833047e0d36ad14078439d60bc4cde95db25 GIT binary patch literal 70790 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 IWBcI$1SF;TBme*a literal 0 HcmV?d00001 diff --git a/docs/images/gateware-ext-prec-rx-dsp-chain.svg b/docs/images/gateware-ext-prec-rx-dsp-chain.svg new file mode 100644 index 00000000..af05bf82 --- /dev/null +++ b/docs/images/gateware-ext-prec-rx-dsp-chain.svg @@ -0,0 +1,4 @@ + + +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1cXGtX2tpcdTAwMTb93l/h8H6tOfv96DdcdTAwMTRbrbVS0YLee8ZcdTAwMTlcdTAwMDFcdTAwMDJEIGBcYr7O6H+/K1x1MDAxMUnIi6Dx0XOkY3RAnjt7rznnmnuv+PeHjY1N73ZibX7a2LRu2ubQ7rjm9eZHf/uV5U7tsVx1MDAwM7tI8Hs6nrnt4Mi+502mn/74IzzDaI9H92dZQ2tkOd5cdTAwMTSO+y/83tj4O/hcdTAwMWb22Fx1MDAxZP/cXHUwMDAxXHUwMDFi1UjnR52cWiNcdTAwMTfzztGX47NqcGpw0ENjXFyr7ZlOb2iFu25gO1XIUIJhJTinVCC92HtcdTAwMGJ7XHUwMDA1XHUwMDE2hmSUK0RcdTAwMTknSMvF3mu74/X9I6Q2JMaUI6mJxpoujuhbdq/vwSGYcINIjCjVcFx1MDAxNVxuX1x1MDAxNsfcN+jTXHUwMDA2WmyZeu54YO2Mh2PXb/V/sOX/XHUwMDBi29wy24OeO545nfBcdTAwMTiTd1S3XHUwMDFiXHUwMDFl07WHw7p3XHUwMDFiXFxcdTAwMTk6XHUwMDE5OnQzdv3GvPUktj3rLLhhr+9Y0+lSW8dcdTAwMTOzbXt+P2FcdTAwMTRu9Vs32e9cdTAwMDTj9WfYJtdcdTAwMWNZ+/6AObPhcLHZdjqWP1xmmy28fDunM7/d0uFTy/IvgSXCkispwvaHscVcdTAwMTCOb/0+doI4w0RKIbnkLGzAtFxuXHUwMDAx5lx1MDAwNZftmsOpXHUwMDE19rTfiN1I8IVcdTAwMGYzm3RMb95cdTAwMTKFkIBBZUiEXHUwMDE3XHUwMDFk2s4g3vThuD1cYu9cdTAwMTJs/fUxLZ57s9aoWzl1XHUwMDFiqGI3XHUwMDFid1edm+Z1L1x1MDAxOc+edeNlhTLTXHUwMDFjIyaWQlx1MDAxOWLYkJxzLVx1MDAxNYFwXHUwMDE2XCJcdTAwMTHKmDCDUfhAwCOuuGbJWCa83Nj1XFzTmU5MXHUwMDE3+vh3j9/RXHUwMDFh8UuEkppTiVLiVyiaXHUwMDE1v5Ip4Fx1MDAxOVx1MDAxZVx0+1dcbt8wXHUwMDFh/SiExz9ublTrtY1237SdyECOXHUwMDFkr27f+U0naGnrZ3NkXHUwMDBmXHUwMDAzhl26VGVo9/w+2Fx1MDAxY1rdSEBAN3g2KMNitzeehHvbcD24reUmh2fs2j3bMYcnuc00Z9742JreN9RzZ1a0J6y9XHUwMDA1j1x1MDAxYoTn4PZscHg7Q9VcdTAwMTG9uFx1MDAxZTc+XHUwMDFm1Vpm87BZWIc0Jlx1MDAwNlwimkqCKVxmXHUwMDA2XHUwMDBmh9jvJSmlIbVcdTAwMTZUXHUwMDEzXHUwMDA0YYPDvVx1MDAwZuBV2Fx1MDAxMIJcYo65XHUwMDEyRFx1MDAwYlx1MDAxZPbrXHUwMDAyu1x1MDAxNFx1MDAxOVxiXHUwMDAxNSDKXHUwMDE5QJxHWHA1lpVQllx1MDAxNvk61FxyPr87jq/Tcfww2uFQ0vmWX3F4c4A2YErJaFx1MDAwMD+gm2SiXHUwMDFiXHUwMDEzXCKVxlH8r1x1MDAwMe9F88KGZqlFJGhcdTAwMWLt4/rB972qvVe56Fx1MDAxZrpH9i69+2tzcdyvj+mXvT+ZnDZPyOi0zr7efLeOr26mXYYry3d5uL/puuPryHXn37I4iVx0TVx1MDAxNWZcdTAwMTF+fJKkpj9lXHUwMDAxSdVEXHUwMDFhWjNEIcOD9FxysWVUKmIoRDBmVFx1MDAxMa50XHUwMDAylFxmckNfUKmfqzDBVJqiondcdTAwMTSmoPCmuJpcbn9kqMA6XHUwMDA1bkwlti7ghlx1MDAxMFChZlx1MDAxYT9cdTAwMDZuZUZuQk13XHUwMDFks1x1MDAxNVx1MDAxNYiIjmKxtHWVjrahvZabo6Qju9OJ3mpZTFeJWlxcX+Ncci9HWe2rs7Z73WPfKLOP7trbtdu2OCmsrEoxcGhaXHRcZiktwTrm8LgwYKNQioA3k1wiXGaGRVpMOXBA5Fx1MDAxM9L6XHUwMDAyxFx1MDAwMHRcdTAwMTY9RIXDUY7F+2eA+naNXHUwMDE0WSsmMWRDaahcdTAwMDanknB+XHUwMDBmsOaQ43Ck9Fx1MDAwYopos950K4NWt1t1rratXHUwMDFmg8Fs0Jq8gIjmXrd+cHgzkzY7bc+8Kq3v3V5cdTAwMWXJy1x1MDAxMq77rXV8e3nuNr80rquN8eR0cnP4xStL9Fx1MDAxOVwiiNEoSTxe9NNHpYjoY5xHXHUwMDE4Qlx1MDAxOdDSXHUwMDFjwlBhbP6TfHP4oOWSwl1xUoBUXHUwMDBiLDBcdTAwMDbrnEZcbogkNi+0nvpcdTAwMTTPXHUwMDExXHS5u1x1MDAxY61nXGJcdTAwMTH2JOdcXN3ZaPmHlOGan6r2K4Q2rvbJppej907vroKtXHUwMDFkfjGhSDly9HnQ/bZXWO+Fkr6iz+FLYjn7g95rVKbeh4e8y/1cdTAwMDLZXHUwMDA0rYFs5HtmjlGaZ4ZdmcDmgGtcdEP5cmo/aX5v91x1MDAxYs27Rnt/n8krp7ZdZWdF1bN71G25jVx1MDAwYqfDL6fyYLfROoFcdTAwMTF6XbVfrcpUc0jEylHl9N4roMqSXCJcdTAwMDODXHUwMDEx19yfsNRcdTAwMTIvw3quytmw5sggK734u0pnYVx1MDAxOa+RumPEgXlcdTAwMTlPrMNsXHUwMDA2M5mZWNbUXHUwMDFmXypcdTAwMWWVuj+rIT+0b6K6+npcbr1CXHUwMDFh41xuXHUwMDFka3c58jz7flBxZ5XmUbd/dT447rZofeBcdTAwMTSWZ0ZQJLsmkcXQYnacIG6g6CdMz7Pt+Ls8p0CarDHFxqnyeTfVjDOWWMZ6gDSBMSScs/C8Z5fnqz103t/+2mh1f17QqkNqfFTfem15lmdnPycnP05ml1x1MDAwN9Ci3Z81+vlgtzzTrJSQUSg/Xp7Te6+APDOq82C90jRjkZJmv+txJnjpOqvNjFx1MDAxMK0pTdNjTJKL0OGCXHUwMDE0435dXHUwMDBiKts1P12Qq1bbXHUwMDFlmVx1MDAxZTR2Y+qZPWv6XHUwMDE2xHmFMCbsc/YzlCPU+VOLMcZZQrPmxFBcXHJNsUIkOt9yv1x1MDAxYY2XVqOTaIZUXHUwMDFkXHUwMDEzJVx1MDAxMCQpWlxiIWlEglx1MDAxN+hcdTAwMTbIgOCCfF5z0OiIRrzZdbCOOe1bL6zSbFx1MDAxZKBcdTAwMDOsqNJpOGfJrYtcdTAwMTlzzZRmUXEvcW6MRyO3KMonYzueXHUwMDAzhN82wq5cdTAwMGV+LL7/+TH16K2cYFxm9sfjMLxgQntcdTAwMDGnrrdccqNjO73NpZqAeZVikVUzOHo07lx1MDAwNFx1MDAwMTV2W3Y0v4AgvbE6Nf/xl55cdTAwMWVcdTAwMWXT4CgsbIs9+PxbWIhgOZ3Vrcyf7SuplXg5WYeQyGl20L1cdTAwMTWfjvqWmVx1MDAwMFx1MDAwNTxUdFx1MDAxZqi5XHUwMDFk81x1MDAxN9awNb5OiaegvXWr91x1MDAxMLXL4PFvuj+tT4COzWHKTTP3jcZX1qF931x1MDAxMdOG7fXnwCiQZ+1/nX3bObn2zveG28dfL88vqtK9TFJzhoeSSFx1MDAxOJhLTSSFeCZ6udLPL1x1MDAxNlx1MDAwMlx1MDAwMHKNKGVcZstcdTAwMTCCXHUwMDBm9KyZoZHiXHUwMDE4ICE5XHUwMDExJLVY6O3T8St4Jv7k4iDFKVFKpk50YsQyZ0eIX/cnMX1BK3Xa3MV1tFO7MpuXuq/GX4Zd77So5dlcdTAwMTlhd3+G735sfz2vkcb0wu10XHUwMDBm3mJxUPpTXHUwMDE2sDwwXHUwMDE4hoJcZlx1MDAxOVx1MDAwYoxcdTAwMTRcZkysZC8oXHUwMDBlykOh1Fx1MDAwNvcnJFx0mFwiiFx1MDAwN5RSOv5eXHUwMDFjlIpCUTwnooQz8KXpcCNUZcFccmNIXHUwMDE4YGRKXzF8uvep9c2pXHUwMDA1lsGKrGa/XoXQXG4li5uetMaX43byXHQnz+1ITlxySUBcdTAwMGKl0r5PTrqdfCBDjoM51YJpKZAmNGKI4maHaUjPpcJ8nZmNf5HbkcWRzVx1MDAwNEHCdzwpyFY5NfSKXHUwMDBiTVx1MDAwNH47VX+lup3sWPQ/W4kwTGTja5udfFx1MDAwMnhps5NM7f9cdTAwMTV+Yq/RPN5pnHi1o2mjentTV2fbzXHxNVx1MDAxOaZcciRcdTAwMTBTWEmCo4XroZ/QnFx1MDAwNS9cdTAwMGZA8pokQMZccq6IYEiAq1x1MDAxMJHpolx1MDAwNf/x90wmje/Uk/1cdTAwMDRcdTAwMTZcblx1MDAxMc2oTJv10dlLM5hcYimYovxcdTAwMDVLJyr7jf3rn6je2Fx1MDAxN5O9mTPtXHUwMDFkkT1eeFxypfZcdTAwMDVXd2rNu8pgq+38PK//dXzlvEVDkf6UXHUwMDA1XGZcdTAwMDVcdTAwMTNcdTAwMDKA5s+4alx0Y0NTXzfIxSFcdTAwMDZ6XHUwMDBmXHUwMDE2PJlkmKTlIexcdTAwMWSHaTjUa8yySjB8XHUwMDFhYiZ1OVx1MDAwNdHsXHUwMDE3XHUwMDBlqNBAkoi/ndTjwVJ8Ntve2J1++p9cdTAwMDPuXHUwMDAxPNPHXHLBPlx1MDAwMkWot+AwVmhb3GFcdTAwMTR4lnJcZkc+IeVcdTAwMTlcdTAwMGVOsKFcdTAwMTnnXGa0UlxuLmI4XHUwMDBmXGZHiPPkm7rI8F82p0RcdTAwMTDwI5gqnlJx7Gd6imnIXHUwMDA2NXhcdTAwMWKy1kvn/1wiw1FZXHUwMDAz+FpcYlxuakvTqo+5znRcdTAwMWMwxkpcdTAwMTCFy15feSOOIztcdTAwMTj9z1ZcIlx1MDAwZVx1MDAxM5n82o4jn1x1MDAxMN5cdTAwMWTHiziO/FKZPPZcdTAwMTNcbudcdTAwMTVoK2RoycBLICA/zpKvOoNZSTFcdTAwMTjofqFcdIPrlVxc+G/RXHUwMDAyWOU6rzhcdTAwMTer/Grrtv7ds53tNUiPXHUwMDAyov0107Rkh2TnOkQyf4qBPWb6dKlcdTAwMWQp1ch+Pcur095WNFxmg1xyXHUwMDE5XHUwMDEx+HS+yy/6fFx1MDAxY98tPWtAfkX5LquV+dUvj2RlXHUwMDFk+6zV7JLJOIdcZvPL7/PIUEmV97LZnFxmqWJ+NqiSlTaxUlfOVlIj4lxmnD9cdTAwMDTnOzemceNOcW6UfsVcYqMsjVx1MDAxYiPKk1hZ4lx1MDAwNJJ6Rlx1MDAxZjPxkk+Nb6XiJjsog/MzwvHpRPlcdTAwMWNcdTAwMTUt5Vx1MDAxM+Uz0HmyyuY1ibKM9DL/NeM8RsWIiGgpclx1MDAwNqFmXHUwMDE1XCJLf1Ffcq7nPZnGp+/EmUKcu8WJU1x1MDAwMN6ha5OvXHUwMDAy+YKY89fbuO9cbtSzXHUwMDEwp4j+yaXXXCLOzNiLnf2G+PGJzLOGk07jnWVmWmatwslb/stcdTAwMTl5VMMwy3vpYTXXMFx1MDAwM8KOMTzvupRXXHUwMDEy37kmjWs+r2FgfTj5r0CkOlic/SdcdTAwMDNcdTAwMDQlkj+HgWWYM1x1MDAxYy7evFqWllx1MDAxOX2x01x1MDAxZss2z2FcYp+LXFzKcIZcdTAwMWbmfbRpTiZ1XHUwMDBmxnzRN5tXtnW9nT2J/mHealx1MDAxZo1WsP7768Ov/1x1MDAwM1x1MDAxNahInyJ9RX DSP chainEnableDC blockMixerDecimation stagesPhase stepFactors:16, 32, 64, 128 \ No newline at end of file diff --git a/docs/images/gateware-ext-prec-tx-dsp-chain.png b/docs/images/gateware-ext-prec-tx-dsp-chain.png new file mode 100644 index 0000000000000000000000000000000000000000..d0a971902e8a42668aa4f1a0fa997d104a26b6e3 GIT binary patch literal 51198 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/yxjrxXGaLwVAwSyolW42tIEtGkiFL8e9zWzZabNlcdTAwMTiGQCZcdTAwMTNRRcm93r59zrm3W1/XXHUwMDFhjWb6eWSab1x1MDAxYk3zqedcdTAwMDa+XHUwMDE3u7fNdVt+Y+LEj0KoXCLZ7yRcdTAwMWHHvazlIE1Hyds3b4pcdTAwMWVOL1x1MDAxYU56mcBcZk2YJtDuL/jdaHzN/kON79m+p8zEvJNcdTAwMWOP37VGZ6fB2a3Z+bibdc1cdTAwMWHdXHUwMDFiXHUwMDEzm17qhv3AXHUwMDE0VZ+gnFHqaMFcdTAwMTjHSHHKXHUwMDE5y2s/Q60m2sFcdTAwMTRrLrDgnGic1976XjqAXHUwMDE2QmpHYkw5ktBcdTAwMThrmrdcdTAwMThcdTAwMTi/P0ihXHQm3CFcdTAwMTIjSjVlnMJL3mZi0NtcdTAwMDbKS5I0jq7Mdlx1MDAxNESxtfpcdTAwMTds7F9hc9ftXfXjaFx1MDAxY3pFm8vLnu7pos2lXHUwMDFmXHUwMDA0nfRzNjI4XHUwMDE5XHUwMDFj2pxcdTAwMTn/fGo9mSlf1Fx1MDAwYibsXHUwMDBmQpMkXHUwMDE1W6OR2/NT6yeMilJr3ajlZfv1vrApdoemZTcsXHUwMDFjXHUwMDA3QV7sh56x29Ds4s3KdKE3na7SPDHGXHUwMDBlgaVcdTAwMTaKXHStZV5TYItcdTAwMTEyW3pcdTAwMTiFXHUwMDE5zjChkjKCS/38ZFx1MDAwN1x1MDAwMJZmw166QWJcbk9bI3ZL4CtcdTAwMTYzXHUwMDFleW46tURqhYTmXHUwMDFjoWLQwFx1MDAwZq9mTVx1MDAwZqLeVTFLVnq3Xofnrcs+v1x0qdy49kLe0ofph/Pj7sp4tnhcdTAwMDSEcSVcdTAwMDRRiGNRxbOQXHUwMDBl4JAjzlx1MDAxNEWU6Tk8XHUwMDEzxFx1MDAxZFR+9DygmXaYLj3q2fFsn/86nvdcdTAwMWWBZ4awJKRcdTAwMWXPXHUwMDE49nEhoCVTXHUwMDE20SXZeVx1MDAwNKCnXHUwMDE1XHUwMDA1XHUwMDAyK1x1MDAwMEvNpzRfQlx0n2r3ZGe/e8A628Ptsf6yd9GSO2Ezb3e3Xj/spPPg3UmqWu3u79t70YfBvt44P9w/r85yP79cdTAwMWLH0e2q435s6+OtUXRxe33UPVx1MDAxZW7GbX1w/G61cadvL8Tueu/Nsbvi/Fx0sTV2iCU2RCpcdTAwMDJokVViXHUwMDAz7YlaRmyskKMsoSmoguRSzfOa8Oclclx1MDAxYbthMnJjwNtLkVx1MDAxOX8jMv/6XGIyXHUwMDBixJGGXHUwMDEwVUtmTOaK78lMXHUwMDEwg1RcdTAwMDNpwZ9C5ufEb1x1MDAwMUdcdTAwMGJDcEArTE08ilx1MDAwMjdcdTAwMDV7XHUwMDFiSer2TVLa0ihMO/6XyVx1MDAxYSqle+7QXHUwMDBm7Fx1MDAxZYjKkJuB37feaPaMXHUwMDFkuFn2SOpDXG6YN1x1MDAxOPqeV1x1MDAwZXE9XHUwMDE41PVDXHUwMDEzt1aJlVHs9/3QXHJOV1qGO06jXHUwMDEzk0xcdTAwMTaSxmNT9ph5l6dzXHUwMDBl4UtcYv6ZXGbD06M/bvg1XHUwMDFhjLzNXHUwMDEzwlx1MDAwZY9OVyF4KVx1MDAxMyVcdTAwMTJpVcDfOlBp6mBGtaRcdTAwMDJcdTAwMTjORVGbXHUwMDEznFCHllx0XnDhR2L4t1xu1789guFEMK0gXHUwMDA15WXgTlx1MDAxOS5L6f1stFx1MDAwNiaCRENa9t1cdTAwMTH89KKx02k3elx1MDAwMyDXc1x1MDAxMDswl+lcdTAwMTJap9FoXHUwMDExpytWz1x1MDAxMrjezOchbnt89Sc6iL4kvyc4XHUwMDE4dM8vTkQsV867JVKOwoRcYiFcdTAwMTjSxVx1MDAwNk/gqSA6cySYkJRcYlWKvTl5XHUwMDE5d7hcdTAwMDJcXCFAlcXJPHd5sVx1MDAwM1x1MDAwZnNXXHRltPg/pNnH9bwtcsT7naPTkrvC+Hs6w2FJg/d1XHUwMDE5sFM6g9YuojOnQlx1MDAwMp/Jk9j8tNw7PPvY9sjw7GDbhF2yMVx1MDAxNPu7x19WzZHjLd7ekPuD8Ijs6tPt3vl1lOx9jzly/SpXXGKhklxih8OWKCmZYJKRXHUwMDE5XHUwMDFhavZcdTAwMTBccjFyUHakhd6Y4JpcdTAwMTDKftKwjoYnq4dPgimmXGLcX8c3jcRcIr5cdFwiJIYkmTyFb/XQhVx1MDAwM5FcdTAwMDBcdTAwMDJDykRcdTAwMWVcdTAwMDHduei55/bSKE7e/lx1MDAxZGKx3qBkvSHYutWV2lBaupN5iVx1MDAxY/mBuDZcdTAwMWJiV1jL88Tb5XI0I0VVmivsXGJtOcwg2ipZ4GhcdTAwMWFt6Vx1MDAwMzRHjr2shUrCsMSIlSCV81xcgFx1MDAxMiimXHUwMDEx5lpcdTAwMTJKXHUwMDFldWn7SrT33GRgXpj3nUfwXnFifVl3LubzpXnWrCiVXHUwMDEyjjWvfmmbXHUwMDE3j1wifzaMXHUwMDE3b43C19mP/P39en3rhWC0z8ZcdTAwMWNcdTAwMGWL8eaiMpxo43RcdTAwMGJ2x1x1MDAwZvvVXHUwMDFjaPpcdTAwMTFlXHUwMDE1RYDWw8jLXHUwMDAwXHUwMDE1xV2/nIdcdTAwMDBIP1x1MDAxOa9tV19ZvF1cdTAwMDT4XHUwMDEyL1j3XFziZUKvsLJcblx1MDAxNbuCTUv5gXHncFx1MDAwN/3KdXD89GdScVx1MDAxM3Sj25pcdTAwMWTLXGbvmP49LuYnbSWdken5blAz6cK6YXRjXHUwMDBl/IlHknM/XHUwMDFkTKG3QpKz/CZ0mfyBrFWu+GduXHUwMDAyNXZAIC2WtMKKXHUwMDE1upVf8UvhXGJE4PyqkKZKXHUwMDE1/XPxQ1x1MDAwZSWMXHTFYFx1MDAxY8GZZj8/WdWJ3+nq4lx1MDAwNykl4japrFx1MDAxMT86f/LIxY9Km6Dw0oXuMyQ9TGjYUy6LjOO1xG9jIVx1MDAxOLPaeVx1MDAxY/579Vt+Z/g09ausOZPC51G/OoWramBVqqrqt0R9ln8vWaY+XHUwMDE4c+5cdTAwMTBFJLdwXHUwMDA2LapRXHUwMDFmxfjkO0RxNM7FR3BcdTAwMDdLjWG/IXmDo0Dxwb1Qn59iUyM2Z4/ItFx1MDAxMFdYXHUwMDEwKetOWISw2dJ7tbE5NaGodOz9wcRmIfhm+j+kLbNcdMKrqFx1MDAwZa5+qEeokjg+KEPfRGzWpk5ruqNRJ1x1MDAwNVx1MDAxNOQuad745nZr8UFobWqbZafJbvDu1u7+XHUwMDAxkVx1MDAxMOB/In0=Interpolation stagesTX DSP chainFactors:16, 32, 64, 128 \ No newline at end of file diff --git a/docs/images/gateware-halfprec-dsp-chain.png b/docs/images/gateware-halfprec-dsp-chain.png new file mode 100644 index 0000000000000000000000000000000000000000..a0b6c5ed1feaf3eb37319391ca81f6e431238458 GIT binary patch literal 79385 zcmeFZc|4T;|2ACqB_vWLq%4&s*^2BTB&4z%k}>vu4^2X)QfTaZ_HD|(Q%Pd%41*Eb zx3Q0HW}eUVyT7mB{kvbT=lTD+uRpH2RCAur^Rv9)@8ftM$LE#)JxwMCE{0>rjxlN7 zxqbiGF&dF$$0)PuXu#hPq21lbj`1DSx~=lS&vJR3wtDdP6mi`^Bv07K!iYMy!rH{- z>dvo%YMJF~hMfxE)~|X-&6eo!lGo>3Ytvu8X7IVK&U#wC75jmopYG7Vj5LO7N8J=^ z*Ydb7G~v&~yN!KveXp}-E>+$Y3QfJ~8JWTN>=*^zKYe(!$44}0zS}qFI!<*``Pk7P z+{Y;xE$RO89&~*AzQ-vdYkiM1{nH)Dw+8p1{P!OJJ<9)%$NyY#|BlBI;T}hd zKH5%w7_qLFJuw1{{<%qEGG3n3IJ(ugc9knkLHS@WRM-pxB`PL?6%)Kqa!e>Mv z3gzU`ABbzP;|V!*m$d#$Rkl53mXzXyX%{K)QLS;2e5-a;IBV7bf#_Ty3O&mxUf(nJ zbQAR%cRO@cA#g`3gs=qZ=%QJJ{7DaGzP@idaNVl;nCJdNu53!>uPcS|c4bd;6EwPD2 zz~wwFd}OO<5CTKqav5=NJD;L_=ZF904UF42CRCIXab9YHC1f|$e{UwvX2i~2&oJk% z#5vdc&*0CHL@{)gE5x(;IPDX6{}T97(Ofx$icW+pz0UFcun+x^nX7_lzpm`f(4Rz) zi6Hzfz6u*FN2-K<=b*U?^0gEWQk$wBZQQ1h}59ALTKl)ut zMr!7n)G^XRZdzih3rnpxN~(IPr0#Achp2XAu@K64*qU~ey!wJHC|C6q_db=CKd87K zwC`1Ruv#aZB$1x&Q+~^EEc$`*A4{d>{!LQ~ZAr=05aM2QPKUS%GHoWP?C?du732Y~UD&IaW zp#(O)TN2Bnp4>+#)EDf0NILqDbKed_cM=6MN;W9;hN_x~T`k2FivxjDIc?4{)+o*nmQVG^fw4 z9J2$4dd@>l+fp-_BQv(t>tk?r%B?5EDhKIigKX1@Hz!vw|K{0j7fL(pJiRQ=Rjoie z+|+4&+9}Z%yR0**GzVV6rE>X$j9BAj_&Ep5oIFGzubfw2nSZtK(92Pum5R^pf=N`3 za?cTteLrRh``e5aknab$CfD-hp{yvy#eq_jD~?5{VZI7j_tjF}7{t8EQ^ufR~rSp#!anIRK z2~BSI7rjvy7SY?urm~P@8*|idHk%}*Grp-wIIW5vWJmAA0e#uHd{bEUjLQ>I~RU5knXBbya;up@x(CCoQQi^K@&$^2=Hl9hyZ zy2)5Ug7;=9P}_ zts}OutnzvIaby_ueYQ+0x@P7zMvNBKwcefB+bB`nh~SNhS?qN;z?`i8%>DAHmwuK_ zLB)aaNv;~vI)TA~!4r@?@zv>8G1I4mofR{Fznu{~c^!?0z1bubRLH4`u+tUZqY`NI zeI@+T>UhHrSecHi)qmba=hUzNIHysjSwHifQS2Ith;K@2{Pgyx^b&u2^lhsQPBBxO zUuHZ3^A|SbnhvvPa~w!asMubOWgwD{NPSkioAT_ z$K6N@`UVc^LiL$0d7PIFgalGp3Tfz#0BLqd?p{aspx!SE8DGrH*dG*N>?Ck_3Ox#4W&29Oq6WB2;h!1 z#f=A|(oHzHl85*zs>VFK6=sXdx~}XcuIiq16?zy=Cn9(^{`ZN*z(~a0{vTe_*}8

5c`^?!9B)kj6=8~j zM3i#Ib!v_XQNVB6bD)OntLEBw-^!O(C!dIkkWTm!CropaFV(mTc4_$sv`E1`YlI4P zpPYYW-I}0q&Kp6#TREjyO?SDX=1o+L#Xx!vdQ*i++nQd}4LwJt4XK_2H4&?v(;f4C zUvQO4>44x6c7#ecesbN?lIY{z{05Mjg+$HTum*@N1LQ*cRDp#wm7K#l!K#BIX9vG1 z9|rIXVY1vtWi3Cp3xfpBg5c?MmYP<)a9Vn5yen6)=XDgQ$f-Qg(Szdjwg&TG=PlEy zTU7}LuaBB_|n2_JXLWLfJlI>)y0iAoNQK?)Z<5}7Fp&RJGApiVe?P(suqgFfDAzlQsEVP6l z8u75vqcyC9X*_ci3XrY!eMN!qAeuLIWKJ0dJF!3`&aCmsZGVM)k=CF+vI_AB{cR1c(nnYTuRe%`r}#fQ`zY_}MVK;fXj zqI*2lQ24&G|^9M!rLwftgAmvft$}bt$km58puZ1 z@h)TJTCHQ=7qeQ848*r$gie%x?dc-KcZQF}ow&kUgfUS5SHSFTYr=xy*u5f&qDymr zeQFxM11D0&zEi&Vv^uf6p?QzqAq~q8)rPoreE3dBCR2UStf##kvkzU5(p0JNpbP|G z?JM3$gHFl*u)<WWPTNh^ zeeUraBhu}SrzCt(4PPjbYaa5|IA1ZZY0NowN5Azy$wJjEr)`b2C~*m`QlP&o#tH6f zX0`z>*%BTOG_YnaQx5j~B-;y#cPGASI-tw(pyspkFkM+R78lSNPB3-*L~_ zS3k|+N( zj*YTL+%c{Z>b0;IH%vy&>+n17xOmAp`nbCE>5dceNAAjiL6i~=)Kdxhfc~V)bi`bV zXQ?L*i|UambUD?_k72n zRB3I_V!v9w`8hKL@7Qx{Qu8{#n0B2=zA!RAb;LU^*XbtPmPORjM6;eoXYtLI(f1BY zx2eDE8j4KE4Tpr49#AJ!+kSIsf2n0JF|+c>RL3%53eMz*iJg(1iW{v z^S+d?X>yDA8%_~1li#Gzc8|#&*GJ;G6sKB6YP0u8yQ^N5EL4UL+(?uzUH3@n;n$rk zn~i5r;1WBB7mp^tPwPc34xsmG7dolJH79xpjlETT)&xd}$AvU}WS^8{SN5`$aeaiR zhvAWrEB7zikYR60KK(hb!~M$W7Tr#sJmQ*y+NuSkyOfR}z3cKP)o~xB+C$qXnOI+k zV^x)u<-*3J=8D>7Ou`M_aZtEHSD1oIo~t)vah>t%Ty)aHzT2rwQI1dTf%PhQHtgT z8PzETQ=ZkAnK!PPDkYCTuLPS3W8;w*-&nv6f4ltZB>$KIPglLach_4?VKv%He-1}B z1t+9uHR<@=e!X$7E%%Ibo<1K(n3Mq5iBUn8v2R)m^g zjhIZh85Z19CZnG2av*MAO_QbYD}Rq7^N1vOT6iIh2N1;Oz6aTt*pEB(v45A4*p66&c>H(PVC4@r!E<{FNAHUpgU3dfPq4{jaYDnI(y^I*&dS_ z1y`%Sc=f4Z*|C|d8~uF5r`ieHr~E>{QE`E=6%t6KyA7+YuBww1=9;Ps&ZubQW~{Z0 z$TCcmxNKzrmPF0P>Wdl~ly6-gUbL&|(`T7zw2V`8t8zyF@T#s4tJqT0JkO-+HS+Ji z!8h2-%~Rb}A}O0lRSn>;EBv(A)^BUu#-8rah}jS7vWqQx+myr{;^tQ}UQ zMHvi!pRZddrhKbf9Gdl{&z=IQ0=xRoh&s#>#;H>Ym`i?>XlJ4O zFP^24uhu57P8j#%hVj8pC5o9QztwVtR~}}nz`f$wSP$p#-N=(BZWZq6n>o}t-kxFl)R^fq5$W$=rk`Z zvYeRbAw6n4?89D)GlEmok%HBvmES)gEHy4=xk@z?3EpzS0!`_yUeU7ayKgC9soY_s zvn-VFCX1qM0r~l%HI(7okv97aW71bRk+AM@qzLVf$h!!}qPLk(`o=!+S%6`2hIANz zaJ3@-^rq+ifq3{c(sz|-eUEHej{8zzIb)aQvgojbjLvH9wR|8`@ehLUWi+2D9J4Vo)k`1FO{AlLAhv0vD)iQ0kx)1G$1llkc( z$CqK?N}+5w5De>=8LsyYO2K&5 zCP!v`xaPk|d+4%BN7Po_@e$bd8n?j#-IMHR!G#XW^+(GEmK8mI5uFU@V_R{w@s$}2 z8B==-wDXGG>`=)y0`k_Ws;<_INgJD0Fl|HMz_o~K^ON5VC__iC8&f?Eji=8;w&zfVxm{cUUG#)v!P|5bAK(D7QE!+B%fN$^5pLN~DGrn;O zD8NN_8gxBHuXp)vb}~6n)7heSbXRD?yt94C<-6Z$ste3J8{~!}`=c!Z)glfy!CnV0 z@3L9k)>|YHKKQBt{j$p8L`QDBjUkeqYO*v5{cKz^Z#*YUV1dp8n7Ut_Jc1~vf=oV5 zKvt{V0+TglNRae~?O@kft)a~iO;J8BU0(>ZsVvx1Nz_v!0EMG&xMYPC;?gA_w=a7( zZ3Jy5gq4KZ8F76!cBuyU%+4;LcRoE@J^vUFU(kBKmLtYc9!FyI`1wP>X4WB;Vy-Xf z9JYl{#JgNVa?m#~-x1ilwB5Js+dUWNz&zCw9wyW*eTwO6zK7Uy;P9hba1O|{bs1W&QNq_EG!%EdBARm8FR|u@afTSYB>$3!xWyX82>)Dj6?U-M;9v+_pw=uN$sc8pdD_t_t*A* zom;mTJbkb({HRD5vS=s*F3EJ9*WVxUoonB|Cd4=UwUd_k*HzrNWb%^;5654cp(}0+vE)Pdh#b_+r#_!r>*ElRaQHfR=XIKEp{Q54PM-&5Ffjv16CWH*RC!PQNmV&{Hy0TldX;gUm z0^i!)Ou!6GI?dmFLY>Aceg6-8Ul~^A)~ziFN=SzRC=v?+0g(nN=`QJz?v`#8q@_bT z1*E$}KtNKuOG3I4B)|DA_I}Ua`+d*(eZK2Df7X`GV$M0n9OEAM7-K&3$#q>G_>?a# zJeh!JM!@O#3S9Cgls_83p0tzf6$&c5^Ku!&A2l6kDk$?{cAR1l3H(bY zr=YfE<^F93Wi(qo1)0FIlBByjEe+o3kqi!A&#JB_^+!x(xqMOaEK}D~r%?Ib04s(r z$!a#CvOuZR^;NG#dS)>`l`ejDc|q&uz40;U^xb4)1-RGqG0l_m{ZT#iL3ucDtjH8DourkFu-N&Cg6 zVR$Da8kR#^vH8o#U_bNDPL_DG!u99Ix zq(Ir2TBJHAdIO5VaiAkuw0!Jvqc4^4773=Y&$|^t&~gjfA}gA$ovwNJV|_I&HYJco z(zrf6kDf~rT~LdmDg8xkuZj(H3gz93QG4L0M#79+uZ^}h=JK*IPG{FDU#Uw1=shK_ z*k%VEaoXZ_q^8EHUiLQJ!-AM6Y4!rIrU?x-<$eGQwgJP|9Q-qh|qs^_;wWr6PL6RD{7T+hvx1WeA1p8ndIk?S9_ zTWd=^$~d>IC7O15CK-1RTwnxUIZOkK<0Qam+56DiAHtIUZG$}Yh5XTI8@NY*;VVjU zEtuol}x4))h!zCOXG zdt^~E2M=7O@xJo2k!_yut8SkyC?&}yu*)rX{BCf4BiC5ZB7BmY0|MlZWu{3+PlF}8Z>E@^KCUVkBkyC zdmWa?)D&<3+O+3O{F&#Tcf^Gpq-FD!Kp@EFM*vmNL)T4kb7bm~UtJ^5W^rr%i5Wlj z6D5uB8gm{>Y|L##Ja#Ml;M%r$s+Z%7pV}o+QI9fBHw1gJ;5WE_UH_i4@#}MP?ZK%e zK%e1$SOOQrNw)LB*KmxPHsQF=jfBW62NuP+-96(BA);h(cRL&C!7KC*gPZq++iwn` z;+x>5CF4=;tL9hVI<2#HKRaXavXpt!D5x@+i+k3VzQ1;u%&OJO+OT{oY&k{2BWf;t z2@WWiWuYCz-g}gQYrpR&*~%s&vzd_dFPpT!#KTt?47kP)f(Q6T{FSKtWxTp={>@G| zbXo+%`mb_OKe2Z#P?_wPk3@P-qIjnAQU+pn;M6~d6A`{ZE5R^dch+)J=lL_xf-<( z%78x!GQPeqe0@6r*Tcj0K$dstSg5LC6FE>rb`5_NI&N?(H9%mlDK<8jg+zF!AIVC1 zbZLB39@OCm1*)8vlDo9dJ4IYsh~Bf(e0`l+;gvJG?#WHUL0^tdrRYn60b z?00fXge=Kt5?m}p@TjDjaqTY3K^MHrHabr5(NV}5I02v*h(`3zELY;qgWhlDvjebD z=|&|9+{k638JTTi*>GRfV^;sRHx`3mXP)Kzy3QW3>gC^KEG}|(>BL~UEntvuylDaM z$)uO00C9wlBG##PYZK;fX16Q;rWbU*=)R|NnrBE^9?Pcqye+=TGcp^TYE`kHN5|Qd zb!8vvbH7I0K@T%mit~vfRmF(bjtI$l*;n`IjC0cta8u#PXOckPD)2gt%D@9ceA#IW zKeVChteX*m8LO}f^%h^tmpkB)1f3b)a*!I;tpd5D#5VG&4FNsRlHV?zK_x#%L26ng z?FjCg-Qx5KaBE-?GL@qDK_!ZlHa8Vuda|m1PsJyItikrTi5bY#-uj?ojQ)Dx)G3bt z9hPx^D13pjw(<_?i>X+)RJqPZiumDy=}q~Bz<9wm{QC95ADTq(Cg;o9(BDbZuVz-T zk^*BH0LQtvEJUFTtA*W_n$;~XP4QlS!)PRC(HUTJ#(}{k1k zU-%K6*mm3j2Oh9Cgwt5h-=3VB19>gH#GHoTd#Vi1C7l&mdlJHIxxu?xytflBfA@R3 z{CIo$;V9%392o8au)m0k>yU`qKxDhMO63@yd8|wnZF{`X#LL;4>KkTGb?JdtJF0cG z7&E|7ro!iAZ*Uk!)T=jibK0GQQp<~rHQ|9P_4QHP1(`7#NjLCRM6!&rXYjGaXchlr z#^oAP1Bv=e=jkTeUaC z-z5$1!b(+|CU=+Hnawq`eiycX04Mo6)rm|BGLPchON*!M;}td%z+K9Cuyb1qKC|uo zii)?A8;0dRx2i~V_M)KU2IS$h7s;j;1rwy+WBS(v^swluv9pdMy`*}ezEPQ`x$@*j@_1ocV@IIU2+PV5l$`JCEBOE9t;+h zDHmzYj!%xt$LI-hSAiPZJ6RIc8!My{1(ZvL0C%e&47i;;zn1_ed1zMIROG zpOEfSp6P$ORKKXGQO1L{NO#mlQ#-|b9GpP1RJ0_!3 zsnG0tp=o;1Ei+;{=QA_!e_$!fTxMlbZOOg)eAsA)9<9u;PW5i~<!tUhPbfWHQK9J#lJP#{go+op~`8#rw4H zHNN-WB+(sI>*ba-t`b?y@j>GqA#xS-^R`o2cWgS-)ONJzlV>Y1TP`p%m%*?Z3kVz_ zlWQ$tc+#j76>@NSW*AIBeoO1cg~LopiRIX1$)yX7E~_(JTFH~n@(I$W*+3DTnDUZL zZEFjbHGw?e;FOrv%1<1r{Z-=PssY(COkL zwQ@_26Ds`0-2?QYTW^!|DxLL}pK`5M2Ag4Vg%Xi#gA-}tmUPq2rZzV_koo1FIZXTj z9m{~{B42loJXZA;!ty8$@;eAQIr5use-+D#IjqWae5ss%@io|k)#Lo+dxzcnEB7dE zTo}E{P3d1F1eR>0KGNpC7vSX&$Vnr**)f@4-=Auya1PC_gO_V)4LJ(ix1)AyNlZob zOa&tgs+>_>AH3BP_TFw8hAT{Cn;@dJ~e98`&i^`s5Qa(Y}cNHqPOy^JSkf`k7$Z%AB$&GRP^O( z`FWWm5e>~s7ts5KMjAVpqml5gn5(Amf1{IC*)RIJ&gz%2G|EqeRNH6vT4v-{XfpcL z9c2#CZpE6c#*9ZX#eF*Jk@%td{G8RaRr9GG9j9b!*e>}PabU;%WcOCyRy%*S?_+X>7wuDe<-N9H{bX9<+ch>9=R8G=uFH6FYVSQ!28hkz#JKZw8$q(nZ?=k6Y z<^`u%!hT7v5-~gf%|I6s54}G)wAJntYRn5u-6OJ#dW}6su z870}S&NHoi-L)TElGyA|cs4yq$nd#hHvne?6{O9GMsS$#?jD#hiN7J8d4^7OCBwep z6Z2bUdOm-9E9P0#T4j^9g{W`9cg}iJxvne2IdH_wyRqxtJrl{n$4n%MsN%Livxq)e za4g&8pRAY3)R+9Aqq@#NbU*r_hMWF69jS=<6~g>;OhtaHGG>(NG+RK!@`azv+>JB! zt)MMVimYF&VN$ZLb`memu-tEFj4GPOYHXNpR$x_}ZeggWm`bH`R9VU*Rq!tsjULx} zlMvkOGRzQAe(=-o`?y1s=})l$6hDO_yh;G+o!- zY{D^IQf8CL{?6jmak&Z;-A}zz7ek*4rIBf>Rb;J=>z;CFhXfTTAa&*KFD+^z^p)ol zQGsuj5=gR1ri|As|p_b-4Z#2=RA^OI{ znU&#Tb}&H@^kWzO2jyM<$rrH=%yKcp?WTcLlFEcpy;}<^ZWg`!@4Sec0l-}b#SSL!giqv?qN~B3=!qlG9 zZ+27El6Z3|xkb6gA(+Np;m|FPD}X|aJp?)aV3kN?OPi0}evNB8)c3Q5UPC)c$drv0 z>gs%q#tUR*Sq!s;cCMu1t-&mbR5R==j$XxKScuR_#A#ZI#8#ohXSV!10T1GLj4g9_ z8cqvUglvAk3K>tG=wETquQoVUVO;nsH`Eb$rMJ_#nE827Xor_%4F=M}Cu00>N#&66 zXjZ)Lb$nY$K9~hHm*a!vb}N%f_nlFS`NDak3m za#XYjcb~UfC3P!qHYlI)urxSs85LECFDo;|nF+lqBw)GdIiX{eQ=sDN8=9*+tyA7n zKB|kGJ=nPT>`^5;wgYHTL6gjpqq@_;PWU9&MPKSw;TT&3=IYK&{GpI%%KDC8L+B`y zw^nPq(U=gCSsgzX_iNAFX1bZMrv8kL(dCIFm(NXeN3I!{T4OssdxbXNJY%i$eZO#6qL*$lPIXQ)=2;0;V zR~oB9LJQr`*3=_8nexH&zurq_jg*fPFS4x^*Moa^DgEnDSoQY<6Bm#X3JJ9Qb#?Vf z8N=i7L>_xJM!)vndx_Mj6ublj~(V0R>z4+cE+0CKO6p^Xe<>JrG@sHVzP!E4m#paCG9J@Pa zKb0{S6H<}p_Wu21m*H>3g|6a}oS|ujVSl<}{6#CzvnhXfix#s%pY^NL*H;qI^Jwub z&8jHXd|qb0=x_Dq?utpOAc?V{oXC2a37w}##`#+i}TN{1}AGmq%3KQeqM?S_6cw%!05v2`xUTqT|9ZV<7sk=X^++%3uQ}JKh{al@G?c}xk5D}9Q%rQT&GR;y*K=@9c1d#_!io-kp zZ?lA?U5kNoqU6PZeTm9_jDH!xLN3&73d~+9h_i~Pkf1LFgIl0^4brrT|35Wd%~qa773H6>|ADD0Y$nV^LK(@fajS3Vm; zK?Gi_j?l<=LUlN;a6&a2jE|6qT&<)+w?2JZt;Ej8dUyvR8{N9V|Iy=r+l%(^goY3J zZy+GK(Kg>jx$;SNMf;a((ei|n)E|wZ{cL}q{CM6DNVP{Us7uxuju%SGxX_jp7FSG3 zGktHeMUota)XU2X`2=wx<@+0%g%lMgd8B-xN1ckCHyev;3)AD@nUE6cn=8mXeE#D5 z1h{yWg1$ro*Sm$iTX5qw={cUuHuX84nQ)-j2vA!-p}nQM25lk{k;{9pAs|&-D0V== ziLmsGw{1Rn883BPVsfnYjNv|e1F?}{9^J?DWk0v?zYNfKB)zX7)XevdoFuS6xsaS1H%Sis$luek~l*qqF2P4=!#{(WjhC7`xqwc(m0n*wV~ z^VR!^(svxb(HU-5B36jwq7bSFCl2V*;@#y`hz>zx!TBUrH zqw+U%U-l0@CGGk9lV*f0?tS>X&oL_fL;U&VmUsLHqA2>JK*aa+7RE7Vz8Ia%-d-|b_^`O$E|H`*A9OM?T{3t6 z(|&5UuDzRcSw#q@!XV6H^EIOkn@vkTJomgLOLD<#Y0UtOb7C&OgN{H1!ppdSQrf3# zC4H1+L!8|(AouteeQGL4k4|WHmJ~3+plbbrfY74;@llBm0%Fp1#4W(>TH{AskAnQk2 z4*xzPG=X*YjQ~`~_7EWb4`JOFWd8J^f2{G3AGPbi7pK0_nS{0ZPzJ9R__PVSxaX1} zAbrNoiRuarY{n`pEjzmML;LroTStLE74aos=nIPzjiY&UJ0h+#k(-WuUfsJ(oSKe@ zvSX|lor2=Q@k8q0M-LJ_DLEFV`jKvmKn zo@Ie1$Gw^Exko6%DZw`a0`$uh-#iUK{n?h@l?);^f7mJ+;zPXenQh)eJA%QVdvio6 zs*004guAS-wOgz-|Kpp0t?lAqvw7<$_YP6Zty79$1(csyp-q2-7#~(`eZs}j;GU)b zlRy-&!2^GI;dG%!lquB?6^IgR?y^D27_RkN!jy_p2bDY;TkA>wo zJ;8OAdN2uTp0UtOQjYN|_{O{Zb>rtL5tw|Pc78IPKMP?u*EYPLU8(nQab;Yh#PBJ6 z!(z}j6Al7jMQH`+lqt0m4%&8VQv9Ne{sivp;A>V#^xk<*u#EHW^H8n2_6kY&nhWh{ zFpL|_Gq@PPttKX!sXqSp6d0wKC;?bnar4AZV>BL7Y}<=Q{;`cro#PF>!l0YicDftH zXCU-(^Wq03~|Ej}) zl{7IM3Srd7wRvFSp5@Xi+=O(??TwcZ(>1|Oq>Gs^Ha8$&C55$v5s&j&y98KY5WmIe z4{bi+01vZ(E*SxLpU@3&eWboelawOLo^o5%7_N6BZfO=ueNqFjc7QCn`2g{DtaM}q zQ-|IM1w8OTXP%|8I7E7}`ADD0YT5D!t~456KH!+5KRIZ&^%B^*bq4|I2MVydpCUeg zbtWgl`Q5JjwtFga6&V8IF z&t74Xl{mX6RYBT&dz#F*m&@z@4a5=n1Rd}GHUZ!KATVO%-+rhenFERfSsvMqs3hI| z*BW!uPu&o>fq;vb ze0B$N9#IIsMD1`8uuzEA*<=Ebtt5;oIFRjcO32GzyU%VNpUIvHNAc>G&%JfWZK@C# zOtDoJ%$Jasb}O4d&TK3#tr(zUlrGP6WhpHx!B=%6=XD=%JD3w7SQ~^oZw2q-Sl94P z_?DG=en}7uNw+D|kuTEQ$?RLuzFuiP%-Xgajiby=s-@L5uMCE^PT}_AHXI9_6tu5e zsxmjvKl!HakSzRu#YE0+s>D0&?>+^mLCC$qo7 zCbmwDDcsBe_ezt$d8Mv*SD-C*yxAx#9j%xPzEt7;$M#dl;-@d9uhG029r5Qciyaz(8ygD2|?|C(p}F1x2w z74gN&!2=ARHiwXL_HVc0vgrRqmYYGd;e$UESRV36@?>OMP9?MOq~Kkv%sQj_>*Cq+ zu5!w7^@fG;3yK>12g4_B(t^<5TW5P|aUDL8kCbmxcx_@g@;yGWivz_;&8y9as})cJ zWE*nEg}0;wLP#1kvToLX#Oh2`S+g6Q*|oFO$dHHKK;T6sR_9U<#4<113$v#d)Wi)3 z$v<%NN8Ya@Csijn1|53zB9fH5d5wedy^)YaB`Lg?ngu{Hvw9)CY` zOZ=VXP5IC7B5`)(p-3ql^;h4kmgU34jgKo%Ut%++FR+Z$7g8LC`?Z(XBC@o_Ys%~@ z&m>#kze?cl4KQVCe(+cx+*0D9qdn@QZ;+*Nmws@A7V4%w`TJyX5oaBuqG#=xOsZ>91Xnm`c`7I{|D( zf2qfa$&hCJFA_af_g6mfy?EhWif&|Z^_GopLj?rRLBpk1_}Z#9p^YX7HKV_5H1Oer zSX;wmmu}aY&DuTrIw7j=jjWf~(R9WsSxenhIO zr?E_RqiU1nnrO1INST1)pn#q5K!Y6nPk!YUghbONo@u;oCa)@I=v;&zUmipF4yLBX zKyb~H=8?Q5vN5~U42I}-#FIufvrL*2o)*QT!R`4u1NO-!OTXI{P1Ub6Wsi$bgQnvR zA0Lr&^ED#?*C59Bd0^+=CW!HUlLyqJW`30Wc~v3zZK{z|RppCLktSf(E21S-$f#eu z3WikU#rFy6gO!T@=y)QFb2z|lV$Yi`s2%?QGqFMX}VCVn%SY}n`wISihkP7NyY5|G2$Ul^));9yXx zRbSqxwXSmJBCME01U$GGfiV9E7csg2ieHaO&;1$G=+IkW)*?P}B z0QJ0E4Hk^64%EhjtXo^(;qu%-bU)gWp4(6Zz(Ul(^eE zElWOsV$iu-_*8oBC+%VZ>Q z7C#4dZGWq%Jy6xve}QCo17Qq3Dv_K1cC2KXJ8^GeovLZos<~?&^K~}irHGhiB-80l zsZj8lKF!4r&wS=i!Zl1vd!vgScZDmaOiunDW#b`rwkMjHgoQkw$A=~g5VXAj<@H2m z;SVsza2$sRLd^<@RBZ0lM!)i_+$Aw;HE01JDs9kw0EctFW*Tov4S}))^sJe2Y9$PB z7kKzLCoF#RNz>Z6;z31NjKFrKb0)K&7ZZSDc5=*bzuvY9mg?STPi_;u_1y#)ArzHZ zl|_M`c*f`~2b9mGrQgAIMu_%GDN15sS~)X4f4D^rFYoh3uZ)C}w59ItH~5evdFdf_ zF%U`&oR4ko$SB3w#qH)_Yz>kjVqya$@FE>fMW=w7dKn&5Q@}O}v$>J#sjMyM3^@S! zfr}*idSjJZx5K9=G0ZJu+pjrGvG|{CNl7u*n!5(sKC5?C1dk{An$0GX%i6X-&8Qo` zG_X@losp6ebwsr?-f$s9P14qo}!UXZcfViD<_A$l#VgET+M6+e?M z90U7NUPWm{xgTf|+%v#=P%hO43iv17C^^2g1o`~qfaiwbr7 zaKnp^|9M-F5h=4nyS~wNFAuJfN4ze|WnOhDz-P5^C8v9;x*Z3Ux+edssF+LPb5>9dPBxOwRJe=q-LlgMV6WrT{N@2(*pxB0>Uk92|lBWN9tT-ZdaV9jh>34 zz-I@`cZ|!PDxCY=N=G3j1;yZ`6vV&r9)554vMWp0GiIyJbJ@eKJ_o%=M?eUKv zLxZYN$vRw?15oOHp-h9WIGK6;BzLOvb{S@Y-X|CouoMupD+{8S>c%7toY#&QpLZ`- zK5!^n8nfuBXjKR|e6|J*DV%=da8yEjOP!5Od^Gib zWPU*F9|=|{$Pvz#R@utPVmt;f$l~MJUVpS-P3cD<7__(a-THZV=Q%G} z>8U2_COq?m59w7qB&8rX%tq3sgvB4;r?gjdT%26U6=jP1Z&Tn1DC|V~)}%WZCA10J=GG{u8S5H zH~-F?&lE&*`GfA^2@2SA$`rvm3Rpz*4_PGaVo7a4Vlp>dYWz80T@MQWi&{#E)q)EgG_7`g?O%57NkZ&)eg*S-16(?Ykd{14|GlU zsv)v?EAFo$%1QRmWTmsMt<#UP%E2A$L(H{Rk!ptk&W!am+4LcOv@gwYrpE@+qp4H~1F>T#zye55hW-a=LqCVOlCk18JS zr7n$_pxlFi0~qbA$Fd%4^~SbMrRTP~WZ6mXS2vfF7Iq=R)J|CIS`r`$i#5JO!p=XdM(! z&ZvW<&zbj_F>Eq zuAyiD&`_Geqt2`$kzPW)*yA{3VA;7N#h{&u)t>M#L}I?epl))aPj3M^W%P7Q?I_Dj z$ZY{ELuBcr?DMcYzPp%f~B)h-P+=g+jK3b#?o@cGW0sF=G@y5=d>5=)}$8*!i_NU_0 zB})!t@&u^%(I#M(0LTIrD(CsVTWNV>f~c^BDPU@MY6Z|9RKXY*zBn;az^!`&Y*mus zFB483n)(+T#RO-}s<*F^rvn(J)6>*D9S%S_m67U$qyeZB_lDF_0|E|D<+(tEmyrE@ zgbK>?fCNWX68w>@K!Wf8k^nINUR!?=G9wZ=4>#^(iITyD{Gyovk*X&+;7x6Z;COIA zyJ5Sa=8WE3AciisKP?c0KAap2Xs@$a&>`@TLItqb0hTvQ=V?hjal6{)_i>?d_*{^6 zVaFfsA~^aH~BvKO@30ST0NMX&{zwK ziT$Ic@c^xa#r{ZK;R0o9rk18_^lfV0Rf!RJ#A>43q!aoDRwZN0yLG}0fmZb^@P(K3 z#20-%VVx0b80z8<9&}6*Ph&s4w94-K%L zElFum)!|=}@ERDetpu74{;zR*@I?n(i3b#*o~!3V3!p^nHGEUA+2eH;2dZ&^e>sA< zVr>L3Fa4Q?{yEOyhyDM!%1^<`D3{Hzx8~H_e8?)k^&gc6=s_#8ID=-G#cKx4qvy1= z62CSl7I(FVxPjH3+!3ULX?!>M?>ZnT*DBwCz#^)fE^PiQSx~n{%SsItBH=pz$Hy_6 z8+Zfn_Ex=&3ji2o?_@5=Lg?mS`c-Pp>s;mE|L!>zbgjZijyHxYoP6D=00%|=sSj?0 zr~DK>&Zg$X4_11!pa%zcu(|)Xy?>dSnBtk;s_gHX9gm->4+o+@14!DPfPXnhJ~h8y zqpQ0M;`n_r_4OL%|FD#&ggN>7QGtPI3T83Cx1%X*g?Zr7s}$HTgGg)%6d0&cR;Bj# z70vW#suHrw^CdYetY{ZIrl0)&{hNBYEB9LKCjxwro<%2^G%Sj^ipE z0BnweHgzm8GF!U4ko&BsTibcCP>R-MxR_@wME^0zLx4z>z^oM)(#ya|UhaslL71Jx zYfM5$Qb^r1RWna#>KuODnrM9X9S&z+63CduoKFu3=^`&~V@XVn(ix1;BMhc#5*4P= zhfvKxEwMpr>!np2=c`Cn+m#;GTxN_B`lnG;4{MayM+zeQ40DOV?sevXj!f|W5IgX# zc@1xt4YqHIz7m=B8rgWBuWuc0&}E3+!KgnHyDqAOXpMY%xx&ohvMH$D?h_+TGWo3m zk4jWp8q@q*enFI{ntK)bCoTn1F){Bi&$w6O2WkXd-^|R{-FvoR^7-)^{6EY~5 zCWD3l{Oa=%TKEU-@nVy_p7^>>3K;zk<3OD<*2iZ`cz90*7W_#EP~k?|f%f9K#mu(w z#7*$Z`xuv7-FQ>eqd0?wg4?M zFRu!)-29_Vctei~amrykCX^=$FFbB_G%I7;euEvfI|82HgxjEKY;(d8Pel4nKQL(M zv}1O;#Y{w-VD0zZyU?JyV9?lGA1uke_{5=1;>V&sq=Py{E(dq2AtMhQFz8_JJIUG$ zLtH%Y9uIOjh82s>&#W;S^v|aIx3yh3&cTugb$$%AFL0uf#iYrKa{ zCX+1H%31nswah=-V0`$`vjA;^A_p-)-5qCNf`*w2XqW*d?D{r$r1XZyIZURA-5vk;7?64(R3u zJQ%Ve1N=Y*Bo;yMM8=@R(GG2M%2RgsLi>G`FD1;bweR*$jesrS9+Anl@HF}b>F(Ea z%2J0`SY%udO32E}Kx@|P9W77*+w#VFjs$mtJfxKbZkUqD7ULIrX{9xJEZ{eaU=js# zfhGX!-gnS9d7V^dJ$4pBy0W1}x3u6&1$x=m=EeYIfl_}Xo`#u>#ADzDpt^T57kgUa z;`L!Y;)b+hP7v4RR3j$oMpW|qXqCvXv%2z&ELS-4hLx6fyGoWD&%T1qc~&j0ly>-Zw@O_1 z!Umrfn=hNHmbxhl>gui@jdkrlz6q2#k$mlyV3F49ZL18R39N&SME%cKl0$@vc|xO`Ev8L+n=cV!~O)^)&23u zs}1UUw2B4kwd(Ot_+x&*IVM@mlIXyUh>XOz3#~^C;B)dF+INuE<>OSpk|#^3+KO@) zx}E7<_GvI%8H@Vq)8bFIrq|&M_)~nC)8&Zc(q@frE(rMf`A}G0m!gz}n4uOJRW2|E z(V4IZ|DYs){gIR($cg&W=FdvO^{mip|Lb3wfEAP33z))V!GHenMgrhXf$~-7FMsx5 z1Nr#}-{GYAe%FBT_htMs9^X2IzHFJd{^J1GgNlNhgWy|P zVhG*-xr{#t{okvc-FiMDgDia`180xKJJ|n0QKT0@1%p`Mx&%OnIw8sw>CXcw1Rlw4d18}H4?L_~1- zQa^z)O=&uup)s+Ler?i4$QbrU{y6yA+iMeZ=i&F)@3HRRtCsup=QbPxu*(I67xNJ} z{n7epIA;YqB^A|eDAIx{$dC?QY)*SrCu)p}mg08QET+mM+A)ZB8-n^#!C5b;7o6SN zJrBMMN-Ypz=Q z=7N!o3}#Pq>b30H_$n2K4NtWy<;BIt1)fxeJ|gF?a}3uooZRCFa1aXe!d+wS`-H&u zw0)Z;K*iF>d5Vc$@8yQu`oLM1Nu+STvnld3d-!NhYV(WzE1fYGTv%iDuClD5#kd?41Vg zlUgVl(0@6<%r+NE-thrcjF7_+aL+V|_b<$h+E4`Xu0Q4*4c#hIS ztpX~)+pS;onc(KPl(hQG>-IQIFkuX$r(>_jV%vO-(tG&emWkYWzD}fCVk!j~KG7{r zs&}`48MO>%CnHS+)r{-rtu(l2%q;23Xo`DGx9J5cK{E~!eZ zR{4NFzKB&x_&HdB_8PsK9lY+^Y=YR`J5>}ETM#TN2|1|nv^6*2>BvEskm&azDCKvH z2ZhjJsQ7n}8v}xazPnD2r;-z8RZqwm_nK#_#5#Uih)J^Zx*fuihO`wiZ!|v^IVe`b zSx6{{>gMTD%mDd&aZD!Ib7GQssJ@j5)s#3&QmKG>rvP1_<(r@aF|3oC%R_mh0Yw#} zfNj?uD6LJGkOZKP2}NRFb9TC7K)yaUXa_{azLu6e+;EQ$CbG|1S}Kl;oY_m|h7^&D z*xmCGkF&clvz&SOYntIi)8kFXh2gLP2H>-NKsCtzub}f4PKxsvp+?_6%*;V^ePrja zbyx?B%HucEfO~HYs#~3Nf*Y$*nwVsi-+KMZ=WW|=DRL=YvW`Ei*5GL#aGa8a*Cq4@ zA`2cVqZ|`k9~6b%9`Ak$?l(B=;|7URoz)|TnL0ZP#)Tl6Yj%q*Dx}^0D{22*zBBlJ z@(gD;30^y>sUpX`fyh@V>QnU^wU`86U{8fjO7G)%0TMkVqHBd{LgQUfTc(1Czf^H= zETg`d0xkzgfLe>h!_~=;ZAd9ZaJT~p$yW1Q*O(gpMJ4SR%P^^CJedAPdVf6=>RPuzC9fApf6Q{XSiPO7Eo*cjX(g_Xw1X?e} zzpodhqWfDb1@dw>&h2d$Y@snAD~;VWH(x z|Msqrj&jchdjr-th1emJ91%O0NEjYxJXoFfVsF=- z0#;ArA`Bf*rl2g=9eMEEc9~6HUj6~6)suwRR0f@208@m#cRT1lH&hrg$7A>Sky+2N zrC0Mx9@m(Oib_4%ZHdQZJhjonJ@xVR?oQLBV#Ou9?p65TmI8@uq{vQFP@9kXJMkt+ zU(6`x%-uixlA8cGt;PR*@hQAD^Yu<%&k-3L2Aw=5B#kqe@na|gyk132XMM8DI8kh4 zmKvJGRT&8iCyeSX12`FP;GQ6j``rMC&0+>;{07%l4=XWqYRa8Q&%Zwnh}+7v90X+( zFykFGjcRMT*|=Y6i9BzlX=rJ0GWbY2TSqWk4u|b%-48~W0y(b>l%4wqo|3h(bll#XCFc>0r;rCuy@LEH`l;gKAts+^O<*&L9 zWt?lGVwFXOgnY9sW4Cx(jS(Vm*aVz2lK_qLDn95*jOvF!YSPSZxYSl z2TMpsrM$`CtmX}{qk9sAXn8k$(e|NHEi~ z>+FqoJ4hGsC~%|BSCl|QL(6o~@wVHV=I3-Y-)Zn>N~b~Y>gv)@s$m;1IpgARJB*8p zirP&lN{OvToLJFfoSJ4@iMyK}&usdHWU?yS%*7%0)5yCN}>*fGNsv> z23@NkX3wj;#-Oev4@F5b(rog10`D61Sct$ZM>w1LLg#2G$Wyy{j#fuix8t6%ejH*S zB}bggzffvCbqPu2aqS}~*S!bdqK~-WmnrJ(Oc@|1Sh0V=G=ec%Av(VV_~@wXqi)r0 zoTs>1@hXV5=bO7P}=xyn_A%sH_WRUhRMe*`r5d9ib*doOjrzVv3~#M^@jXWbTqvxB+)r#H zrZW0RBf^qGjCU96=OU#e@Y^z*e3dk^oS3h*`aLIDSQ%npEVZQ2o4 z*@|M#^n3cD!2$`91DM_2vOSC2NH~1WKM{p)V?qQ|$kXvv#dJa;g6m>2|8spl&lEFf z#}mKMhtZBaareim$g~D3zHW@=-F4ciNNvuivm7O0VBQGaP+LScY{J!~jk`Y0Wa0he z$AP@t<;yk?0q>>+-hfTru{;rFkY7v93h$4vuF#*#D-~!xdh^O<%SyJWu-tM+o-?5V zas8ubk<}%$x$K-C+MNzGZzz`uQuw(P{L%i+{JhSDdim=Q39&R1;<^m?C-cShoK5#H zl$fPR0Izum;D9L11QCG>tw*(3u#vUfs*T1)kA1b)=F%diZe6_NuCDRWhh3l>qzicSVC&rPo0_E8Y6Hv=0-pC_aoH=NSk zXVT4~ZRe*-qAS)pQ2K5AmCbf=(~MBFaC;(z(lCjH$Hk8i)PIp4-9mDEl1ik}uU@~| zl^}G$XYzwiueY|a=fmzo$J@u`vgF}hsk*PRZlls>Fjs&pHn=lo z=Det>-tOzvCRF|r*{&N(Ar0m%KtzwpN7=L|yy)sVVPq5cEMsH^Ad^T3Kzm{@%@IX~ zK6U8fYZv!*0yFo0!ieSJSN>i_NVC5|Ldb}Po8DcpC@hwe=X%=J67+5)g!Y(_q*~V) zl<0dx1hR~d>9vSi_278i8P?pxu+sfrO1;Qf^SB{lf1bi*B3<>pi*VTps@vfj@kG6a zNZVuXI(t^stORcXQVT?N$IS?cW92k`m^je6lstQh-iwO81+lWOd47t1acwUEp`<(s=5mOJPH}P zz1`TG^e%l24i6vh=9u!nn8{mJ?&sM)xjZX*cXgS4H?zL}1@j>+gy5SRBLiXNsxQxlWZXWKvv)sWtx}Utb*+Wz@Z^NE?6%ibx1DfOL14bT>mP z-Q9>FA*l#Mm(txG0)j|)he+2DLk)G$_{Y2m6Xeta!B|FUj^(8V-T1gJ$eL{ zXqOB8))SLLlbS1~p<8E9c5yKlS7IB|h>EpdCwq8+#iAy7Y$mfvdGGQ}tmhrt?FLH# zOrC~@pk*>ArL?WItWR3p0|lkg=NS)RutofZAgX2rd`!MOhUCw>iB}mf<3<&nTc4fg?pZeF=b~csW^J@84xW(KeC}PdZtKTfAN4!s{YyDoM6cds+Nn z2G(%DEfW7vL|~IJ&3tp) z9SIy`fU`U6`jk$kV)id9VVd&@8Qj>*ey;8eX!chx4+3uH81K1m9kGiJBq!2e$0dzs zpqq3*y7pZ()vfY3le`r1L#h@S)!BC=B|HDKNB{y;&(z%-V^i_)$lM%m4Bd${2_!Nq z`-~y473}>vBZKAkZ=SMwy*4LYCeVt)@3nvj{NckEFK{gL>bJ9t&mY*RmvODxDu6cK5eU71HDzS@<;#~* z6&8JNhu1M8zGa<0if8|jUNf`fbaT?J^LWZd5Edd%wKLSyZ!?%?Gnh z&R1Q%!&xXAcuK^d>a#i@L=lZcID>Y{ev!Bk+Gexup@fU(ejio#WYleaJDWvf4`pH^IJPsj>cUs9?p?6~7iJvPRqsiXl8`SHE{7R2sy2>} z_)7F^VtdHz1=JU^pcMCuuidNqldD)?wX;tO->|Z}YNOf-q!Kq@p>aL+{I7Lq;p)}d zGRFw<7OWFFqbi}X!J-I5jE^5Nj*2WOl(R7()#y`tJH+mk?J)qBzov<_8kJuG4ksS% z_Hh2bnwAzhvVnQ+yF9-tJM}@{hEe>=@l)}rsRAfn+xf`Xe%BB!Kc;f8xkne9V|*{~ zExd>Fn1Z&c_Z%qO;$TML-t9xrp2C@;(Qu_F0#K9EIJA8+`yL95$#9+o+#-A2ITcB{ zo}4eW-P&{|sgpO#=+#(ibb>yr>HdprOvaDn5Pn7Gz|#*%#MsGI6mCM*94L4JjQSgrx~_0;=h!Oc~T-Fg;HC;ZELs zzTtfahSxc@Nq<)2fgG0M^V=8y8jfIZIFyWVCEE)6??HToG0R#wpg3 zX^^Z~yFA)Z%Xwp}fcA^$qsw=%-;Fz(+w-w?!~F#1-FIgRxAi(PPu6;r1$VnjM`<%~ ziVa$n)|vE(pZzOn!|;_#njYvRwm>3KKv7vVRSg{_g=Wzy0BZ<@XlYd zWZ+ zd-uTHYTVGqM`Q*J#&CPUF{!QKA$oiId(Qc2d`pF+p@GjSrT+`#)-0$Ud-W+g#nk+L z50o8V7bRx{yh<44(es?niDUz#Au<^00Via9PDdJ+t}Q+%v2*t1dm|%_J~~4wOGdOE z{sAZ3Gl~$UXFL01BR{Xc{W4t+-g59eE<7n&_)W}(++w(!7uSMjeanNj0>q{PQN?fIr!+X$2FEG z>O^9LLfZl|^K)!A9y!Ci^sV*CHgZ=e{xLYZlGLJS;EX&M7(n5a}SZW;xVVw!AVvy_Q8;Zh; zCvt3p)=KqzQ2ds0-~0Dtj|m&A0o=3*dG**!RRM1@^qS|v^X2KN@lFf8TqGHjpt<6J z+K_SzO-73fs7Js26!}9LmB~D@>)sZ)3q2BbF#M2X*wT1Zl}CP(7PN5hRNU0`x|<0l zmUzjZ+dX$pf28Gu{HfALi5a0!=)U+#Zg=GBrp+iJ0>=E&pV$4n?s{&(I;`O^NXV?% zkdmJ*O9mA@zJuZe&e#_DqX72ax&t8xP$28?2}H3GHVH1`D~PVH+w&RqFB!0n7v;}S zK|i%GP$L{h@q(lSRcBS>+`NxQe7~QykJk_U18%~9v7)gPgT_;UpFa$tqkTrB2;i3C zwp(zz@KF60a@7A7%fvaeJwWj6gV$xX;5f&ku$ytL?`)1vcsNQ?bI)Tbq5PLj#$dw0 zoV)Z3Wm75>;YX6~PQZ?hPs?AoHiu5c2AU@$~g8`=8B$2T)k>s9bw(t2%!cAVvr zoZ|8~<&DJ9Ko}t*f7%U2z>R`+ytBI=`psTU!2K&Yw+RhLVlFsIjZ#BBq?Lw4PQhC2Nfx_luQb6)aNH}-2PVTPAt5yJ4h&@ zXN2qe(_?Yc4_cZ9wI#hF7UU7Wf1z=-9Q_wQr^MkmKAt15erPT<{m%a*@!dXcmaXW< zaou*N($B)!R)pPQY)1s?QB>QO@Qkb0B;CJaBR1I1D4=%*u$1qb||oj7f=& z*|!1And~M7EFxDVd0uukVCU6ITsUt}uD)noRI=-XgoO38AAfPKgx`3yUFoz<5fWAy zrjL^3{&?D>$K?QNv~sVMf;|&-+IC%T%p_Hr;*n?3)-IG+u!PDCoCz~Es);u*ZE-D_ ziI;`a4&3f5CHqhDuzaOCau%&*Ek=j6u`j?>6Gg1XD zeEvnaxeZY2Qn0EpO=3n}X5d9hwE<(6RqZgPS1ZZnq`DB^#wlC6{O}x9E_!7k75EC2 z8gA(jy8X5t?ycd{0iw{WsSG0kU*Uw$k==9MOmn4gxP#$}tO1AY7uzX)KKU9Gxe$tU zZuvxvsh3OkkA>g?M_!u;kf?!5aaGxbdgB+dVAw+V_j-13M_1Di*YDV$KYETwh#lZq zCcC{Sa2D*3{N+cO15KIK^xm>okYk#UnM6F4Sj!4vGlVM(6eoVt9i-lO7WGb@ud&{8yarDq|`tW(*+IMKkh-iIln z!0unJJD6LT-5Ul9f*6M0^`3`E{_Nh(i|UqhJWAQn^95B6~U4T^XV zMf07ikmvUoP>PU3tWOo}vJqS#eYM_%kHAm&V&0DkG3q5QrJX6IQX)TSXujKthE`gi zVAAtDvYHN23mIAv?ew3^u1gi`gntB{ZeoY~x2a3X=&H15@6b>j3TSqQK8T!&e@jS+ ztV}WN8|U^dgS5DhbKrc^w|}m?<}rGnLAetR(*86Wu9wCr`Ix|0t9yYDP~VtM;z2Es zjg;xi(k;TbOX4C|@j1q#?}#o7(%5Wh@j8oCyoKayW-P9(-V16)K0CyL2k(r4JVuyn z6Qc7|ODq-RB%n&V6FWSZ_SO*tm2Z#`^YTqn%Rf00x@ue9SyNayai=mpFANd6jhYd} z=CZl-@Hrfx%;W3Guu!VSClfB6goGnvMP`fTQ+4tV&)p=J?Xp32w{K}Fk(gh>p}DM~ zut`>Uoab8D!XAN#kx-1A0PqGlPT~anN)yI2)}>sYzc&^)<3p2nEAg*6!e$4;W>P|1 zqf3jQp7@M^lb_G=z|j&@(u^bMk4%h+#yc~Xwv+T1!p@8b$DkA~s~#6?*lMk~4A&HT zJ7qHmVy$#@6jbvbwZpSl5~Xe2>oJ6~*)}VJLk27{WFjQ_-+-rsz>Pdl(!zFJiltBZ zFNRw@-iVceF<6r`&q1YBwX3GBknLn=K^_uGMu0pFxV={7^INywgySu^$n?V=6i4uK zy~HJv3Ht%^rD_+>MhlhV#Z<^|JXmLomEY7bjX`M14dZY5T z5)?eX)RTkOdtJI%7Z(WhrcPV*Koy!Q*%O<^g_tqEY|0+{iq=nJ z17K+9pO1-=T0<*qMQ9Ge+egZO+?v@=Iz^Afmuwr&IuhH1Udtf?qYSk6+bFF)lboLx z4v=0WlR_zw<$^kp1vbORo^xoaL-qEx%7J%r_*L0daTzyT*3_U#vW?|=BwC9wH^=4E zt$VaBm@-5D*vjv*E~}mT+%Miv&SQVreth=ho*50~cOS!Q&eYOF#tQ<*Sy*WrKl+#F z$*xm5xgQ)V+;?mfggG7dLSW16AwO<&IfVHWXTI>)nwdBBkeL(9Ij(sWn=+_|Pmpo@-E>3#&V zzSn~Vy?Y8^-dxg`JSe&~MAS6f+&=Z=dj|AW_n=K&&ini%X^C&Df4_BDF%0l%+~skI zv<~+dkUE3vob9cvj8%{)YD|?$5)%Hw^Q{*4YY&L=;(b!oeA`IT!vCXb7 zYz`ZTkQtG!xq&&fC;ZeZuxF9Xf2JaqYn-!WZ?t*LsE{5B5M1&G_+Xe#2=mXwkvtbF z!#n2_F%&SpizSw&EWG@i;~^Gl(CGPQ#Sru-C59=z?n|^pf-`d6h0ran=iSP2I z2uV>Xc@~z1hr%-k?Xz9tA30M}ibw};AAthR^B@{1(yF`IE0E^tT#XgcK_+Fa)!o*e zxBO_ZIgfweFAG<4bJ))DSO9zUL;6mZHH_rr8HpTcA7Ta0YvB&nDdLoYf&eZV%?&fMym|)OSD@4J66SA!TWUw zJ#El*vS(?_cmXP9;hV-QnT=hNh{J~OeBXN5Ef0~e=^YUVCgj*ipj0?8lx{6dgmCAbiEyoG%{q@G- z;3km=8G;x2PlzR%7xH@GO#GA%EoV-f^P9Ra!uu0-?7P389qPGib(`TSvG<)wGar)L zQeH{De`{1-;mPjd^6$sqTB3E4w|hkz5G_NQ@XrM{{Ko|%AM0HGbd%z*bztBh6KoC+kSVZN zriMbaU=a_A(*iR&2);1=@j*TypI&0L5#ZI3ay2y#ZYX_NVj4p&v?W!AZtQqV1?AT% z`~2g}8&fL5nPDLL7@6_bBwCnLIpZBy8zb54)%(ex-W5#rX*>}0g@7EdDwhV`_9@B| z_U@2a4}a>COp+De3 zQ5P}{r6^Vg?U>cE+7t>O9<2TCbU>b%gbQ;P6mC9qD;Zmkaz{8NzuFqFNCT3kC0pBB z!K5q~+vljzb(c#~6G&+Y87FcXRWwb-mtJ3A8{LvOYGG2078{m%Ef-(#AGb!9tlktN- zyn^h@9P%Gsex8pdEciG3?*6EAJQ4J3pR7C6G|vdH4RjkMoFQab6IHv5MvQv^?Gf*P zQsk`1I9hYz7Gs~bwm)YdUEo%F-i+$N07p%8Rf*#bFZDKhbdH$Ou735xDH&&s%M1AZ zyKgO}W+`MFRQqHkvq2XXfsJn4!Jr>oKt-`(9os<$!sANhdV6LVASZ?3p~Yd6;Knl( z&38>Rd2ix4^jz_%79!{fKXyq%#;phglYx#mzk$qxYT5H{-U1~*d4Kk!ToAo-=mEn; znni;5!+vYvyYSmz+%})L9|Rt6umpR*5Hh2>`m=(6oICKVoX74li8*jn2Rz=c18UHw z>c+?Z&5#GK{Ep?pv=G;^ofMg+XLUtJ{wGnsX5_*|DJ*E3M9@ynJVJGLR7a=FX>YB9 zDU~i;be()A0C%-z4wl;~$$G9XTSkd3kdCP?a)A|0l4qx^z&0e$chOrWZ-$I+@@HQT z8kWC8;L~4uwnS&}`7k@$SB771fd@Ak7&qQ3oPVY8t9kB*14`9{F= zo2QQWg=wC(T_0WyOK16&0eT>kl!(6=5+1(Xiwqe}Z;=YD(Ks2HsM`2Mo!oYukh1!^ zYMhXU16UdRU&A;pS%q=QD3@LQd}g++z1y2I7A)>36WQ|o7%+|p89(uNE!@E8vMCZy zz3s^mQQtcd=C~9pxio2`UGPNQB7dHRM;?F?8~+y56RPJy<)gjj)cC4bh>%tiVRTjm z)T>s@-ntVKY|wqPO0SVjhF0w^ba7{avHSwVO8O1YAVi??7tC2qJ6dhu-P{O z#=tkj7x;9z!>IlfEOD3we`3I0-`sFTuiEzAX3!U~1H6g-v!^nOcNrW0Aa^8TS-&Mg zLV@EpE19{DVnytGlCXIVQT{v{t13UJ00MChlJ|4Wlsc0;U#n6#J!Nc8+x#wiE)l7N z_ip8(8)!)itn>P?rfqKZNhhUni_N>3xP|@(1nOA5iwm}*DIS>`qaV&=`6ld{M)2V! zby)>To_rgtHa0hJI=-==ySh$BTs~S|$IBZ9fHQkDFf<67*QW~<^?R|qUf`{gr?h{43+Bz(ozc8^ zx6|vRKFetp3}L2rULGbG!Fq?Jg^22|ja8HPyw(gM z5rfJ^K55#BUpIOo92;ukTsWc2s|6fKb=r%#WC8FrFg#v~td!S?;%)QEVo%ns;X zG9-4;8ash@05fY~KsxJ(={r;LovY2UnM$v@LD3V%6wWVsv?BN;0X1eqd%+@)>B&Is zpb^$r1{os*PZZ5dd=`)qNwIi6NvFehC~WNNB6e9gA2tyAAQd;8JYQNEY}CZ>W=Y%V zDudvn`?s2=KtQ;PLaHay4hL!ICN-f6$t@+qDJ?48cGoxW?lFl3tXD9QC+bd+p8&x$ zF#KYb>fA?pKRd#KSoR+4`9g_B9kcV4-QUvosqcqvw_6{Foxrk=uy{*pKl6u1E?)6p zHR)=~vs_>NoRZ)eaU)&~inFgxi3Mgh(r+yX+X4iAL{h*v(o?6U;WuAQgi_VbbE+W|*Wx<* zTCcI*j=+lg_0!!@Ra7c};?~_S!t*1N;>9!g$&@2pnn5()Ob*xZKX`!%cm=nlJSSTg z&u;RA-rioE=9)3S?t9ci(tk5|>aB4CbiM~nkvjQFXgCbRpNVKSx)vg{)Eh-u{PQ!q zf_<_9UJ|M}(pLs?ViWGxa)y&o$W2}X=I|RDzHFSaI6&$y@Xw5-#4dtHpazp+x4`w2 zhnjxsTsmK5=Vie2GaXGWrI8oPFIAFpd%jOX}viX92!Jp>}Vc3C0{R8A(6MP5}5bhWiPOZ7aNK`}!(H2`~8E z4V2RP$&eQ@{Tjv#=j=Kiu)N{hTpJ6Xd~Ic*PFpo_a$5cqA#54GUq)UO9to1daQjui zc0rX99=0HH^sg04{!d^n$&Q)9)F-Hmq=T=7r*O9(H*nBJ}R+{W+%W zD!C79{>R-mtbrANd0sk*-nCGv3Ul>oWZ6JcF9JHQkj}DnKPtzP@oc;-L?f~P zwfrlmvJGik2%_y{TTS8A$Y*ogib?on4&{kQ>7eBc!OQ^TnAc4Fgl`Xz;(^pEPgJdK z*r4xQM6_-XIirXVa%4DyAlY&xRLA>vq{5GsgQM%hVF0-oK<)p61r5uCbj{U&b1_Zyl(214SG_6&iY+@OvB#kAx+C@a~7!MaQKtG)Zz&v>2%$ctbT;ggu>$ySjjoQtB)dw{38{ zrb+5JH+!lAD9rA$ym%qv^m-i2Z${LJe;DAiF-Zz(0C?P|m+xX-i{eaS?JbPYiw9Vh_$67dcpP?wBSB zq$pV^mo*=Ny8d(;=>O7u(*H~;Lx|xL??rSyGdM@&ISG%or{I3|Rce={o=(A&z-m+g zCS@X`2J&P8Qq2v!(#aW(8u$-tvq7FL1DU;_<}cuJSjk||cp}!-;-Q4y@9nwBxb;1! zk%kJ#SZK7cM#HA^asP1bR4Fe68I zsVi%<+C4lnGmJqW@!4D_Ya<4$IiqW zk93OStheiquarvw51u9BZZyQyzR4?cbKLy@<<5c~?v!wxy3Tx0PB>VSt@v_Bugs9z z)!mTQ#86Y8-{+5XRrTmFMS|leVWqN&BCWEr+9IT5Vf!m#=C<+S-?ol^8$=tMj1OBN zHdHSSNBp-QNAbHz_D$Jv{{mg`f76K18wSUHSN>e~D|Cfq{ryHr7!Dfats{f*--rQJ zLBFi)TR3K##er$aR)uG+W}TwD-F7wdb-3UUi}FK~X(Q=Jq%W%U(&%q4Z~gfYRhvt3 zjpN0G)ng=fnnc&v`b&&|ie&FG`3Q`Um;sq z8aFFdk!i0RB^|z!-CT307x=?{IvDT15GE8^^kr3yW@)P(8C)mVxf18GTM=ruASr?rNJ5ota+Q}r{D2lAK5uip7;6_f!T-Y39d zSc4<4WU|+QCOlAx6wux8BG%1G!t~gbC$J#2N@-srp0Z%fj_|N|UK&7sya`Kr21fb> zH9ewFWiH)TR;>y+Z}T>`E0w&}4WKA_JugH{9HL4XEw2Fdai&U2ykJZVVYVoRDNgXv zD__J-ljPEQnbpX3{9@{<(=uZF)MgmmG3hBfxBZN}6uW2{nc_f=G_1(d)|^p88zB{R zVQ%$vxkcI41YfpQko5e6s;e=Ik&|JqrKKr-=4&S8wll^`;DA#uem1wu;|>hyS2n_* zU<4}MKY-GX5!BDO5}F;(3XH9_XLZU_L+8${8@#~0_2b3cRhJq2gwY99$HTq8Jx$G< zfYbeZ2A`-FZas0r-mhkwqCffk9bHw7K&*@LwX}xhk5soH4|u@eh0XnB)(>}y2wy`U zyvGisqNAhB4*Q}Z8cV5q)aG9zgg|Wd3b}Z%`|f!8?n9N7Jx=nF{YeFN+kRLxrX#Z@ znP+4Ar6il^-q(r(vlhOmUPn0n=o>$jnr zsWY&URQ6Q?u9!qz2M!#Ysu3U`?WU88yIDl%@@pHf$yK`L-0zvi$yz`x=6>y%J;|c< z8fLzL?s>50;O#m(?l@ojOBqxBl}Ud}sZxrD4*4CQ5kVbm6iDYU+7BmlaR+j*>^p(s zbkTB-8Q2O>hIBN$(qD^~auOw>ml5)nR1nIL?rl$oJlxLvK8_w)T5`v8qEOTJQ^c$; zlc0*RXwwy^Nls>9>7HU%{&KTg6kUrNzapW=HVjGo6yw3;Jvl^%GJ}uy^iSO16oxs@ z?`ShKtl2&iqtQL9l%F8G|7vuLwuds71)!u=dG6!m1f&jKnxUJ1AM->ua|=a)#m=8S zllzi8etf!Pm*rLJgRQw0&|hoUKAF|NxQui;U)ty`TYcAl*LxH2OS_#S>gd7pl;!&yjR$V_R1&QOuJOiPOGFgAZ~97uYpd5BL1f#I)wf9 zdW{JCYaUpOqw$Sd>{Z>&aS_V(q?j?#qVWbSocIUEb}O(#car4)i4}vX+C9=#o=NcG zm=WMo)8dG|ZRk zq@FG}DY@32JHq})3y^-Nn~L8|b;M@{Pr;J5$HAdKJG;v97F$vaT>5QjNc79traKh) zESMEufIt~& zRx8KARtRSLeSseq3ob*=`OFm~5SLNZINK95qh0bal1K>Ey7wAI5M&>t3!ZPPOS?JG zYZWChr3aempc8oH2_-ARfK!$>HdvI<6P0It zpc8eKPSlu6rX8&{wO#UVMZ>?LlcGu>MeYq}8GqV_T-V_^K4vl7ur-c&Y2<(Cm1P(d z_cAuRy-7VcIXq4{lB^JL)CMymX)tGS*wEJ?@Vd^T8(*-W$i2tg`Oh22Du9JK6r@0h ze6lFs1fo+)l@W~dQo=M^6zBuz{F zH8x2_SOz*K`x&HCnbYXt)6RvB7@@P{FL!!l>Y(Pzdl6Eye&(4SR#q^ka#d6MHykvU z7PVJ#xFg1t(vcM;JWkHL@tQ?iHCry2Z=E29A4KiZI*oh>e1!r`c3(w3=Vraf!cSWl zcVrFbwxob}XdZDkMgMIC-eFAy69Lvc@+GUPpz__o@D&mUZ`3e*4Oegn$qgKxTH6LM z%jtvqN7u;4K85(#n%QHr-fAz=F(3N(7eniW-OKFsz^G%PKhwBQ@cCD0Kf#J;Ka+_9 zI;7k?6q94YE3DwAMW~w|g?Kxo5oS3%qo&l~2^2)_lbCwlRAg-vR!WI}UAN&(k&pIR z1vG_muuC5Qm*H}Thp=(2!xFiGSjLzVE7txJ=Oy8h-2c9wvorc{ZPZKb3{4P-E?fND z-mm`e?OmPz9*e^7cd^ciS+RQ5^T|4T*TE<)+AT%d`}vzSpNyL9^3vSd>lQuV$u4p5 zgo;+&2Uj>H_2aV(IpE@oRBA6z>5TjAI+NU<7i{{&*OIY36dR8=npXJZTQ=r}=wm zIOyl)gWo{Yqbfewz8_WJl;NR6MqO6u{w>iF$~-S(0^6`|T$LVfPjSGEd<_x&axrpB z#)vt>foc+tzxz8+^!2k#O4!7Jg>M4058Fqbt>jD$cFJ%*S#Eobnbd%DUG(T$J!G)l%{9a@en2n*Sc(GM8!C$xv0|J+&OWS)RlOvtqh{ zlaTa9oxRn7{iHMW1RP_QM8u$-1R}tOa}i&Q`_81D`4(PZn#=BDeqez{ftu3B{mY+7 zxk*XyIO^{O`ZVexlPu5x9sFqq@c2T73z1w~e&p zh3od?oeLwI%km!kD20i_h7*&4S+_xWTrUz18=h1~Fm%GYnECvmg*GeW&4fbX{@y2Z1S`?WsZ*A^s35x_gHGIzy zLQ`&k?S1@ryJpbh<4^fkp8Ku3Z$VRs?z%v;Pm8qskH78y8zF99gF(Q(5(D>YVXPD* z>V+$L(uu5#NJxBY=rv~fW!et;;e3v?^p^mv^ctDyByEH_W|jP1_`71C>!XL)i8S)C zB$*6fVRu6ej8bw(lg%k7WnP<8?ez(so^C~Z+D4s$Wn| z*mB;DFW##jQyNUPVK@4bSe}d~Uk#*cmKKkyt^3RMZEUn5*51841b~%zX&)+s=C3}y|Wqpu`V(7fz z_jtjy%IbJ%OZs%t1<&R4b(3^oMtkPVn3S4Rhb(ujMdyL`VwTm`yxnFOA{VtGvwpGD zYbsTJhoDmm->dMXlbM>NsdiNa<>Pnh&; z`3b8s29u%dTDSy>2g-=1v|<6YGiogc5x;Z9`X)ZEP3Gywjwk4cBJjcD(0To(2cf zaZFPWn(6Y6A>N1I>BmyT?H$IpNBG@0-rI6|A^vW2IT1!Yuev#nxZdY}s3xqJIMjzw zwwSSir?C@$n^h}4oLYU~R%1Ofj}WGBW@;C^aCcKw9M&i>_$fQJcDs^bnY|lsVZ@0$ zeV&3w4`MJE>0U!M?#x`F=G;#l+db0&!%b=9?BSIeBbMFvF7^}%Jc~BOV*cf|VfuVg zlhs_IRCn1L#!u?Z`mnMCQN#4s}0j=s3*~wRM*usDbm(EJ#_kS?4T7l`PG0akIXW zpfp0m)$kC?j^n!;cE4yu-vCFd2;$%;{vu?|lcK~$Q$?1SlHtY(9e+q;U3l^)t}j@! zaWcdA^rM#@qJH|%ef&Aop>A5=sRmgjU)dLL(N^7QgcTYYe;KFSeOwDR;+VWUT+Il25 z+w<|FnJ&|Lsk75`YVw+F@eto-l_0cxTaJ!0^YZ!0sX&DVXFru+@`KBv%$F6ymW*6h zljHVAXSs31iA8venH@AwQ`g2b4r-gopFO{?NB@3&FTZo(CAURco%By_?zS@bcF!PY znHit`*#`C5zxb1Z=#Y*t{kL8nj}-d#52C!xyMF2P)bM-KHVJghfmv7f-1e_Y6~@@e z9$DE%!<}4j98wv+Mzmv546vHfDma&xoUQ9SG_x1^`?3zC_vF^N=x=oLCz?eIHupCe z`ujzW?29^#4Yq}F`ry-x&nG~0e*_UJbhPSHArCcIkN#>=QX@X^(ppN81LwbJ>`~)`rnM{2;F1w^nBz118A};_{P?`VkxS(Z^wz3wC-R%U*Qu7sAESA)I_y zh~u)v68Fb?T#q>m(({FGf0LRIP92+xv>v1`N(V-YbKY8uo+V%40`{xpn7bxn?#Wl) zex-R@>%|FVgTgDCst3njUVkQd24F@5F3fp1r$Rf`!8H<3GSA!BJE7+T9_u>#iOMz7 z9oD~VimG3x%~GVg#^-Kl&?$bL`|tq=x3a}1qNT=iv44_`PtfEy{Y2HXt7(pka1?w< z>O-bIon*@KBS#~&Qjfn3ym+KA@4VL@{Bb>AZ9}NZ#|8irr?~Lz%k+28;p7yl3?6j+ z_@6{|0&y^=iH4^vK6P^wP2&hP%`Esgb)7g}e2O2u%$lnnz4=h(Gxx-_shceC{l0>} zih9XsXg%vewu^Yf)F=NhP-@b zEJ{gCJT&Sv@G2ihzT>Y2=^&B3ukTlYqCB*7{2(nxLx~q3N6?btuxn$FA_r&URokV~ zTe-`{hNfMN*E4X(_+3-630f=ABdab^HXz1itHRR^x1KqR)~0$d$_&y+=aIiP*pdd6 zauky-O+=6ErY4gRem*Og71@>_u@&C38!R#1EDGNAhfT}lg31%cB;4E4(pav*7Xt$E zJNTS*u=sVlrEpTFGCS62Ry4#qxPGs(kA<7dqvSJ=!*8=rZ^N%1zQfBsrc(m5#CRg3 z!~`+kO?&%TW=)VU6UlEjgvo2IToYzp`K{$PT*owFxyO|%F)vvOURxL{Yo!O`oOPQA zyraEubnR8CqPV5?)%~NEH<}3sWc2fh95jbXLp>Hb+lR!s**n6lI)={Cr}@KvvzcH* zM%gZ=pm^QxA8M<)=#)enw3A>L9-}uyirP6Y+o|9VpTYU)=&Hz4!bPYPsxudrVn&0U z3w&jo2H25Mn-mT`>nOQY_?fA29gZYG$M!VKQ zU=fEXCvDrrqvGw{*D38L$8oUSMLJG~Pd|b~GFrBNYh-F&Y+B{es{CpXK7-pp^+mP{ z)VzPIxmK>4HWzBn5=q#d2y zfVkeuwSE7PfIDgflPJucdex{+WTMUHUUPjq(yU?o*2g~+)p7Rf_;Nq!0{T3RP2-u63=~mO+j{kGnUynaSrtas z0eMp;Mh@GvoNwLsHmct?=`Z{p^Mk8oj0#T*=QUE?p1759*;!W?3cz1{TdaInbp4|C zcmBGJnLdmBR$Yg!>TN0EdW-XaPw|mcMSbw-*Jr`t&??p2^7H{omn_V)2AEL{2D+=_ z8kv%X+N-&nE%^quM^@Pid$HV1QSMfrm!HepgIhTly>6*1Y#1Ci)~<)}nu&7iy^k7T zrdA;u%@@ra$^)mh9Ntjwp$HGP8_5(GgP%+joF@76wfBkr^HRyVwXMj;YU7W-P7dWO zn@|+5?8H3q8MH)`Q8YrV~?w+V1$$lG+!A`J`|kFW@6$w!Bv z62pqG?u)MG5edEy{+7q?jZC^L#5-lTsgAF0 zs9>F%)KRmM!r_M}Sy69Sr$8-7;n|n`0^yKPQvp}K7Vcj>HIq6yb?5s_r}zC#hrS2M zx7N5>YA~2gY)@32>7Kpi4@Ek4W$oPydV-6cFB~(vcL5g*Xw%T%s^6DM653rgo@Sb} z!NP##Dx}zdH68ZB+ipW!huW&zT>Cw+mLL!4zAE?4nVHBXZeGlNrV*C|$>16cPab5_ zza~?1Pcl8EJ)fJR8V z2OpR|geR@Ji_gtz^dmCwrO+_?ig>3&DcSsTk9pKSe%qNFVnQ~Rei%hUl_%$WU4^Q8 zldr%JF=0k8uDs|*Y))Q8k=&ME>_+gt>z2+a1DZMOoeT~6rt_M`xZD8tK05rr4uyV` ze>bW7(|x{{Byn5*`fFbD5Yw`5GfEr!g>{-5i>&B<@rR75N4qdi&x7~SgO@h9kNN@T z9lan^TS-w&H8$IGzBpf%#+|n-mw27-)>`Z0=v&rUFe}b%>;GX{&GjGk?44UeqmGqa& zU5y28+OvPwxgB0w?Bx$HJyLu`YZL0m+f}eZWUhY4{yd^A$-(vxlV*{F_KA^`?9#N! zO?#Kvz?lc6GL8$w7=?3_B=*ZA=;zzx(L#{TD@)h0g#lYPS0N0@yq4ca0x}`O*S<;Z znYVgmcvggSUWFO-^(#itvJU;syWV_*J}sDKlOea~pUX_E^AfprtqaVieq{I5^SR&t zvv(d~B7<2IcdqtXX<*MFz}#cBTo{ziKRa%HVje=~HY60y1T6NO*?(xlWi@>bGi05w zn~6@~`~}_78ULAz2uN6L1q}r4`%4xpR2UpG&u+lK$8CGwcPf82#CI`Ktyd}rq%}>5 zyef0j3310ckQET=kLZC&U%CgKN!m6)`G^n~5X%d{60cFnczP>2;boC-DjoSE-kIy2 znZQN9Y?kkSY|_mZsGO1Mz#0j`+rHjVMv2 zTxBZ(;>S8b_b>u{()#ak-HO}2jCP@e_qn-41+=e%kM@&CFDIk`WAI`p4gail8cI_l z+d+{~Mpt#21%}_O_p3+8G^LBgW)uC`u7fPr+AmnI&om0^>F(ERaZgXrVUo2dVr<8^ z{OtT5V6d{|`pJO3u@^nlIjMey6UBJzuFd z7*bvjFMQ6M*hKN&)u9Y98`|es7!hAT$oa)bY56xXrbRGt041=_x4}Blq~A8JF9{WsTqD9{)RDG4B)$HuEqRTb<$j#IQJ5wC%HMpo76svbN{?~;ctiFr8WLm zfxz5CTo(U)Vy%=+*AygeR zdhw(kfd#2{6bt0OEq$4zJX%$Fe11>;regAV6_X2*%pfF21%KEe5H%klPwc6#^84Wn zVLn3MuZB~QZL;08#=Hh zuRFbS2?stt#(YjnYl*x5xNADAd|s%BI^7KnWc$0wrJfhCovH?x*M@qVB)KZ$er-0R{m7;3 zHEBiL4e_nBUogKBvUIfVfODoW%Wai4D+ZXk-V9j;tbG@E9-Do~PA=B7?!A}kWNGgq zfe-Ki ziXegtC?z0akrL7&DS`q+cS}l(bdHn?2&jO-&>&qZ%`kunf^>ID4LNkf%y*1lKF@t$ zdj0;k_kFKFZVn9RdE{FAT5Dfx1$&=m^mna!%+xhR5IOD2&nozOIoEmcn@#LvRUNC# zSwn}~FlnukG-=cKP%SR0MTX`6aTUyGIIB?;t^pG>*LPa^)%Cu$gwev{9E87hM4*nT zNl%oOBCB9I3q$${1z}+&gzrky6+>6cNO7+3nE0GMDZJ9^ne3EN`J%93AK$*ts83cg zxf+SBEVo8R*Q)8^yQMJ(W|Ml>Uqv@__0o9L&;x7LJc)e{6PbIjp~Tde;f8yKvVvA~ zWAnrc3wv70q&x6=pR;tH$HPOGkzD@ov9NES@r(G+;x3?I__I1(;xjn6Zez7(oFXsM zEBly5Dk|&HT105J@KbBkPPAsTLF1y4Pv$Z?D_3fN#g~;Ba|~dqclQ&jKw1uU}1Gv5hTg+(503%iQaxq+lPNf!dt?3QbyMI?H43r+pRLBoFd(HI~0j zej<4NIA48x6i%pq&_z0D-r00=(D~3fjilwf{tY?c&`}rbIlFLdS2OOH(#J32n-$O- zpUMm7Vq2{K|4NBpLqa<@@_1ieR;fS!K!O4 zWtCJ?%$a=lvKu8sO`9+mMo(mCxA1DpRU7Bt>Ma{Rp5R){vcPl(=V&PLlOXouow`^p$XZ-}Ryo_i+`eN7b+R z_FZ!PG6!)%o=7TlHN)Xrr@zM+(UF`FKWvi5h;(nI_IXwnjz0CN4LNj6bcTux{-_0d z$}fZ!`25qt@9r5~!%ag!673D1_a=gACWX1XsIOc;toOc;sc{sl{!)W}K|g#IN$T6- zSjg!SGaOxoj#2?eI`fG?)q!;NaC$Y-^;C8xJoR#VXJ=_ zcTYXLRBKP2vwgXfKw&mopf3_B!j*c;PZhZz&zPW5GGO#t^I$uAqN8?N&q{HORvDcV z&{43L=S4sArr79yGO^ttzpD<|-=54{qp7nHx3rXe#yM^)n6_4jTls<3O|Jmvk@1YZ ze&^5$Hqj7kstO1mN@JmhgniKFsOj3US@}fgPEBEE{}{Kk-u?3mH}fdpPu_w&fY-_& z35*XWIC-w4UMnQOfsXGOuT;mOn0F#nKAjq?2wofAe%qUfTHG3PD6G9+n5S}H;kd*x zy>g~T*2G64I&I-VYvKud0^Uv&yN*%BTYa`^;=Y{Cd@@`Y+7W!BeOd!gvwyoL&68@v zwS1iYdu9^oCKsulT*81H95kSF%NH<@r?7jx@x0ul=_YgREM4BSNE{`}y`;sh?3r}5 z0aLu!G~W*ie@^`-7Ry?dR1OmUz_Qp|X>+P{&M41eoc1)~<%CJ3dX}$%hPW{juMwd3+4j*VT=N5bPSM`fDMwc){vM z@4Fx(OuVVN*H^3w$xfE4T6>nBiw}Bz(kQh`N#ddjw*U(V!5*I7Z*Sj+Bdp3 zG`*cHgxsR^*&zyL42r&CM7jW5A2q}5S-_v_L^kwhc7iB*N|t}J{T326==E`PDQ{t~ zrc5(Xfc6#UPFE!c74@TeZCheB-oui&Fuuv=<+>Q9sK6v3}M%uCoon;h5OY>zjHW+&DJUbuB@pc1kxW9al zx!|3ub5*++t%Cs#Vv>1oaCJ|VOy0)h9XoeXiM4euajK`Ny&9_!9(Q%S2%}`F`Yl7I`4M3x3V2KM+vN2wH>pv{-MQJCov+72S^iT`wF;;`Mn2+B2 zYRGLR1|}GZp4qZkMGo6`ql(dSe@V#xkiCIaobBfPE20)1TC*tw^B7x7saRiNuuME0 zaAtHe^l{2Bz9C)US!%6VGr#$me)wUWO47r)_`;e#_2S0+k$li`LWs=8iuHu9r=iH{ zDPo1bY{PbnNp|{CeN_7O>ayxHq=g>|C2gh^*caTi78TJ>o#Ecd+~p@7oP1}FBjSgZ6skqWoUh}ZNAQtE34>b#LP0r6Qxi=N_AQ+9ch&Z^DRhtMFD=naampf6EFg1`XL6sVD@sZ8548{VG@foPLdJnW z9M6DJ#xUWbs0;CWR{GZ23)PYuJ}9-3ax)NS2>zI2*|mZB=yGigB%vG92rb#AZ!1_6 zN-Pu?xe_h!>9ohoslv5NLhrM8n*^(WzP?9SvM^nU|NN_caGi8nZCLeRLb(S-$W^Fk znhh98u!NC=fREmdtXu0JEN4RKj?P=p&vf^^kL$TMJJNBtv9Q9f#uKuaEv=m=krX*g z2GyHi_2!E?=Nk^wXgOgE56V3GIBAuh^bE>G@Qhf^l(m%Ee{(2|P$E@nMn5TNzya>F zeWpWy&5O16$$3<@ceN%Gkm%~=!O`to@DTzvhb??Yd&EY|dp|?yBRvOWlQi}R1+>KI z%suK8BF#ee;T3hOEbSIDS|qVTMUhV@MOVMIwE#k{>%lEThP?2`$9mFwIeN!@eq&pY zgS={;I?uKFB)6{JFB@d!+912)UaT}t(c7ID`J>Wjen>YNxB z&o2b0vgnM9=aNXBlwEz2tlJl$6{kH>)1@@T1S# zPZ+ZWO}ab~LAufPk~u}2GF>rS1kw7hRccLVeF{0UQk2vc z(ncbHYl}>f?s%wI#MjL(95BQ7%J^&sVP4NyvTuNn5ll{fAY27WyT!_|My#Ok@z5w; zV?TIgi*qz#-3ciozFWAWWpE)w(DBUaLxkG1y}C27ufRknB1Q~Wv8x!@pH%iFGE3wz zd>K19KeN=a5NpP=O?GTpVwR%Xqi<|G{ZXh~SttR4wWa=0d`k?Xt0`zn!*xw$DuZH5Q`jBSspAaC`rl|w00 z_Z+IlyK-nr(bOUnj_zhdPBJX(eg6KM=tCXKgz?dX6_Mk{!Gytmn|b$ST13pE&!AqB z?4lT8!0FB3;V7zWc->UmYkNOmoK=uRiTbDTJV*8dZ0htCCeL6AO`M4mUSY{Ehy0!E zjo)XfI&$yTP@3~}=c_%&d0^yTY&Rl{`0uyLoQNd_9D*n!oj$x*AVC=b>ax2Fb+QR^})ndvo`lrOY**tgqyVy}_g> zNT)C89FB?+k?grqA7agt>p)I&ob70=$r`MrE=EG#Ur11CeeGcdq*y8)?uRYCLt^r z)n1J!o^`l1R2a6{kFZ&}W%_kA2$vpH{aV86Ui?aV`N32S9^E(BtaCV8VWD*C&FHHQz+ zu;}La0DO-ZXRC1Z6)MyvB9sM{D|;%~>*VBJfrE8aR`5#XsJ?w)-QN*VkGPc^M^-ri{q*SIqOzb%~$M8g^i|DPthK zk5-JBUt1YV4Q?vNT2>Jnt_`!kUM*8!{H_{i!zixgy2^Z(v4ml2)y=mfYwi8HV!X;o za$6;fJD*X$YNvGAv(LfN27ZAe^zMBuTq|Yje!&*5$jQpuKJ^WwNj(t>aS=db%G{A` zpCM{(nm)aU<0dNDmv2=2`5^hlFpM$xMFvG!BO16B$@UV8@JcFi(6q| zCk^z|oR+D95f|yYPyr8xyRF(%Zj_u~k@vu3RFAhDCKhp3D7m|S>Im(vuW8yG`Xd$w zLe}db>`Ca*L4Jujwr)1Z6>p+}mTkSIIJT%}K`CK7b=bQ08NB_dvH9fFEIR*fdsD&P zvI!0SmXjjtQ}(SE6g0-|&k}qjf(g%Zlk#y-0>OxpIR_N&-xKOGzCwlp}ZUC3}9pjrO%# z@x~uQ&f9i6k&Xljs&wE?^w|sR6b3!yxv0Rr+RUscJ;FYgC&pX$xdFYq$Djfqt%2h8 zn+Yyfm?qTTkZc!E%`)8`wo!6I#W6Ky7^{X*h}KR_@~yr9=qi|VuKdV}JXX`*2jGuO zJ3J1?Qnh4y}5IC!JsUFWGlB!nt+X@+UnoXR@9Z7#8i>Le(Vo$je+t zOJi6X6(LCI{AK6%oe^M)?=yuX{IV6GpO5|Wu;%TmcVA_RjVg;v)ac}>DhTS?{>7ZQ zi*k`$&f501@14Z$CwSr}nd=&gLrF-HgATyxKwP16Mf@hS@w-mS_u{z=(#K16irT(= zitQv+L0hXu-lScL?Yii`Y|9cWG?nHwO~LVAWPWWFk4~<8mLIZfa}^;HN%o-|h3C-1 zM!RM-^_^X+Aep|;8`^vHKFJX-dQzHQBKZ9)8|TFfQ%j!ZlkgjJ3FQ%zlEe-Wf<0{q z+GK{vO+p3tGg3=hT^tk6O|NJEbQ&#_p;m?-PPQwaB5V4M2mZQr?eQGU20S-=z1>DJB7@DhOP) z|S!iBb8UVOZj}24SX$+u$NnlC7WK*Q7!vM5NqGR+$YhWw0hvg;}G3 z8hz~qb~{BvzaL?sO@A4qugy^1oE%Ml+?}n^iapDMcW!5{5DzbNZW$QC2}+kCNn5rB zgc)z2#tUR97fKH57Z#wU?{K9u^xC-*ho2|2QP?)jM@r3jh8yTiF2n>8e)ARb9jWLZ z+o|iSa9VWmm>G9-!a);yA0&GuawgUVhWDbn5i8nkbsryp+jE3d?Q9wHN55sX9-oIS zrbAnAW()!kMEp0ktfmf)Ni@eAAU`T1PUH2Ld|52!ta+6|v!!Gv%g6P-pgSeL*RoT% z)OvXGOERVTV|;&Ao{ZkJ!+o(6dlwK;mYvKV0qcB?*}K^XXGjUJT77;g@GP~H=4fb- zQ8YO;<~G}w$NU9&=rRL5o=s(oLfI|iVbrG{xJ|RDrk~zwiCkX`8&~Qi=b9y48ZOgA zB_59LN&-%vUpYk$!Yu`(Gb1s)NBGbf|b$;3CdtyGPWFDCs`GLHP z!^nMZ^f(LE&OKybi_4tgUS@bpDQC`MZnD=FJ^e^wLHE*^6bcz5UjDVwyMuCiX`G#C zavzwPGS>qa>Z+k=m}H)3e>vCT<$$2KpXG;NzfemisllFfDfcgh&MPdQPJLbGrw1{MC2+=@Ws+3 z@1q3NS}{~hCXH4{@}z6>lRT#&lV&>eV2TaLpeCleNNI=aKA)_*mo&uGvGSsJrCs7h ziZMI2bCr;p_lf)y{IeUDCVcU#2x3;t6Rx8JmTpUmey<*$cZzCNclya5>>gw_o^F5T zT3WF>y>&FyNds?0m%FLzND3#mj*CWS6HCI~j`Lc12<|Sq9em7^jT#<~SvW+KsiIAG zD3hTU_09?C?GSWrIji7WW(}cS(?Ppi=&K&Ns#Oqtqd_F&@KM*`!;W>;}O) zNSr`HhF5hVi|HCq-uTCnTk+W}8VRF=pvyG)FV!Y=+%~9XvilQ?fqUf;LS>69^r1^xMr+{5k*K z+TFNIMZ$PDiiZnG`-mYgv*}^lU97q%S|RO59!~4R_3%X4NWBw&Rd)CjnA`>6ijB(6t2+6_B4wcb;f^uc-F4 zsBQQjMSFboBbAh`@o)4nCChdqeZL}KHWwu~5uJtPly<%aSZgUFZ$T|%W%KM3ak4_B(7`w@_2j}}tph!e#TdN9{YdQ;D!xd+c%dZp$m1lR1bnivt49h&l+ zAim7EwicSS1MZv1;;m}GFCZvx zX{jAyTGy0$aVE$WmYhFnC^SA%o6sLeCis5p&TZPX-oB+RCdctejArD1S}-SHhD%oi z1$*f;DDs`*n|AxiKCS7OT)4+pkdRd6O7U~a`CK1tqHYJhCY)wvsP@>RU}Dg(O@fG< zGf99rm+jzDP!HEZ?+J53BnAF{z~u=i8&wUYvH`tb8b8IAft#+v5Vp6`!jMRKu2Fi* zCs&hOHjnTOiYv<)jkJn%A~mgVLRK6;S%vZn(f9Fwz8Zz@5Y;TCZdIp4JLRDoWR`Q) z6}?sln3sdq!wNbwEHxK>c==Jq84p+1Yzu-#R}_g2nnsTSBMhz5d*G}6vB!33J#q`f z){Gh;ddNF5QQQ5^_40eq0cYZoYacw@g^@x9T3O+dVz+e)ZtSRtoTlb`{Q8M(ikAW2 zIhXv20Do8#pXj77+nzRL7W2U*NR8&U#>uUqf+sIQXTemdCpg~z=#&QUO55h|f42eM`Pn8)Sc6tlj zRr*kr4yDWNOpUXkgx-oSFX}gm&Sxd_#{qV@AnKOZ_S$l_B_Npo&{rvfSmJk+V7EN( zCyI5-L=Edx8RwV>`1e0%h1IAphQc*(zq3r|{CueLlwj*XSyF|ZqJMU3ho2qPOKIdP z(py1#_EK=8_ai$^%2OYP(iBy{A9BbuJ1Ww>m^(bhzk0I@F!gTu)aH(-^&{+iTPD== zhIw-ANEYLXQ=M=P?n$(5v0TPu{8YID+(FX{@7NY7)b|DBoi?d)TkcH-Sn(v#-m+Iq zSsDbj=;;i;!+v`H8D3`qVR!qxI~7jj^(juxZ2eCyEPDA6wv!rIc^yl0?&`tZ@PhmY z-zfchZ!wDD63v;(MBCm5I{bNi*kATC=@K{{W{7dyslq zT?5fI|H7Q4cbgawK$FdorM|NX>D{b5gJYu{g-Pr=gWE${(MnNmTbMR4?0KM$jFD5K) za*iKw#lay#x}CzS4G(l*c^=Vjb-zkh3(^=Lrg($kuXveXyTzK2hf`(E+He%VT=a7G zq?-lz=K3+4L#&WdryEy@=q;ujfYS>s?ipXbaWh$wy;o`q#f(RRE}qz&kRLmWhxG}c zGUM=*@Hk&B_%!Okq+?85K>hK?08P>DHG4-NToPu-(+(lDrrtQ91)-()`uD|@y%)s%E++4ke?z0p!W(Fh^# zf5T zKzpYvg9%4^qw{=uiSHh&kBz(CeGl9+={9)e-^+e@TGDQ=uSwo(d&jP4Vla}ofy$ip zu$0LO-Tu)+3Y;md$NbIN%P$y|330wWuQjVVuyRc~^QYZ0uhjDz!#gGZ7w{`zRV>lOu5n zF8Cbc6}x}ysi0@kPy`H=GnzMXj5>`TKd;@_)pvkjo#+ObQTxr<>2YkgR1)U703d^QjBTX}a5ui_a~<;KlrW z^pvGv${Kf8;!tmiShkS#Ut&Z#5I8JCo^&m6gWR{VlPOb+@xvX*tjGP+r}P}AEA$mx zZLk}}gGR8-pAd_ML}r6rsLI_<-%OcO>vQCBbs#6O9ng2b?o^F%z+*wQsH7Pci2A`9 zAcNP%Im#0+A-jUsDU%=7*2zbVPNtj&ICCV zlX2?vS6S^xfVFLb$vm2K_B8}u5XohYRb+;a2zHFl+={4BskHvoJU)Kz-4}f7Dz0roERj}|ECBagl8tPxJ zXfqZN_K_Dfd=9cdUr`Bk+rLPna;Is*hq^bp&UfwFg`V&)ii33Ghf|?KPVEhQ;#}gl zji}s%=8pvKfLwW#q{0rX^DH0CTPd8HP9msJjDW(?%A*60{@u04Ye6eSgFl9m&45Ep zOk@9U1djwQLp?&cdFo00kA1(z2{vH%MJ$ap!$;7|u#=~#`p$suM77X5!`$V;S(DKD zqxkvuq5GeU13}LAzH9eop5;&>i!z@zZMdregX}FmhJ%H4B7sC=JEQJOxh1rv&qlO| zOWzQ;)hU-qm*qZwEH!O)q>MGkYu@6=eIn#cVW_A{2AfthT*W80lkeHRG(mMBn~)EoKC&M=!{-iZDgU zZi_Cf_hw55OoLu#emHq{RfSE-RF9H_rc%^*miiN>8ZPlOYuLr)GXG?~ye@mTqz>_h z<{sE@m=ybXt+#Fq%r_*B_lPm!|Ki1zfGaEQ<-I_QgGcIaa}1vcA#RtMuMj*qBPGaS z?IFolt^6eP!Govw2xMY9-W$8G>c^DqHGKQnRi(!?7gb%Hqg50>?68JVXr;g(nsR|K zr9eREJ%6{~Fe~G5$z97g?j|zO&gP`rn%vf}EM!v0}x4-V==SVs! zK;ORKW-IrakgGec3)AJITXvyZa+vz7*lxfK%IY{Y0(RVTTiIoH zdj16@4^fZHWiGZg`gC$@?CYth&nQ~{^qfDC1-DFC?rMMp9$D;3|6QJwUY+@)4Dr|< z8vdIGjq0a8D$H?N^Ce2H;jy4daqK&MULN^`dHY{n0MIF=-~-XvekV?3rnecW_|TWH zS4(&Kr_h$Vq)g%kn1tL+LnV+MTiP4{Gx<#H2>rV7)S11oX41x9YXbQvz3s_? z0!5Xo)zKZJg_zkuFcl&kT|^-!D#;3&m^XxRE#tef6gS@vgOwHNMcs?2FuQZk+affr zR=^x~u+h}tF?sf{x5N?leJBlqp~@qK!unpv^*G-Bd8_Y(OP$92B24ioBNUwGZZhqU zo3bF~f7k+Dzt%>~rWxDL|2h$J82)&I7V+bXHfz*DmbLOMAieGp<86NUynF>j?|r2U z3#;jVA}JG)kFR2TLk9X+MBH^2^yA1SqljexX&7<}NtUdGj5M$9d&2yVcS9sP08ej;J)k^7JTF~|Yc zkj6{>KT7yNfBEyfewQi1=%&xKt^RYTOOjh4grY(Xnmb@e23F-R*vhqiUWGM3MOR zc!BuI<9Pv#zHF6DwJgPXam+!475Jbz=)E`Y)-I0TATIVkc&~BVC}HA(tr=MxALtUX|&(I$|;Dv&Tm(?A&}v=U>Rw8#@mL7HSk%R*b59w@c3|nvgg|0A4BC8;R%14kvYFS8qcx4G}R zvlPb&fyE;BCPphW(HP*&dGBqIO1!__tJH3UIn+wG+)HrkxFay>?UV|C-Mv$TL3?eD z*c8QC^=8I_;lxcKyyC}QN7>Vzo^bscvfRYNQ)#8x%jwtzH#VB+;|WL^ zV_P}kw_Ou~%Lx`^==lxQ%wkWsfuawP^MRy_FTL9F>Aj6z!Hi#Z%+OW4uIlzyxh&@! z=22SV7)8!d>Ob+zF{<^|M84^?D<8K26N%bgKH6QiDXg5QW_tNL+iKId`Smrc1voeLD`HH!@G zMu0UqKZ$IQw=6mT;+MDeZzKPH4w&{$9~msWt)1}j=iA~)4|m9T7ba_KE76C`iB3j7 zJN=4sG!8U(-9HGS_h)*mb}{HR4D8XD=Z&;S<+Ug650O!Jz4n(@sR{CEJr`2vC%o49 zyjDKSwy0V6SF7sYvIbR*1mud|_bE;ozryVMoxz7qV$gf$iZjFty!K;5efc^(>rY37 z-ri2siqbrwR5O+uLOXWxY}TLWeD5tL_zT?s4b%64L-eLbqxt*35}t z^tN^He{eZrO7`x#yIyz35eea?Koe|$`u19|b3`Zw`%}jDwd$=7Zl79Yl2=SzVy(cC-CbU(Wpc7h*T-H0PC}JGY7c$LajHEyC8^BM>|L2$!)TO{$j_ zy=fJ0jVHJv4{MeYzcmL7WkZFVvGSP#c#Z<2aKg_rZGFX*^2dr%Bzmh+d3 z0T?jj|P0n-B@jzD~$+B2cKHOy;+Psl6OYCS2SdbrA45+-B*O z#Us@%wI<(N8}WIos9b-P1_9(^zcU$Lgw~a~jLtP5O`BlyoCsc2AN30$3g=dmS_wcQ zgtVp~ZqeTaG&2Ni^a&OMp0fJ(!KYRZCsjd@QJ&7browKht9uS^<1R7XxV7ac--)5; z3OJ;%_z~HtnM)(wUIe&aXRdf7FPEDl%Q09N*pYK8M8F{0 z^~F`khD&pYfuym_TD}~ATp)>?SclY8?WfD?vEcD$b1l(|xeOWip2jmSG`*rS-G=sy z9k1VW8ql$9bkS!?ESb5U|L_=83|X3;i!nmIkJ;qUaO&~#j3;!@dtE+SvUN{HqTU`H ziV{8Z&Dn1s3$3!`fs~M1UHTeE(+=X536_#9RbWHui?=#Nn5t!R>jDWq^5~jAnYX@D zHCrtkweRp?)5wj|sMVOf=cD(@ELf-1IvR-28msQK`#oZZoFzU-uEoxas>MerIMZrC z&%2zmml#2WZ(|6#;zg2v>uwIJRsC+nFVFnnF6uLN#ajm#0%>DO-uB=esL`?{zFA_* zH$;p)w3{W?XGSQe_1zz?dqEYuKOgVVrXovAJ{Sgs!&mZ)gT&<3$|PUxV0SSq#hp^; z%PNo;ZvgI2Lm&aEi4PEpP#g6WkwJdbuNQdS*Nu5>25$4Xp~kqgY=%E&sI44FsVTA) z&)j6@>dug-w8Rg`k&I-7usVt1F>+L|OEn>^`*T^~MW+J4E*>D01xCJK?o+49z-r>R z@*EGTpD0Vr9B*~Hp^81W<`uiQQ=GM-$20WpU8(mH<;S14vdFi~-@3?cQS4 zJ8)^kkJf6%3!Io~2LN)2sCX73&Xsz7>V|AMZ>Q*Z~>RrL>`Zs>zfsIVWQ!)UcfvC!1BGbWh#2KO9l4l+r%*C`$$T0P}GSTcyFZ*W^KD2L@0Rg)5ySl4X1wELHv4}Bn4c;+` z?2?M8;$dv&;$9Y*0UF>&B$)Hl&}b<3g5U$!+(3LXVf0vKIXNC z_z11t_)60b5f~?P(^r;s#xtkj(2sT1Le(7E_rl%QN+OUFzex$K&qxmIh++ww=iGY> z3S%f1X)GZ;cS7{y)^4o~6vP*|h>lX9{`UOF3b;$|Y~ydrb2OjVQq%z*-ChJeS>`rS zYe|h%5*uDfA{N^>Rs&hB`N^63IHd9Xc2E5;kBomW(^peGSyX~@M~EFh9y?h`AQUXM zA0Oeh>S@SHUk4aSh$MpEI6htzvoIn=i{Fg-cLE1lz z&&mHj;muWWdNKieQ!EIMG4d7S7)seQaHa%dR~l zRxvNjSzh09oNmZZru-2p`yI>Q!MgqCyXCv~K=!Qjx4lW|WP;m-hj}52^Cd06-H;yv zB=D$rdx2VRan_F2VyqSlO{YEg4aS(J(Ig=mvb#DO4{%@xBK`U($X!NbPS&6T$xyV1 z{veq|S4Xt1ZWro&hFH~1xCkgJfzp|eS{^KNTN0@TQbsY60Pn^a1zBvD)8#EMRr~So}+zBnCUQ&Vx)x-uvTlw}@pFP%pE% z!klJJEk!MZBupMHC1MLK#anrk=QnR3kM$H0zOx=m=*yA8qw2iN{f} z>o(Ae0p-WtI1IF+Lekyg;aivbcr9Mgx@zmoarN7FhtvFWsQ>q#kwdTmA>6&@4C%e+ z7w&*_Xmn57{n~kpO-hajSF*%#%N=kh@D{2*D6t(2rDRyMLg+WhZ zFzNW(CtO6Dv5cj`Uvuw`{QzRf$Zh zIQ5!wjW0OaMvbc>9>w~&NvaG){ex6Jv(4J+pLDNv-KR8&TDIHp{NptX!A7jRFX}82 zA$hni@y4yKzR`RYK_@!WS7;#OBFl64I=Erh#QM;a9Y=(+XL5@AU38%}juA!f!wrB& z;&RloGB2n%%lJj4=#l{)sE%)1_v~~gFkYxyP=ek-e`Pu$I~xQzW#f03_wAskg@Yvn=vN5kws8;CwUe!=0`v~?*~ z`Se07XYJVV#qV~Vceh>@6B_JL9!Gw7l$ApOZi&VLEWtG%=k&-4iA=Bbe)F3f?a7#9 zB)15Qzvhx9$gOx)s}FJVCjAgsSRtWFTA00Hsw)bSFw^v-w735=UfRp#Md= zHyPt>ab;X*NaZ#(3Uo`~8vtCtc!B4 zQz==jBgDnQ>ttsTvOG|5kCcdqD>Zt%>8g{$SmD`zzx_@qnp79+lGZ7X85=gAw!>~r zJ07Jz$({qqtYbFs0R|TJvL2AtZNTh)SNc#4T&NdBd|2&Yi~<#l@)zB&{c+)hu-vm? zP8cVV2-h8-Luagtsff3mtU-7H%M}9ZB_}|QJ{bE69WJELgsX(43QV|v!P-4!*tN}s&P#Ucx$5796i}p$3NI0$MD0R^Fw+lW!`~wz4D8^@THPTZlmuwl z7uZTp#~V#etGByw*Rxg9+r*AmNb%F7#L)={gpae|-Mhk`U)SI^>0_y`JS?75Abk1} zo=PVNI)FVJKL!e7^&ikoY%=YvXXPNg^olII-cHS6{TY7ZcB zQjM(4-az)uuTaPso6LL-lM%V%%;C^#dCi2pdY8gbvx+$*Y_kmH-3=5OIp~(zQVo^^ zkHz3T@J_T_A8(S!c7;d2pz~xN$O2%kcg(Sgmf%Rx4W-|U@Y<_LCXH>je&BKG&l?O> zVsjjqCllaWZ2)5_PW<>gv7>-H*xh(6q#|(8B^`ym9F+c0&QjsnC5b;57;R%bo#-}k zX9V%#$@lsSU#zK1P}2t_mjXsV;6}J${e`Q7UE2%YUqHcJq17YzjYi7$FoB6HZq=xC zfIJ;B6w!5o!Wrn?N&8-+xQCI9alqA)=`)G)14Y`hr*=q{?p|8%)X*{@*P{6{+5I^H zKOXBaaKwNT;WOU>W97fK#Ltg`*TD*teSMsG^Hp5~FkVd*MR)otXlw@Zl(Lx~Z92cR^^dq7S z@wpm#jMdTLBwm4dV^D6jp&~LYZ-71Xh5K_DGae)G35fQVJ4{yi=4HNgZOL{GsI?pZ z)R^5>*(1S1(p_i(AG-Fl^c*2a*iSV8SHOG?r?ufgZ1-?uK$^|yr1bxAJ^yQJpv*a_ zX}A?Sn+Jd-*1p(Y>Srq~47BGR#;u{ho_+RN4<$>O3ScJ3c6q25eV**Y6xE`IIeb*R z_{eX1O!?urkge|0Ui$Hb4Rfp?Ee}pRx0^rJ49^BvTuLK5c ztOxL5Iqqd9pid5W=YQUa%r8< z_&CVS;5hCw7?&jK1;ctGT?$KW28DA{&_|P)OqXg*$~_{XhGDBGZFib(dLyjevcm&L zv5uSG4EdP+Ldt>Al9_vrZKN4>n?|yyPm>Fa0|9I*uF7RoA4u>KbJD_Abj9PQjasI{ z9b|%&;>zop2=NIIwExH#VsTsGud97uJ%{yGDW=rxafxK3z5yGsogTB_G<4vP15!&I z`0=kkkr?AE4Y<+2#rwD`rGoa}(q1E_QNpLStA7khO5|XXXX&NVz4T|#7)!_1rLcxT zT#3Lpd%ebjwOWiX^7TDp(Y}Qp$;zYN)T8N8&Q~}$?2UNucmsKrvDH&&iUqh>(OY3G zn-;kW99EeCImozNQ?J(Dn@(2lcz@%m#_82yQYPwt_Vu&+$H?v_?+jaNF5_n^DNGv5 zvUz(BRf~*BUf9&2%iI1B&F?2cZcid-u25kReA zFB-*CEG1*x%HLdJ+2RhhCuNmbZS{(Zr^Q*xj~VZjulB4^J4Sm}R3od9!6>5(0 z7yehM@x$bm4Z?trx1ZVleWvGCuyo?8+?`3HqYvNA^M^U+(u%MswVeL1`}|88kb$Bj z!Pl>rI9KI=8&?uP>5F?-0E~2D&R`LX&;EE8K*!5Oe#_DH&k{a3+*uAIG|A(T1Xn_h z){snmFz_UVoUIMG>=QBk_G6EMb9=rI*wn6oPQ+6~OunRPfD6gYf>n3!&t9vRn5i)P zb{Q!?UhA&`(^euc(XDdM*@6MzS(cBH=Pq!YExPmD4sjJsuw}2WmQTWf$sY$y>bNt+ zcR#cVGFQA$6dGvl82v&a}0p>ecR~UcF7||aU z-H*|!fNJQM=DJfI{y5ejY9%Tb>+Pdaw#WZ7u>lOgj~OVM=Rfvic{@ z`SWKa=3x0$=x7iB!JUpBa4`0Ye>^?s`ui(>E!N9VVELFMh37@_t*nzn1hq^Al{u5;$dw&UqPrtWq#eJNNYnSj+S{WBd zWVIwRP8{lcX}iS4?MYp`KdO1ipig)Jp`@o5J6=jQJFe-~bu#|LGfCD!2jCl)t!s|GnNRQ$Vs1D$=#!5hw)K$4*89Q=y2{@~@nW|NibNziDg= zM#2p6RlgM{$WWn>Fei0D(<{uIFmLl{E{tMFs&n{Bm2hnGieMdM9i!q=cjPe zn9mVjXJ95FII0O`H^VFyq{4FLT-;o;mRu8SI!_?J&$kPT@2;&T&7#*tWx2X9eMRwK zk6kD%8XzEHka%|LG!E{$|NT$1ngr-TGLB11)d%5vBl+gSo4BH1^RKh1$zUxG_T&_h<3Ou;$uBRuRJW7Tq= z%b#AnNk*bDtAicck0IW?ck>T(NO=P0@O_qcQTgA+`_IGj@8bP-SpGeE|6bdFW!`@Z ziJz+FUxo3nw#C8yKT%;YBkCmSkF<3^JXX=Q?|I*&cj^ZK>Nww4y*IHPd?Wk;e$DKy zL_|KJNDW}x9134^k(pPM&&YY{o9uQqZJFNnQaWNch6m2*8|mdOUTS)Mn3=pfPC z46zH}r!R2v{x_8I>ml@>u@sk2ej1QnvLoP#{d0DPSY};)DE9GP%Q*hQhDFND8w8Bp z+408L1FlnD$G#&NsCgr{lcPkyDBPOM z2VO;Tmr4H3Ke!uZzuh>rnsaT?1*4e28k3Busqk=gs)-G(km<|rcnBC zP@GZ%p4ankq5&Xe<(FW2NGuYuFkj;9kL70ice(#vZg4LDJ>9=c>;FfDi41FO#bRcB zPcTzbEZ}g5JA-&}24F)Kg0^ML4Oq587T}Nxh0%+PA=Z4>Mc1JnKRSB%En-;T@lcqamrri%0v+~9js1Jn@%5;(;}er+gw?00&L0c-SS!=>9682VOnc; zncgwkWqn-I^wLlB_kU>3%gpzCRT3gJjhhie!IPJ-1 zsvC3cm8v);tcb5$xIDCzxTdftjxlpTX=8*W#2O-wP>Z2!9-S{C6)T?`VrsTCoB{n` z<3xPjkNCZ5xR6DIwMu5V7fWo#-h@~0wHH_4qN-+yB4`T6h-T>j8@cEs#!hxZ}*%3yrQ60B9<2&?l1^ghM^Zpvda2J#MYNaE|0i4q&EaB}xNl zm7Qv>+Qa^SsB)$2J5&{wBKx~S*<=nWFx0?LVn2pz>Bo&vT3XfEQ@ECYw~Mm%?%dOJ z01QR_FBo#xm`WBq__qC`7VXj{28h43Y)7|`n?@(z@vk9+-%8R@O%s4&dM?iIleE4& zSwxs&j4(S!i%>KYdZ=KEi8PH6<@cJo%9KJOq-nQ)=RD(Cfv0DGz$`rpt_`UZz@ce$ z9(wdbgZ^-F=N?!0|Hs~YM>V;0|D#7l#e#?^C=pOORH;%T9R)$9D$=VUy+nFV5KsiA zs7R42ARygHl@?KoO79R#1f~?kiGfe!M8H*RtJIGGC{oD)isWhsOPnDrv@b{=_(6au0m%9Z0(P-d*xoN z6lAWJhjk`*q@GZA(YxzSMn3{*06>(qT>8wTRmf?f9bl#R7L3s=FWXrgor(GN3r((6 z%iGJ&Y4|gK7nLd z^Zic%JW*(n?Q;V}=wV+V&6Q5}RP0`9C-;ZQ{rU;-0W!5W$Ox zn@3rL<=ywBkDQ7?5&`E4(*vI^XM2(Mp{%NUv2nwWq}r2qj&sy_9Y&O%F{w)`FS?%D zJIAZCW>w7~{5El_BZI~@MowCW5QZT0Foa#FI}3r?%s9<0gF=f%rPDXf$q;7?rU1Tv zhmcVS)zY(0n-*a-(~D(A6M=zv0S+Dv68b@!K!vVYZ4<85uAWWl7$GD*B2Z=xH_BO7v=j+$>?R(;$!~1TXStk!4nKw z0$B$I?R{WVG)I(L@KhswRg``?5(w{2 zZ~}L(#=UD&Y@D)>tW>sWj6up%U2c7R+P=itz+fH-}5im%7w5xffO(9HEKJ z4=8=jBqI6qxf~(g5#+(7Z*8<}**&ddtrJBHUkL|5`u&KNG-0>kM@~!anh}cEH%xLg+Bhe++$z@rRH#?98Zb2yHv#VjYUn*zq*MsGI#~|# zAD7dim6={&wz7>$HiTJA8GU=AX1B+7B+_r~l-W$H%yT-42JA;3${q%`M()y*TjW@n zjBZ~&ww51(ZI$jFE&wt8gL4Yb{=p9KY$C`u!|CMYeSzC?t8c$YtmWqoc#urT6qei1 zRWd8rsLYq%G?oCO{@{YP&PW5O!#hA#!r>ueMjvwvPzJLTA!^6SODF)5*LB#K&>kIC z`r4Z~SCRo3U~{~%KcFSFQ2x=nx#JSEtsl15^dxzWBvMcwrs587O2k!xbtwc+Q*(ci z$Cnhh8kI(J`dk-eiI(}8+bm!jb}gE|=hz(Onh%H1noUgrLZpd`7K*vq(M&k89p-wB zBPQs_L`Tx53x3?aT#-`Fv7lr#GfT~9gl-CO5v@ytt8gP0^ME_2OE3S^)FOXkZH(X7^V1)^{`C)@{&0NHI^?j{>|PaNL`3&! zVdArx9GeQgvM*LK!*2MDT{6CjB@H#x-Wz!WzLbrkhtN{jQkQH;zaCXm!A9ywk@Wjv z5=BDI|JM49F=1LYBL>U!q7nGm{w&(k6JiYR0_Om z=|OJ++Cgmbkk?Grd>%kyE{oNNN@fvn1na2FwC@giSWCx7zYpf<*O!_yAQ}wBhsSN^ zIxH%l@0gwo-ONeJKgw771_OyG8E{hak~2?=7%x#eSP&kElp(Z*w*$yhUX%IdFng?A zRy83XE;SUt2C&s&Nkxx3Y@i$M2}JU`Y7?KYz2(d~%Kx1|zMuM#>2Z*N=!9n{w4b9r zrEsQwI_P~m%69zW_YW?#2CgaeO&6Nf$8ml%KIfU78wGh~PA1n}jZDL(Am*5JZ5F}z zvFD;T2};7WGT|dC&$R9t^K#maF<>yXHQ-jiy!DA2@QnRktV4f36DZIJ=2rg$nvJ|` zZv$}(ow8eXkT=-XERP>4U*!aDzg-a^o>V?Y*IZA=CMKO!EcEU+R)JS<{9>2S&yULGfk@0FggC~{FbP<9fI6Ta~CbR*t4P1gNIS!U3 zpBa6|_R_82zGS+e7W>_4?(nP@^s6l{81e$kuuOH$G~7>zf-BJ>pI1mjW}#FJ0=13{ zzy6F~6LZQMLA>bDUT?f%CJB&_{e zcCjE|X;fS`ic>5fE5SdT>8V@m43F}K(mmn5x5A zfELCL2LKdyM(Gi(4Pg&^s^h>DRri$b%e3!M4f;FMC?7RMR&x!u+= z)VaE)Kd@y!BwFY}0&yV)ML+ruuZwY)K;>`Q*-Ph2_ZaIJwbZNld%!E*a_;D=4zopS zV`d@=2xDY6sfvJdjp8Nd1u!=jCv&%y^bho$gjWe*rx)}RjiJqfg3??Mz&6*>ChgN`@2VDppeR4F4caq4lPfx;g`siG zEPSwqVlmk2(Z{uX3f#&wys=qgOL3i&4j+JVBH=r*k#Tk2cQ?1@OccYaM9IO!LYRWRa6b)7@}w^G##pEww5$gt!XYKWakfs6~5Cf~805mc22=p+E=*>7$Y} zHzZky4n-4W!Xa;H|6DFPz9CyJ&of5{eHs_u>wM7xA&PlJUGCbgn=!nsrB+x^-B|jf z&quzO4ytUHnM+9R>gfG~4973ETp10?a+aKuX|4M#h$3*b9nveax&q-Vk=VRQ`Yd@Z zQit(f7yhIYQ*HZod_5WVN5y@oS#f5qV(z&;%XUX%gm`%cVO_+a6i){Qr5J-A@?5Lc zg0&yZpl@?4g!mb6CCT!>>DJGbp}3czN)per23S{uNPRlfxdr#;F(!e7u2L&2dF>GtGj zK+N-ED=et})YH>`B5!3g9DI;mwj11ppvBp!?;5iaaiS0NSjf|4Hw#g-S#_^bnIxzn zM>AiSiVgV`dOLsIbk23qsY@@yjw|>oF4IAkY~$4Hjb8zZ;do$&ex)ESlzueO2=|2n z&oav&69ko60ow*d{i$U6f?@I|eLC@M5o^?kVAMjl%wc92S51J}etWyYil28LvC_Ug zCOYqtQt4T+G>mAeVXwtQBug0Oa_5Ezj=?6-TxkArIvV9#2t{rf)j`vRaDrEz{yt<* z`-8CFhn1VI>!ps&$zR*geaWmL&l@>^^n=bHMpk-SKRg(2s;BG5gFSjbi)- z$w2{tyj~xv*?$QeXa@}g;w8iHVkp;4T}{zV@;?F*TPdDjc@v}C_q+(jT4FMi#t*l4 zJ^RuxY@F?m%}gKEH$5S)p(!btUiJ3yB^WMXA` z9N+Nsm~b8q0_^KSR0o0KLb{Q{K|HBy%K?9PRTf%h%MxOq6q_Ww1+dI((LtAvs^TYR zGBJ*%o;a3^Hm48bEy*l5-_{;f|5@KQ1Pf_WF~|M6f>zHwj(4eNN|n~DA3{KT{)jAQ#|0*5cuQGVCZKUCEKv@(s#9|_GWXco`T&tCg@cdJgr=YoxQ4Xxr^zzK1Er_3Z}EYNmaHIy8Q z19~QRsuTp_^!dsqYAm9zz-#=2&)j@O*QCbQ#oy7|ykIM2V>EP+b4%)#b1Eu15i>0` zx2e1A3JQ{FL*uiVA}&FOa}8IcM*|=8CK`{YB*@qrok%5MTpRB5AWamqi<4OH`Zm}u z7qw*#`VpQc3x{T)^t9D%@d>RMk3%tv#w@&q`IgjSA^hl>`v~}`15)_gb7B@`!Q^Pd z@&ve1jWNB)Zm716R5CDJIGzN>Z zPz|6?CgAtgfdVA`jBg^S3hA%pe7K1Xc5T23Cq?QDv^BR2A)DbO=Hre9q7G{-#*f4IfX$}E@fOQQV;*Juv&gL`o!=jkaBKRDLSIfbMJJ|^^K>tZ`aF$7W{fk zE8S{}?y@k}CDpgQ>b(ztKT3|NIJ`9S6+T1<2-mvefM@q)L+DTG2Y_6(3eWSzEyeVM ztbIm`YV49d5^AY^ z{NFmyDcAJtgSdr(gdAi!e%w~PTP+Ow=d@g_R;c{fllY*tNiv7gN=C{1^8^3jw7HIn z?Ib%{RSHqmyid_cXxm!3Wya=Em(3&p;O3s(QQjl$;;LC!$u8up@{c5LzYP{{C07?> zi~zzX7TKXX)oB)PZ>#(KOt^_@L*R#F#rvE&{DVtxLLe4B{O%p!OfE+0Kb1ZxVbPM9 zP~2w`7uYdkJs&EHen+sRmETy-keMSA)D!byh?koQ3qFro$e33|}iRo>&$q;jE zwC+$A%9DXfdHJPr(_0{&LI`lrfy>BMCDM{1sq4bfmGBue1M{u8akDGcHGotTZ@LiD z+X}xzwr4%x8jMyx1g4J!`r5@`>BR!RHy`q<8`;v;Lt({II_0?_iq)uiqsOP_q(0I` zZRhjo8eamuM)Wu#~SwOJR#54)MVqLnA5%!>F ziBnE2${4ZE7F9HLUx3ydQnMr$z(nQ(KV?gu4kxI5G>cg1oh^5(!AwmwJ7?AF5I2o?i9fa7m3e={o6!!co=E&q zB**kk1D4hj!`RTV(Gl5O4{(W4xwA?V6Gzz5x1ElY@yIJ(SpnoSK!vNd0qRXkU3~Bc zQVEc3y2BGRy|N3{9U(F-e4Z;?;vamP8(K<}ox`;`{Jp7fnAtzL&g7zKa>8ZvTc~)X zol1qKxL%kV|FA@v!yidb(P>siMAr{ z=jdSqTA!gPzgC)3VxhtrlbP$VS(iu)ICKuy>tVMh>7g#RiUWjV(wtf+xd)9MMMMA|mst*X4S z(W;Dg-;f^qTq}~9Oik7D8=knh+epe!$(vTk*)MW>%-I_EyWir; zKGo22+OZ}Pg1or`Hq5nwC0rLDgq9eUxq@_8wroT4YCOXW&iaQnFEigjXMQl1t+%3lN)Q{hX&kesuDJpXwnx+q+Ryo|a>E-L&?0Ws+y)UIzqgL0e~5 zb*#)8Ab097a}}9!Lax!=eWrSlj`o%O6~SIw;`L705oX9{T|`IGs5^>iC=SAqkl1xl z9VIRnMhRW*unFBHR|KK9?W<3`uEFa=co;TxN;?`QdD4meLo$NL4Gdi z2Yb}bEaMDm(dW-Ha)}Zb9|YkI%%)<{v@;cT8n`A!oW_(RYpJLG7TVHCPd6(EvE>hN z*rhoT%H(}Qt8}Cip9{11C}2|s8yc(QCrm4lf2w6mov>jlraR`+k)N=sKOAybfYwY5 z5M(t5-sUqg7&CWz?^ z^GMO#U;n{SegDy^rg{)%(I31aPH>RcJ0MW6wX(15hyPK6YN+}}(b({e&8JuN9)BRg zeO*i?{ex9GZ34(!4h8bQuX0*7i|5`sZ!`GtQDdux{f5#?0&V6$RE7CtCLg7Ul6QXR zE&bA{*^4vn6ns7dT*(BYG^5ANY1m^|_w_#bLY)o8>ksWWjmc%4CT& z^1|G9*l4}MIDL@2`i|YEzORdTOps?yI}aAsr1~%IYA_lg80ImWyl!>;@kO7Y=NX#kPmpTYY!qiNoKWj2 zDSOdx(}?}J=}=MZH7&h~q=Uv1CD7Cd%f@;fwwgv4z&!gI8FWa@zL$F{b^V~1Uasc( z2jo+1kxTb%NrIhvJO-r?f^9cIak+N~HNe;`9GR|9s{0lmVJ93d86k2VV}1W@k+u05 zlzN0%@^Mg)ybsLIuks3pB8ET_zl>h~RGIq8OGDfQax~a2p^l_EvL*rf}WfXWc?rzn1#6ud&YL-O6VU8nU6x({}UuX{Y ztPAaIx^z!h^5ZF@octfON;RD<_vm5JLh)GG%X%?d)tU&rV`||_-BrrdxltD4pjbiH zzLuw#@^l7g#aDFJs%a3<$sysAS=bwn&t_X8ppFr0*x8XzqK>X%wdRgIjnuL_-eR6R zJLgU=yaEd%A?VR$eZ<@f=2+V|lN8SY+~U)wPBY&ZXvk4VAt{H?in4TZsy$)j=3l1k z!8#J=Z#rcU2gf;id~)>AiHYU}{W%GWV#0&$ZcAXXs_0>6HTeDw_|Vb_;}05ruzMtM z$gF>3tAbwqxdGOhfg!6HA}oYSqS(u@E`7w&u>P`j5$llhQ?{m&poKXqB^$*;x?yF5 ze554$as!VBsU1|^YA3UQFB;@*oo#J4VwOvAC#?f4*M`$2&mw04Sa+_G+WPiI%f%&? zytN4DN)K>qi{w@@I|-;7fAJvv@{Sy1i-}SH_(C-G13i%|GHz@?FIz|EzG$hKAcz-I zSOXVI(5foIAiQG*?p?zxHQ5%@q%mVf6=pxGZ)quT1O!t_PDe{I+SvtEvYmvw(uhyK zsgBhhnGr-i<12f|xL7F8?6X)MNR8A_po5&QkK=T77W02jFfXy0s9#IXB|yHmx#OtE z{Kz2>YC@cy{m1NN%Kd|jkXu`LXMl&=HtLk%GQ%?9JeBQpB3f)euP@mt1kMRYX<@z~ zk;|a?Vz+v8skKb+9n~V5#&|IyrrVzSxd{syG0q10*lzWT$h#5*a3Ein-oI|~49b2{ zJ;!o7jCJoidNU z=k$haWJZCE#$v~L*3-&0*`-LHn{!vd8CLr|;7rRm#r33xH@zOUo&j+d^E(Ioj}c;R z^D>8g&dq+C9OoD@>w8ry*xRhqfc7xsUH7&S%21e0h-Rtui;>w7> zW2h(^rt_3Tv@opl@Imnrg&A@}g)}x;ZF%rjn9Q?`W}<}Y%{Pl-CHmlBS#Dq-A#j_t zZ3qJz(&ONSl{i3+3li-jo(HlaCN6OM)E8t^1%k##=~bIZX_=#dm6`&ACs8%WQh!y?=swYxAp? z@;pR*;%Y?(AxaL1;6%de#Ak9kK9Js-&Gxld7@$qUunO7+&+z!LlB=c;O;;Ob3v}Kc z-^m5AuU(x0QA!DucY?h-==Cy_6ehNbZQ%VHBSH8<)*n``H7YYYt5OcL z^&f8KhLvU-pkCwHq77aa_nJPt${)O$7$45&Vyc@O;bXRX2PCmY1dfkp-_mt1b0dmkwP(#8 zM}j%DqT316+NfA*Vqc3EF&NAZ9pVjZqv^MIR3dtV1EK4+Dg%1&PC+izf6#HP(O3cn zYZ@QUxuX|AYM2u3WGyW$qH|ol_54MHXCyHjMIQ_(Q{!ddlZCQ{it>do_-%f!eE+Ox zG4*)BW6idDbz9mbPjX_^u#pc9G@O+n^L9x{bm(%>OeLb_B^BGn_%pLXB_|z9hXZY> zPWBk_XrK0z^GT|^yx*}9eYb2=C3}+)B*kJnaJyym*l5!mokZ?U`!3A*5C|X(A$`!G zhz<^saaTW&k0RC$wRH%0c5>!d11AfhYp;+4h(2U)8WwkLdQ=(!WXZ2Rv5x%5Bsb>` z+?P&vVlCR#NnLt7-4+@5Sy5|+`^-1;T*u&8!zJ$w23$xqWwz?e(&?A!_73N;LK_Zo zCze%#+_|io?E1t%=9nEd9(3pEc1ax=u?E*xUr6+Kn6R+nIeOYGw55)Ex%4a}^pE~^ zyAb{%svvXg5uyF|RfaOr)ltibpXiKF0z05ePDKH=b-nNkz5_SxiyU3bRd1KSyIFWv zBy%piZGJ*OfuW&MUY73oFoXA%8V`LYEZ)OZW}IHJk;7=+0bzDN3TA_Mt2P-umTx5y z)gRoV*h&lI6fR+bW}0mlK{^78^od>>8zbrNvvLBjd~2jYx=EJ_mk)V?iA5arHB#nrwdua6`;>|@=kFHeEy z&kHe}cl3?7cW$Y&XTf@+xwM(5BQdh@6l>*N zu%QfFYqV^KT~h1k)96e6aWrzw_js>;Nn%X@je~Sj`<}0#KX{b=a#G?j-Pc>^hu#E3 zt!)%56525qv$`sFT!${j=-iTJ+0MPc&95G4a&c4j6iljq|3&O#n;K#%Vs?fbqyd76 z#$N6{NU>ZoW#BiwlIa@P#AtU)51PU$eOz=4*|Z zk@Yu7cWxl#b!SzUUVqnj#4GZ=CTDJ?nMWHIyL0Haex;;4aCK%o>*@nYGXJS7{+nd- zU*2A0M4KymKb*FnSpaD)H;ZY-Sae~FYeCq}%iaG#n6kj_^Tm1F=voPUXq36qZ!+0` z8WO~u$KP%ri=ISYSbFr`i2XPPWg!1zh75@d4#?km4j-gDM?vXZwFQrQi9iDKws)0@ zX=hY-4f)k?_*MFW5R(*>#bFnYm@{FqEo381WBBs-+Ub&D9vb6#d_0#Dn>CCf= zKavRtpB{kMeYp);Zvoi~RR;sY0GQ=`K}R0M!%snn!H*$TW=!>WE`w^$){IaG1Cf{@ zbBlA7R4!WQt6Ip%S<_1@pumoAtdzIg(;udwprI-#7lY6PvGWd4I|kFIs(91ZKlt{Y z8y(xj&jar)Q|RvsJV0kSHm5uaLSa{I9+P)9R4#I|0&Ucq_S-}V6&Ep&i}G!{zb+5f0C>OLW&?&HpvYXb!zvD+2)tIXn|5> z%r`(D>9noOv{OSGq(!>CAi*kh+qV5T))RQqt)qYO#MFa6)qp^+{Y0Yv4dZQw(mo0? zK%8=V*Z$J%)ITc$aOQsEXqFU8aQ?=YmVhENn;yy{DTZP=!On&3_FIX-fehu}&Za%` zFNT_)2`PX^6;zYuEAsoc-A<4~k?Daf0h~OZ>UeFmZTV#ng67AbcA5tLThSPJv>6)bl83eZj$fXAos&Rzh1X?+!28=Ft7S<3!N0!J`U)X&A{s#3@5I=aZ6z0MiGf8FU4xv?c>Cv9~&zDhB{p2DGk36zp zNpj>T1^)E29B)#?R84gYd(RIDn}2?^+&x#U98nhzxdTyU-~9ye0vzLsf2s{xu71S_ z==qG|RZ;Wl!o-iB@T6e%kzT`M+j43Svg7@0%v1o{B=-3I=5q>p7rz!bxA$!M;7(4k zRN=?1b2A%?2E*v~DoT*O^}&C6YqG%J*!v~5R9rG#{q#jC9)(|y?8t`dqUPMvy}1DMnGc5jA0;WQlzx2B zU45Iq0Dq5hcGvgD7xsAB7Ij5M>`mf-8?lMHpBRqeZNw#*5>J*LVi1E=Hm934b3==U zLL@9&SK@c%bzb(rjy&MV#!4QsePN->w-vY2XOd9Z5!-P{s^#$ByaO7d(f;4f^_x!> zAfH~F8>ZB#UuvbE8Ofn`YoT}ZdO+d?<^TNQXVlLRy$XV|qot0c?YN8JIjSY2N1X?U^@J+S;pO=C=Z*VjSEL>@2Dc*Mr_Br zx0`~@S{K7@36bfpe^W-uZ*9HvLv2;&6=FTS{7EidoJqY!FTmM5-Q`kN2)>wbz3>}- z;ys{RzLLINKRihg#Y%}lI zg`JHE>+|dvw%#aBv#Ecwy9^ZmGFbnzzhpstI!R$%KA%6;Cf34R)R;Jv8|h~3jZbq> z+`l{WR5GpEQsuyZmXmpN^+(_nW)+!odo9JgQAt+>E8R14CEBADc0QU?A(}ijUcc#B zHsBL94<#nG))*g_Ig7%2S@(ndc^z-Yi#vLJhH2X@l9^rqHj8iysLX~=*W{d@~aY%vv#FT>gnGYN?{zfZ)zhBwvbgL+M%s_$nFC|o7lST?8(z8 zD-C4jW1}f^=Ql>@24;Q6ekzP3aki|c+y;6 z9vu)PX1dI)l+dTV??-26RCeoUKd*Ax<~VPweLv`WC{N8d^J72>^{UE$|6zxeDjw#l#}0(+a$~Sn>Pd>n1@flUT%VHYQ6g;4atdc({uJp zhEIp=ERHv;VAoiw9pR?%XN;Ai^`jPA<&X#aT-0{Fw!oWDk9|?BgBxGX9&@Xlfr`Sd zdKehc8O^&&>ID?M&0ECUQrb*sQI*V$cYMv-Q|Zqd4*JlO0OT6?CJiPAUPGP zZx=7BYpur-*Ga!S2^K&IjYn2C9{lG*Q#bYNbDl^m$V0I;>Ars>@R)3SVVKS9*2?#! zrh|4R!2p7yAO>1p|7XmZ0jrvp6UreThmY+DLe}Jj-+{=q_|2sstk&Oaf~_}t1+Cjg zYP*b&UP5ti^{irwWPE*^2bOP)@jhE4ESqy=m~({WCu)clEexP1M${OjFm)r}SybZ+ zmC=r&Q`YTkj?=qppzcI=(+ptg{0HhNqZ1HnJSL#B~Mz|SUh88N0MemvCPwGG& z-@4-lcVu=uqgOZ+~U?1rKFG ztm4H)(SLoz;C`A6iZB-nJS6zq1-_t(RlYNU&F$l3d%5+vcGE&jtRB8>YN5 z_oBYzami4d`{pC0S;ug{U1B;v^yB}bvL9`Hje5hV+!#x`%P3=^?A4ZaDGd(QgNEjA z3~isZZ!dTgPO#10llrp6$$zW;0ebCbAGgho<(~tdTtiVYmmN)Gg%Jk*N$%S-hT*zZZ1msy(qFX#bU3w#6Pe z6tpR0U&MG*T(oH4{fHM#kA`-U#V&>cf3%KL2w61Vu#9(92oG;x1TM&MKe)fh9SyRp zE;AyNcR}jT28i7F%^5#(l9P36s3>>3P$f?!*xqEW`NZsM+8=(&MS^|74EgFL z_PgDG7{LaMI<691zB-E6Det=6ho1z+ivO_0t@0R#xA3oc;~^T0tm zxG&|$to{s1+Y5i%sswW@UYexP)uHzm z+TS&po?~~N8Mz`7t#bT&F0Nx#tjL?+Vu}5~rvyXxVrq=-Siw>M( z7x3Nmcd09>ic5?k{XAM-^9Va-4_nVD{9Ks%I8;vYmuExb!EF7U#qUY*jSJ}3XD%Ms z9-$^RctW{?v?$kJnAs5B{N>S}(!9D2;*oV(aYDNT@uMZX*yVy26*Vc6vYde9GVNA; zRMKVhM+#~Bzf8tcP;B#5W-kBUPizdJVIgb-<~81D30#Cbv!_+;hk*}lJ%aGPr^Zfz zl1iz~7CST*pm-dVNjJe zMJyC%BqxBaazB?C_4*1@JJ9+Uze?o-WYu=vK;;)TZ{-9nqt#?2YK`cM%s~17shHF) zw%RT6UphQP0wZlRHWS+2B%E&*qQHUj4>vOsBEd4~1(UOtpqLIWZNN8Pq24Yq^;dhp zdGfK8Ww!a=SSe%4_^CY;a|7H2IZgat^F8ileHAg+8dNZ-_@&qX_L&T7VyXA80L@se zgv~@=U_7)t@%tzYK~*c4vpTq6^s3_gi8v!)tZ%d3F?Mz>|KL@iquaH({@R*c@eT7n`<=+UvsWnhC<3DD$?-zYJk#+zNNVf#ypBVRy&B zXF;|F`@ik~-&;4W z4HV^k^(m%a&~3K2u;F-rdgV%Q(Or7>Jq3Ex2>QLOjTzW$d5ZJBRojS5zs&p%hK-*S z@Ja;%WFI>uJ%Agga^+WB@C2B=bFQLFduD`{4>V0#F6rNV!B;UE4gA@7BuKY*{vQ^r zg28$JV10*#*9aeMKBIpyujn8r(^?i>!N=#|POuKBvaBE&t7$xbHzXkYW*K;+fsNJv zb~V0_ITvDjoeEUiZCCosm!<#M8|f>Mr+qu>RvgFfQu3pv2Tmnj-$6x&Sep-=N^Wtl zFw^4q=XaB6`G z$tfB7Ws0;+?orb97$xy-4R*l~j@E^#NG!ZJMB9cN-kLET`K413G~v{*ZHnF%*(WM; zr+AjFc!cwq2v9jbiOMT;ABoMZ!rX(uKvMNWpsrSqq&*15nYnG_HGM1i3>~{zz=saH z4M|tJlqB5BGDZK=CkN=$xT$FmvwjK!unkMh&d=Q-bN~cZikxCApH*Kk8Vw4$nX%WB z>J&d{I%E*O_tPyd@?Lv$vEF4&qz8Z!@7Xd9bmda_qrC{zAAU|CO7;D=Wx<|r7Z{;< z#_g><;L?ygWrzVkM63~K8n+R8A%BC z1lRf1j*b?|xVn(D5n2ZK^u(8%U^hUD;?F%5e<*ts=pY+cq4l2hCJ$`eu|75FNQ*&N zHAV?HJG4oqYx@!iF+WZ|yZ@Jt4ghd*#J`*Rm!J5l8jatd56#?|Si_DYcf1vOom>X% z&Mrd*J!kaMaL@{)L!GX77z)Vc5_{p-J`U zLX%a0DqOV$Zh695kJKEt&K1#8ii3T) zjebCKjZSf8A=d%fmgQG+xjfskkhs5R`uXcmk=n-EhcC`6hfA*L#7ejuS)@V@z;^P2 zK>AAqWnG89LiDm`hbsS&crI!lJvgZlw<|p66oeBNK2>@Nl$Ve>X-@kxas`4GKwI0tYZ%?`YOu^|4 zKp^Mk{JuqSq%pv7DTNdB5|&rI+4D`(@qqT@Tlhu6k1=%1~70^#_Akar0ZT;;!q{OMKw$Yw2`Qf+^U=-1l0_%;?a- z&b+p47iwv|Nh7ml<@IjKwiz7Wnv{RbG148G`YMNs;rD;B+h-xzX_N}=s+&0bgFaaP zEp20OIBHHMoAP*^eZ@Vr6HcX_F*_HHM2N^glue}YEw{HQ!>>rR+Y4EydE)f>S4BPB zFHimGVS3(e056i{u2B&uamx*=_vtSrAUp7${WuGezk6d1gp0_aT54&Oe z=+oq>HBwhkx@|s+OA0qzptSj==)=tw=JI)9Q#vN&D|C0P)-AIA)2&w7v45@9ehZ|@ z6c-|sJ73%KtxI{&H`hb+Gx=J6k{zZ51$1a*)#Y0n>gG{;9)pqP9%cbn`;Sv~bHbzB zMHR>Z(T^F-=kf!`V}B)YfU~UtvSLL8bK0eJ%(WvFdIRRsgb-7%K6CiQq@bT?q?Aqb zTKBs3N`#t7h~35-cBGg;&)9$yflM@7T6Ly8IVDO{*3A3tY0x*-k8F$Gyec*eA&jU z<;0T8w2ncmaPa(8B9Zw9MtVz;x}na#>^9(R@1HXpT_R5gc}e}`1YvHZ&2HS{%soHb zEhh~^t+}>v$cXEqip%y@Qwpq-mj}TePPMfL+y^77Z~;leCN(WdAT&g=dn)kt1{l?C+(Zi(g4ZUNXC#zhiYbMlJ3IDIlc36TNTOcs zcHw4V65pKgAs+}?#0Cd;|=k_#U2#C z30x}3kzp1lo$_Y^bVrdsO@hcqNSJu+fxoRS7#6UTgk_+w(=)akYmQT27Q7WNz@tA# zV2t7RWz(@U?fzwBwg+)%B;1tI@Cl|n6>THu1!5_|(gHWkeM!QrSSydY*E13G+Jvt= z?@Vrp7dRw%$B#U`$to+zDb6QAxXCb6MH2HjbH>BNK{ArcH;>zGkxGsDmABoPI z3WB1Lb1f2%9u!HWc57;u5UA!s32>Wc+{yqvx^VPa3G-~{wW`e@a^Y_mHYY8+NH>j& zT8yk4nnd~D8YhIP`fY72OljEy zHZ+QW1b*6z$L5mbAggevjJcAV#M$+i_X>Zq{%uM3QLtVBr#pvT;X8AG`SjY7cFV1n zX3t@+v1rYx(_S|N8;*?CVM0wt`Yb2j)+fdo!l!+TG_`MPL?>P5i7Ey3(A4lW>&TXR zi%De3^=vb1OiYlY^x}^%0M!I{A=uUQ(sQxW%U@HiyCSu3nbn0%Nr2t(W9#n0)whn3 zNQd`B;-q3+@2J|_`XrCbxuqk8h3!W=4$i^m2RyA`o3kJHN~DW2`k|BoX9YDBwqOfY zTW_=4CBtN1T07bJ-*|prY~mc{;g>IR&T|Ldi*M_Dy-wri?&el6d@<xj6dy+?d5740!udtP`Fuyt)&DQ{E z;06jghivL&3s?@~=eQmpt-YUxoxy5+aKMRvbl>u8*jM8fQT1tID6JCz*|&?(^kL!C z3t{_@+Ih)W1ImitYL}?;u?&`a>PO`mT!_y*}kk_@@Bfds;Dzd zw<(RkWMLq3VT+XNs&>H)>eVZ2Th4v!2W5J5JO{QXub;Xz$4K)aeu}pAVCLD~>$yJV zQ&7?&#@=4uxr6I`*@4Z8XW#hvgN)}pwPK+TCk6wK2ehp-Do-krTG&-0-oE|GqnL_W zV}W{klnp=1qtaRH;GU^PXpPl!N9Hip6Fv+@j8M^>>)EU@E16_j{iC*$&u|J`_kQ1< zx9?}3(inbA?k8Qj6-Td-Z;`P5oV_{+MWsHiw z0?u9WY25s%J4$HJ)sRpNY!c}^1*|dqxsnSzcbwiMPb53tRp`##`rSCOhRUxx4Hui^ zq@=5UEn*ySn3`(&dS2?Jo6?x6lD_`MvF3z+^u=%M{S$=TEtwEVg2jV^3H}%0zp@P@ z?Srql4BL-L@fm;Q{>H`CJyCY#8mhYLOOEu7n<|lU7Wjawjh>Xb`Y0~*o{0M8tNZ!t zZ&4bWBb^4>quqo{-{~aB7r|R1vwI9EF3&9-f}!R=I9+SG)+Thj{Sq2F=dNq^<`Vmn zpDnP*=k<#hKJfD!<-F@k@*fu(T$=8D?c9BYR&o6&sidj+j%O~5cFy3Rj)%f~lN^Vx z{@5}+p-edxL%;i_vPZxVccRO7t~&GMq&pu&)%2(gmOSQTbWK63-Trl5m{-%Lj@$uw zCgvT2$#y1X;GQ)o$33gYg!Y2)l`JP7Fwmm-aS7=&VJM3JxClatJTDPw_0# zJ)A`*im80%=0I}l9a8OgzC)?rUUiQG8N8x`&1Tq{W`e5WX?JRmxJgXqIy^8=du}9< z?DzGOMI6_l=ywuN$hVG(uhh=zkD}1*&nGeE-i&CvjF#`sgO1&=PnQ`+rdsI>HEO9c z8cZGu>v?kg>&k1UWY5nR^ELg0zs3o9h3wq#cjf2_Yx z8sMfWWjt^7m}@FhjYGBI_(Jzfs2cM+?#V)LG@$=X(rc#OqE{>7vmCv*-?(X6eKw>n zvszi+{XZg0jXj=ZVE+O^Je8zH&Xh5=%w>7o4Rij6kGgy>D<3o z6(SO%e|Bk?>B!8`UpGeJDoMIY%!X-3bFoJEJQ$U)$e3wzYZewi`~GeKddSvSa9x6E zHqmu@zWR|vWoO~N%40JGhvsV^UD$>;4QTek?6hpGSd2WXDt=Pyu%MRc(uIPyD~IUn)e>H;Khy4w z4nHfX^F12wR{V&AJMkc0vzh&1N-!`fmpi5t@?mCq3u{FDe05FhzDlxjx}2kwzcpUY zGW(@`kUu1t%PK*tt<00I;IZZfwZKM^eZa6#o#)eJ-O;kEC%_LBm;bN!-ZCz#EqWOC zdew_w>s|v}Q0batU}zOYI#hC~ff;(}F6k6$kQ5M*mXL0wySux)>p7f}x$pD*zdRq` z_xF3h%-MVGwb$8u?X_3!Ju@Oiv1>ckG@)NMnL}8uj$;~Se;`E2qSg5(o5J`FXX+X9 z8&!I5G+w#DUq>HukJI|=dur)!x~pOi?!XuB-0=6Os2{*IdVM6?x}FwX^pDNf?h@|L z#p91=p9q^9;(0NWekqEje72mnw3Q!UH5pU5=hD3?su&-qzeqxv`C|M$QA4KyfC@{? z*+*x8N_$fgWY!HOM|byk>UB%(W~t2!o+dUHBR>>%Y0%WwSe|BeVSsTxX(K)~@ag`Z z?d4(F2c4-?cU-8-ouqbmTBroH=2esNGhE+#dgJJ%rwUPjFMhtXvqZz3^dZBxSrjrh zSiSX-Wy!vhm3QS+?}yE_<%%O{qS#J|eDsq=?}yRv7; z6XGz-!C`U$>UhR6S-a~y<)4a6kvOt~{n3>uu^j0Stghz)eY^gdsU%}1aSe0l{$P57 z^gYj@)S%eXi0ow=akA1w&5B;*m6M8atiedQW^|l+Mhr}4vG(df-_MngDMNW($A^w) z!#^s|125TmbK?;gWrLL754$4|R91`trMi^4b7hqxAZ+F$)z{Ac36w|XG9Z0j#;x~> zEG`0A{0h!pa^cqUz;lzO);Dqp-4I};Hf26g_FI)59{~y=POd9k{q+k9iiXmX2=dQQ zDCBykk@6BpQ#U4-4h|m+Yg9#vgfd>fd?OVphtQ-}V9zm_E0tU5rM4#pWU&a!K$J!M zQ)&IiXhM`$3CdI9yu3vb()z@XQ#WU@z@}#2IwOW}vN-om{#D*wtxz`YlG&uo{028D zy$>y4-DOfzwTtAo}UJo--?_m)=ai>3!Xx#^{k>_p;QBK1yE{u0<( zi7=gVxpbvqE1+bu=TukTXZw*`v}%3vK=DW?G?DIj-sHecfh{{INaSH3x797-!90rr zCm04t_G8Ejm5iC;;u+QH&eM>bg;oQkP z@(>aTq>DJOKkfn({eJ@5UYt&}57>phs~gw*GPI+H_JCh*{}Q}=Z7KuRFg92vWVpY0 zFlY)^1`DHNBN~Myr>%dvK3~4stW>0vuH4#nGs=23$0pI`a>0iHqupqE zwmB53WqKO#z4p>zzylDp9nO%?(HWU)7wv<^#i_Yt3>(4&4YvBP1zTp@3{}_Vo8J=F z>CIFyUG&b z67%*O*79iOK(!6fmh)~lGd%xoHtfU6fyfs={YB#0nGUl32hfy%$MyuWM*Y*4Z=q#qTK8dXSnR3{e9G0FOnlaPGhXXaAPO%&b6ko?&3!Bo@TxqA0y#*iRglV zqp(>8M}GLqNi<`~C}z5-YFmqBM^JIO7x3>im5(f|`bE0yJySf=#P1VdBcUte0{qXFCJC6}`SGVhmM{XpVmb}gNnD0(haM_7O znZ4ZPE5X}M_NED}^n4)E^S(!qRTUcODiC`=c6JxwF}KAVBz}mzM{iKD+txp*q5dS9okQN9$q(y0oj(&_hS-7LS zlap(v+LRp1X4`@Ja%vb$km!=!MeM6hid817oraP>`6q{u4!S+f_$``?;ut0Kd;*x6 z+^vk4sDTv1g*dVM%rLt;L6q%!_SV{W@}o)T8M+JE+^0^KZs}s+Ll!-uib_{T-KmD) zo-6qIt~8pahb$x-+=FyGJsXCoo35MGJ%@BuiHk92So18Ps)C@~p$VsHD`|WHHgaxIZ|%aVzN++kqby)v;#u9_(d%Of_Xy5w}@HFdbD0{r+WcD4aO6EiZ*B$eVe zSr;L{JXoxr3ejhDZq)MIsrJ4i!#dV7gFOvwc6-+lM#%%Jaq;o-viV<0w^zDEv`XI( zF%1LO3izq&6SL5Q&AoOm4(N%d%yf~%Yo_JioVaYwwqH{vfKjf?mfh$`6`G-^V5VrA zzg(P`CF|Q+pCWvP4{)TM*>s7n#4(T>SdF4!CMHfKm~M&!Hx!k`+)5sq+O5rpI4NJa z^>=WZpORR3W@slV@Jlx9+o@rNIz7e{(+slNI-h>7l*R_IB%`V?s4%;3{JGba3r~fV zp4DSpWusduMYsA9 zh1oKCedfln0m^E;Y)my%XX{dMK|xr-X82Wpi+~VnZ5Di>Bfo7^He;z6`qG_$g)az& zni5kbWq@(GP#q{o6yxu=NfMMX#Kv=D&qO9lJH4okujJJu$GiB}NZ)JRD?UJKBuwhxKOdd~BN6eZBpMx86a9xE4%Kvp|sz z!#ER<(&o^&(CEsdD!LS;$sJy2!;Jf(sWOEEvV*3$+paHpxP? z&VIpO_&^UUiaNdkE4N3M^zDobZtm(J` z;ja#tXLQ3oNa<*AReLuYhj6FL>Dk@lwmY_7U$fHNi>Oj+X^4(3t!oclSUmn@VpX@Y z<9!fap^f|59B|)gDfH6jGUHDzsRpSgQl^@Q9d7#Um1|wHz|VU0g>vCvoo&*JH-Np= ziFfcO`T}F8;_9R|hZF$9g{<7T%QMQD-w_vQ9WdUMzy~8Cfe%X4pCukvY;;>ZIt{Om zNZv!BM#~tK-I`yQ3)O(sT=|sM_;uNyn%nf$Pm9?p$xiMat`!|>*I&)omYUW`1d?|} zT*f>+`|~Y=Of^dWb6HUlGo!#-uf+Xe z#jx6m#RmANtRE?IrF|t+sXeZF;e2Bv3MJ?UkIPD%d8B8{PEriW!R*Y(Cq%CP11Lk^ zqs0B0Ux}tqw%H1(o=Km@soM-pCRI}2uy;$hL z*s7R)`D;qwYSxi#uISP?AVd4at93`H=o;lR_gjd%v$Zss=ok<)^uiv->UQ(kYZm ziq`+yXtEuu7ZCd4aM{L!({Akw%3^wM`-AKq1?=i_?>~SSG zs##THU+dT2si*LiNo+n`+HW%DzbplbURvTdejk+HH`^xexVAoA{CxP*Eg%r|SK40A zO{$CHj(UuolX_!WYsS3M&dto+bwxFN2A#e#Sn@MltGo8O=OTKu^d$6|fI!{-;iTjj zigWGdn;DX(Nx`4w&FZ(6w)=Oa>@qToy3?g|=GSXPF5Eg6|8||2;Q{{B=vH)A0+t9E z2cn6hFxX7s-qz}e{ME7o{ew1}eMo?jGi7XHbFA9*mbulB zjW*l%HZpNx2`nzxZx>VEJLUD(K0~JZ33+guCN)&+t=*|kX;={p=c3!V%aCiAAkTfY z=V+GV*z%`@=@L8HfLH#d=W>4j?-m(?!v_|6_pT$di6s_2QDtGUgTB=5guDJO=b z%*vA#Zzvq=4?fiF|N9NZV7J#mHWQP|DSVV;OPYoGD%GUL_`1{_rHIt&W1MM1UGr`S zjtu7Z3~PA)$?sgyg{*CO4`pHx7=zg^?Z90^r@r3#c2nh9A)YAv9+)*+c1k*FR!iZ}?rA!gO@r z_EH{S(98k?battM2ZFQq4PP$8_Q&OwKC;(8rSb}NxOm>K+bI|L9rg0(nV6*xIA!*% z(0=KminU8t++UcJqYZ)W86fc$jF0HYs+!sqPVIjRgJG-D2lQTocj#(sd-k!ibA216 ze3gtfV|@i|YP(;#808(YvH~B0t1Kip>AQ%&)=MD4P4X|IiKu4V03+eMML!?J26!ny znL1x5jNIpJw0!yWyPuyQZIo#Ed?u&gM*hFeOY;pl{^uaBqcO2KEfE-HDJ z{5oD~q3mPIa@|~sn99Fo$+LT6i>ku!|2!_XX8YFH0X2UpJgarvMq-5;2x{u&({I#e z17DUu96tK1o0&OTI`E~<`P@Kh+~Y3?6R-m^!-ySjQFC~Qt&(>s<;cm8RxCU9kc)8c z^S@mIZ~1x_&r_n7T?ArfwGnT2qT}?43Yg1Yz*DyUFnWdj=90Ii5+~i?qa~Bn);@C4 zEzjIn%gqoQwv#tDab=on4>aEc&H@Ur*!+>kRFOq{vOE5eU1m9ErKRG+OmV+Ws$*z=9BRiBCMdf2#k&p@oDrj=}7&(4~qQVTy= z#4yr)n2Smu&#)2seKygj$wszgy}gA1bZ%`LJF?q}_c{EA`}y{NXo7N}E0S$vz9=c}>s8+sG z-}I5joj47*7+3g#<)t@=KNn&~aApB4F4qEP8iJd$wfBYRvZkn+%yj6Q!Vs%i)+a+@ z5i@+byalF5E(#}rxsHmK1;V-YzMhUHZO1!#-&l4vvt}CZWL6 zV1+jMCuHSpyZtCu!E^&oAZWC0mn_k_zfqANiJ~LVeV>YMV}lIjt0geW?VMOc`_kUz z9=9=kgwYi?{?Vk(!Z~oP)mtVlVyC#8&m=phK4W3P-CAJa$kraKz3*z$qLeA=9mSz* zrV3aTO{o^O8X3Ay$I9H$*Qv=x;u4|AB)teTUx|F1nKWUTisM9Itwx;dB^t}2SblfY z4-o^oeo{>j)cNjjwwy3yT{PUrsUN6me&r)ZayQ+so@U-cs4U&jY^jrTS-|}hw@zAf4Eu#Hsg!RF+uw$cI2q?^ON)N*LqJ5r%7?)3s9XJ1k=m$Y5H!k{cOvTm*J zFTH`6zUek~jlTwFsw8?yaVGUgBQ2j=M}BJbx28PO!ls9}>f@^VKgGyfp*-+2apg|2 zyKIaze^xUGXy7W`b5cX&pj;Ny;q)?@6WRnkg}i7BTqZm10M9(&dv~Y$`o|hP?pJ1rBBwb!0dC zzRmghthQE>B`^6H8@zEJuWH=8^?EV5?|9ae>eiS5WK3I$Q6XRJarh$1!)&Doy+GQ( z;?a4gImQV4gt(wt=iTO@79-Tga4u?I)ABCE$Ov&KKyU5IUk9%(X9b7+`McOW$&o$$W!Mvbg?y_WzIQUMwpmq7$ z+fBBaOpQJ*6cEv*N2(Uf)F7?JpSC%cIm#Zd#i9IP0NN5Z*jz>ue^Hkh0{c<}X9@0E zZ=#6G?dIj#`&TT39AX2Q9@444xq8J?Z&Q4@)b4mu_`Ddqcr-f23pvBZTEEWx5PK3G zyWK+2t*{Z(+I)tctbaI`Tqg>8ObQcb8sxe6`$@$HOpoI&Do-ze>@;iQxA>JdQL=f) zAA;nCP!D=Yr;e;lf**7xsj!0TGqdP4PT&FQuBmfB^ae zhMqCinhvKM_c{OWN|J&NXl)7a2w&Yv_2saM++aZb!x6ejz5ZplPXp^*IV-2YDeJh<|?HL$qB$E z$jJF5C@d`4;f??FVKAjxQ+$Skj;HqfVsCMa7dD}W+%@*ewE)EJ@8XObe>QVkAdB;- zOSvLuGv%}@dVM8}WF+*GM&-^l<~jSDr{2fSsCc3*yuH<%!7d8;LU=QICBfqw*NJDk z_ZW}57@SKEaV}fz*C-I-Bp)tIDdC4pp6{(si)=91&KUd^!(^Ga-lffwq8oweqy>_F z{GIl*p8VIW41Dn;#z)f(Mt%h{X$8802c`ncT^BLv{u|dgvwsEAFkEbs5^9s$9Q4ZP zX3TPptLZ*j61x?n8{+lXnYD7$0+#T5q~aywq5iHkS@|R6!55TqcRuevOWU+{qdsXo4(9&Up4?zF6_m!cuW=Z{!y;U-i{^TTAqqB9$WLo zn4pglg6V0eH$Fbe=PrFtl1ihlytIAUIIEKw>5z@`10e%9M)J;?2>RS2-cWj-`aAgk zb=_vFK*Wz-8b60?jv_GuCegyfn!meo-@V&luYnD)qxaI@`jo#|G9L!afSW%d>cB=t`q~A&CjA_A9*&f z6>ru)8iX`*msr(L~b+Qe*V<^;p1A$ z#D{-eFw859DBPJ$)>AK_M&QMzEsd9aDzW#th&_R?2;K5!QQy2r98rE*WZvsc%gVdK zwpV9|6tuch?yi@#0nKir_D}B~zes3_%>65vPMUFVfqx>_=!e&pv#xH2fG|-4M;B)UAa2Q)|1$=XpD5XT+9Myh(!TJjtZ8J&+o#ZOr$a1ilB5^Qs95k;F%ypk$n* ziDxXnbbvO^H{$;V#@RN0@slxkV4z7joU{ugbCIl8P0dT*aA+6 zuHOn_GfMlVR5%8k*ZM%$035a(hFqv5I({+yK#XlU?dMB$$zPHJhh-x1O>(1|#q~xl zoKy<^xj{ez;ZiX-0yxELqk!zx0NI;HRPf6?Oj7SDy&ePKkH;d2W4R9GQ)^XjHS3@0 zi$CoC))~l`M(PfHiSG~XYC2xA={*@58SZrD5xx0VP|y#!dQlIoF4_gi%0JobaAM&* zi6;(+gf|Va)|1VTIr-15b;TRy;g~7I@y$NO(W!dA`gD-r<1XVPq)ps&;N)uf{9m6B zY3X`&{Q?3y5s#O83d1@RWjTxf;#oO!-v79ed3s&W&+YI3ARPaMFzaQ67wXdt2{lv& z1vTTW5@-WyEhX>t08)JYc5i{v=S6q+7dQU>x7|xI*G(HZz2EEL_^TeGMqC--&OT*~ zp6LC5h;j+D-wMlZXy!DypQDtb@sCB~zNch@)HNEV9O2r1fl6SwPtMnPM+JK^IjFsn2&tt>`CsLWA<%<>~6l*O37dc z_l+mDj(^>l>fd^3*mY$ zCV`WZb>J&cA3Me3fC&|!zxelEpXzJ#r(zgm=Ho@ouGXI3bU~S~-)AH8=c;|+0NG-J{K211a{FgJjZ9AjN5aw{=6!aHg5T zqA?2ii;1@-))rG1MmfT%54t9SJ5}e0IJSuUpTsE|5aD~GR}_e8-2cSxcZp2yR2iD! z?vtLe=0E@BtfX@4mAUx83jQCx&vwR5Hz`1MX++3YI}?@lS?{ws^V~bN=VRYmDK6ZU z3RE*XckYix0p6GI|6A4ny%ckLL@%cvWI!~5XOxJqxak6{s|qWPF)@1l-$?-Dwb!@< zoDp|_!J_|1Z{h&|U(2TdF`M6(;{SuJSv~oca7Fm4QiJljbLVcVVPEkhm|wwI;ZP(J z!p94L{Tdp1o5lRxxqJJ@d@{x=`uaMWsw&2sdUz&Nyc#3cTvbI!Q_WDtj84l)4}Z?; znJL!L2xxuA_6+)r{uvgps;8!jR|l#XBV)tU68$q(Jzaf6tdS8`?HQY~p$Yak+G+Kf zdH9k-pVWXL+5`fDB5g{Q#`sDd@J*FArvM1NF`z*Qjd^Xx6r=+JFe%DHZ-U76r4{#C z_E+|W_5;X+Qlfu+LStTl6j;J9?(o=Z-KhhOxB4t* zp^!cU;xVs*56JQX)nTdBf%Ya-_O9;6BG?hSfqpg&mRP}0RWyzzcfW>zXo5Zc7^}NE zI~%_e;a(&KVndM93QU^&XnYTdi;D9GpGYqQFR*|}2+uHB;LO&F0jp}l8Plf)2n_th zG`m+%bhDQd+RR2lAeQ{+b7(vWQWeMppD1m%lGuqo=?JjKiZ6F!=f&_Q#(TZnRk3bS zbv^zby%zYA(8alDSgRLp_`GAmPPye!V1Lk9zDHVu6^-k!;|vaF7-|O|wz>EBtyc z0xoD{h1#N%$;6!(M#YOXyA{czqiDlZLJ%ZK4sL@sJis@6ZsJf0K%<$97xaGeFKn%R z6Z0L|$Z2!{F_|mzX4ZXR5!W9n>*7qQ7`a>U^=Q+CKq8mW#L-d&7GOdQARIwxm1{5% zLO+xD`mxwG1TG?!wGM1SRm%uAy%6ed18ca>mu2H^+Dt>sWkR@qV+;d@b)B-yQ4ZLM z;ojoaqVNS-3x)ks9~|CtT+rz$R9Rr#3u^1q^WrRx}=MEW<~!$+3Re>4U~73!*Vy{VFzv z`IR7^N^)^mw5<&Hkl&Uv$5*uRORwVewTis4ftaPA5lxjcx=So4Xtcs9E(FKKHeflY zW_Ivx_a`3gjc_*WC;{$kaLLTMCYZ5UH8~C&W~ZBh*jQwO)k=pLHySgzZ4nXPA%I3} z6J#FZs4e~;#6dXmiH?JmRK&#s(1txSotDF+OxKQjYi`$yxbWpL1Blo$f;0Fafqi$T7uYc6~rqvrg8+0$1u&i zDzLZ=cZ1}S8CFwhLrJhqSP~Ti+@R8K^-Y25_IV$taiI)ganN-@dAK(ntI)y`7mw&4b5DQmaKEX4t zF&n4eH~==Q4iL5ib_}c{%}4wd^|Q@P8}!5zMijYT=$Nf&fGsNK360_)4VZQi#u{!f z2#NuO1)f9!A~1i{WAy8W7NLzze)kwg8-pzXzpP7x(vHF10D+gdE6dp*)pa-=IO(|? zO4sC25y*BZk-hY12h|b;Q}$U@d#lVF_S7t_FHhN9$^LDFijM=)WrO*XI< zGdhl!0!#40qp>Yi|L^w`7>NXxCQ>+GSYH2)aU;*+Ld@zHb$VBkL;+4_hPXL>P4{V_z zwU}qA<0l(&uYq5$4oeI-=jwC6ZH$beagly5eJyj?%0fiKuNwUz5d+xJd*V| zbA+;ZBH{y+hF@G{uOhp-dHx(oPAzryh$4RoZM+61YCa@bdaT~#QY(Rczwn1l8}5Hx5V z-p`P%kpR{(qRbvWqC#L9B^c^dSmE7zT7e)N(6~;*hF573(+cJSp>VUw5FZA@V!&J| zs-3wqc)$j%r10}bWX6nXRt zghCo2gw}NM0nP6+vM1Oq+`g=C`F71`DHZe4A7>#py?j%$l0WT~1 z8nJ08DcOj|1fdPb*(1Zio^W<^#-Y;^1d0D11+6Q_!;9!-G9+riR?KjhS$?hET=&&L${Q@JqBa9K_2b1f65 z%ewA{#w3rGpr6@>sg()D&;rHN)nf4puraE7Bl)#9?{%5L^qWQdOf>$jAoMs-)0cV- z%8xc){uuS?C1Xh23EG&jlrZo8(Qn{bVWMo2b(**Hzdd=F1oIbqeg1Q$3IQFZtiRI_{Ny{k~7^Y z9t5Y}KzP0^e+6G0m<*2KC@?#=(4In|s0ufUUoWeTeyMSY zfhbTA409+yB=qc|$gnBi`e0`&tq2mrmYHyaI4lPd zv0Z_mz%p{K>+^iFfmo^zh=V9KjR)b7v}gsip|F@i3|NH`NFd67ejWi4VP#z&sP{$` zU@J3$^Dylu*blIkU???kTN^+>152ur z!g^3@8L*6TF(xQDme_UKQwRj7TBHdfEG+c7Q5@|DntXRME@$>0uoag31vI}<2eRh$W~` zRb;T#PV7LK>PbL^$Hoxtr>3M<-6-j>474$qGR+T)3U3@zIoUzElgUSUM-FYAJ=5U` zl48m80SAEbFi<(B|Gm3F^9g~!d(O~qVWIcUli;x+aW+4@5xGUK-}=01R)5zDgwJ%LGuNH8968S)H!ZUx#AOYPb^k4Eo==e zeu9b{ssfTh1T*#2F||f??=U@j^iOZ{lDXJIU|cKre%D{)qkNOL%Ed zdaKGWFZnVwsNog0H1WJFfCb*-asK`$XOO2SuGL}BnqD|-u< zTxe8|99PJNqtRLnv7e@AD9p5Uo4$qr$cE?B%2E*=E!@KA(9R^X;ZVZc$I)mb52|cD z8b22LeR0ZInBWFM>P)ugEseTBB#doA>9Q_Dti@C>ci*Z>V`XZ79~>#OR@eS;!7?8U zTi%k%TG!IhR8L{U(m}+|?5ly9E~)HzXS89jEL8!hK_H;@fr4TWVRqSZbzyYYvjXWL z(S{;~xkH$RJa+i77C^Lvqw|P+p;i*Q-m?{395iC3_u+fXY<2i+1uhpYW0Q1)!9DAa z()mbTUA?7kv57ZD%?YpI1dT;_5e(QKuW4Lf%=nvt>JI{g6YQ_51X(tO0vSWrEfP}M zxJ?Ilp7mIWS?c+{+XvI*!qRcAP2kj3yWf6m0M$7ZuA?m{M_4&*JsPddJSP1C96dtU ziyEV85CL{z?sw$|44ak#OETC9P2&vE6UibVdV(md!@80)jrL@7YReMigog2!NjN!J zWC>$?E5jS-+=|9@ICxRMb0qsY{nqG5I!cB6<(m%q&I;=<*eH*8@d(;q&`gk=swzR) zARlEscR&byJ`K6oj(+wzcDsn0GQF{{`Q5bBfDw}xcOKY|jZX>QUbW#XZcOl$D>&Mr z=|=5l&&TSf4}Ws0VOi`z8!9P)g8>-{!O$nS!@)W%#9dd*e@+xSYU>v((wsOgqYMTx zQ06I)u@M81I4I!|#3IwXuEs9}qt4pMBmt@hC~Ct%kca%a5{StHU#BN*M1;7GDFI|L zHVwXsxjZ`{2?Q}q@hBRxy_+~D)OZPaLA7=hRHvuhZ2}8YY+-tfS3g4ky?NDGX9jr4?y?Wth}K(BRmK z7w-sK44WY(S`H#YK|D3^6E_2bU|!T?igh?VydB83#VYK^!2PN!ag_WMGdRn{kcqgiVOd%s{!?8m&Nt_`RNSMB@G)J(R*^xjP0Z$|Z8*uy2gEXg25U7)b zBe+wbkT8ncHI{WROZW>y3)*<~JvLg^iGoh#ZQL|92*i=M(y_kbZ4OjZH25Hw2=NXz zG7QNYP&f5l99)eZiYc8V%o_@B83+cAPOmY17YU9+O$X8At-3PqAAbB+8Kg$ooT}hf zgdn1MVGPW^93UOX2dSO7>oL_-(nsnL}_@AU^7AKC3@ULD82^ z@3|KUfi)XU4g45RV;vtBq~_y1H0~cP9`VqVQd+jyY|+ok$hOf3@gR%f11q_*K3YB$ zVBic67Q!O3WQnor_oM~YIwzD?-#R2Xw z!L7ljteT4nV>NO92#eEJ2+o)*uh?U14O9S7RncuPL#u|$y2Ws{4T3s`5MGT%O1{Xh zc1TX?DPNS~_l_4zjtAn@(^_kG&F_GV3=&bXoKA$tl++dAi?QC)m|Nk)b-R_e9;$4{t5Pi zG92nKS^NNzu>~S=aqFhMt=(umC&9|X?dZqAHk1vBj$q<2>n?M!6XU9&1>ww7hh(!( zUndAiXp9_Kz$A(23C9Dq;O=#5$U#}&mt>ihgfURXL1CaULsbdh6N}q;1KJq$0HDly zx;TfNjDh!rcj+=&g#>?fryPZF9r=ed$br0oAOe-#m0Z6r{ZvIdfG!ObCa<08wW2Fp zO@q}Sa7|U0upk(U-_Zg>ht203NEPOAOc*T$##R!6^|uEH3?&yg7&l{k$z55~NDo3# zej1TV4y#7128o*l=3%3Pt(5F@6qhDfJkZZPJNDK}IYp)&23A-=TLFc5uMzYfjO-8y zjqo#Ta*JC8?x~0=z!FtHCxDb;xcv + +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1d2VbbSpe+P0/hRd+CTs3DuVx1MDAwM8yUwzyEoftfLNmWsYItXHUwMDE5WYbAv3Lzv0a/XT9J73KIJWsoPIhAcnBWsohKkmvY37eH2rv491x1MDAxZrXaUvzY95b+qi15X5tu129F7sPSsrl+70VcdTAwMDM/XGagiYz+P1xih1FzdGcnjvuDv/78M3nCaYa97095Xa/nXHUwMDA18Vx1MDAwMO77b/h/rfbv0b/Q4rfMs6fMi/jJ4Gi4vdM/O+2ePXj1L1x1MDAxYqNHRzf96EzkNWM3uOl6SdNXuM4odbRgjGOkOOWMjVtcdTAwMWahVVx1MDAxM+1gijVcdTAwMTdYcE40XHUwMDFltz74rbhcdTAwMDN3XGKpXHUwMDFkiTHlSMLNWNPxXHUwMDFkXHUwMDFkz7/pxHBcdTAwMGIm3CFcdTAwMTIjSjVlnMJcdTAwMGbje7536K9cdTAwMWFcdTAwMWFfXHUwMDE5xFF4662H3TAyvf4v7Jk/SZ9cdTAwMWJu8/YmXG6HQSu5p91u6qZO7mn73e5J/Dh6M0wyTOhS5v3nz70nmetlT8FcdTAwMTfedFx1MDAwMm8wmOhr2HebfmzmXHSj5KrpXX+nNVqvfyV9ityet2NcdTAwMTYsXHUwMDE4drvjy37Q8swyLDXw6sTXXHUwMDA1reevm7h94HnmXHUwMDE1WGqhmNBajltcdTAwMTLZYoRlr+6HwUjOMMZSaiZZso7+oFx1MDAwZVx1MDAwMlx1MDAxNo9e23a7XHUwMDAzL5lp04mNlPAlg1x1MDAxOfZbbvzcXHUwMDEzqZVEmktcdTAwMTCfcXvXXHUwMDBmbrNd74bN2+RbRle/LVx1MDAxN8nzWvuG31x1MDAwN1Su3LVcdTAwMDK+o/fj6/OjxtTybORcdTAwMTEkjCshiEJcdTAwMWOLSXlcdTAwMTbSXHUwMDAxOeSIM0VcdTAwMTFlOifPXHUwMDA0cVx1MDAwN6U/Oi/QTDtMpz6qcnk2n19dnjdnkGeGsCSkWJ4xUaRMoFx1MDAxNVx1MDAxMrCKSuh55Pm5IVx1MDAxMcCUXHUwMDEwqo3j+m5jj52s99aH+mnzYkfWg/GAJlx1MDAwNDH2vsZL44Zvy7bXdraPY7Vz2Ph7fTO87uzqlfP93fPi17pRXHUwMDE0Pkz73i+H+mitXHUwMDFmXjzcXHUwMDFkNI56q9Gh3jvanu69zz/9JHDb53VyTidxrbFDXGauQVFcdTAwMTFcdTAwMTBcdTAwMTY5iWtAPVE2XFxjhVx1MDAxY2XwTIFcdTAwMTQklypcdTAwMGZrwqvFcVx1MDAxY7nBoO9GIG8/XHUwMDBiy/iVsLw1XHUwMDAzllx1MDAwNeJIg4YqxDImulxmy1xcaIWVlPNAuUrpTYTRXGIhXGZ/J4i9qFx1MDAxZnbdXHUwMDE4eltcdTAwMWLE7o03SC1oXHUwMDE4xCf+kzfSXHUwMDFkXHUwMDEzVzfdnt81KyAmXrna9W/MXFwsNT3z4qX0fMQ+2H/jXHUwMDFien6rldZvTXip61x1MDAwN160M42iXGYj/8ZcdTAwMGbc7ulUw3CHcXjsXHK+XHUwMDBmJI6GXnrGvO2xLedcdTAwMTBugfcj6Vx1MDAwNadcdTAwMDeX9/xcdTAwMGV1+q3VY8L2XHUwMDBmTqeBd8pcZiWwZinGN1x1MDAxM6g0dTCjWlJcdTAwMDH45lwiaVx1MDAxZMObUIem4Z0g4XfC92vp6k8z4JtcYqZcdTAwMTXYnzwtuM/4lih3dWx7XCIw/Vx1MDAwNVx1MDAxMjS5471cdTAwMDD89KJWPzmsNTtcdTAwMDCuKoDd9dqxXHUwMDA11nHYL8P0RK+zXHUwMDAwLu5mNcA9XHUwMDFj3l6hvfBp8PdcdTAwMDB3O43zi2NcdTAwMTHJqY1uWFx1MDAwMEdhQoRcdTAwMTBcZtZjXHUwMDAyu+BXgm7mSDAhKVx1MDAxMSqlecfgZdzhXG7kXG6BVGGResFcdTAwMTi7PFmBl7GrhPK0+CfY2EfFuE0sxFx1MDAxZitHn698Szr/XHUwMDAzzuApaZj9nF42vEtzSnxcZmdcdNa6UmQ+fW2zZYOzL4ct0jvbW/eCXHUwMDA2WemJ3Y2jp8VN72iNXHUwMDFmrsjdTnBANvTpevP8Llx1MDAxY2y+R1x1MDAxM9k+fotcdTAwMGWVoFx1MDAwM1x1MDAxOTcmMuNScK4zONTsJVx1MDAxY2Lxolx1MDAxMmVcdTAwMWZALFx1MDAwMuLx9FxulGCKKYL5L0RcXCrElkVcdTAwMWPhsG6a8/dnIW+6zTiMXHUwMDA2f/1PgJdhWWpsuaaWwVx1MDAxM1iuUVKoUFNhmZ9hKb+g3bKKdrrhVKN47bSUoaRJuCvsXGJtsMxA7abx/Kx26Vx1MDAwYnBHjlx02UIjYVhiXFzoXHUwMDEzXHUwMDBi5CDFNMJcXFx1MDAwM72QmUK3b4T+ljvoeD9cdTAwMTn+JzPAX3Fi5rLIPeYpds2gX1xiglx1MDAxOWbiNVwit3ou8PdDP6vNk59qyVSP/jP++V/LxXeXyqL5rOTEMHlfTjmDZ1x1MDAxYsVrsDh+cDNpXHUwMDBiPe+kTMNcdHB3L2yN5CmMXHUwMDFhfpxuaPtfvdahXHUwMDE5/cTgzSA4Qrhk3DlcdTAwMDPMXHUwMDBiWkkvJyXFjGDVIL7juTmxg+fSbeCG+lx1MDAxOZPc6zbCh4JcdTAwMTVcdTAwMWJ1/MS7+SFcdTAwMTf5L91cdTAwMTmc9L2m73ZcdTAwMGK+tLStXHUwMDE33nt7/vdcdTAwMTlcdTAwMTmc+3HnWfSmsHXs8VBcdTAwMWL7XHUwMDAxq9ni/Fx1MDAxYTvAj0aWTFAptas1jvNL4VxiRMCPVUhTpZLnx9yHXHUwMDFjSlx1MDAxOFx1MDAxM1xuYIdcdTAwMDVcdTAwMTeo+jD/77BtdTo99Wlccis1sfuUUFx1MDAxZqWlQX4wS5HkYDQlymdh7lNcYlx1MDAxNlYzmtaLb8V9K6WyOGrNieHi3GePXHUwMDFjzsd9XHUwMDEzQ1x1MDAxZVx1MDAxMWE13FfEb5NcZjhJVCXct1x1MDAwMEnZN1dsJFx1MDAwNYqMO1x1MDAwNJxkbsRcdTAwMWUoq4CkXHUwMDE04983LVx1MDAxMiU85ijBXHUwMDFk8LMxyFx1MDAwNTP6P0FIwlFcdTAwMWacVMBJZzOYY4grLFxiXHUwMDE4Q1x1MDAwNaRESGn8Q2IlXGJcdTAwMDVe+l05qUz2Mo+/REFZK+JNyFx0T27pIzQ5mpfY6lx1MDAxZHLS7Tq5XHUwMDE41Fl8NVx1MDAxY6qDcO3pcvUpKNhoKYnXgqZxXHUwMDE4xVx1MDAwNHxcdTAwMDBcdTAwMGVOXHUwMDAwy+y2YPBZJUNcdTAwMTgxijjIeI6YjOcpQEIwmF+gtnSB8UTBYkdMc2RyiihcdTAwMDeOe/+e41x1MDAxYjDV54VcdTAwMDO4RIF1ZPbEXHUwMDBi+Fx1MDAwYlxm5PJwXHUwMDEyo0A1Ku2IVlx1MDAxNMDtKD+6blx1MDAxY1x1MDAwN4/bW9dbu7c7X/Vnd23xXHUwMDAwrvs5OOycrq9tnZBe/bS+XHUwMDFl99TaVvFrZ1xu4Fx1MDAxYdrVwPRcdTAwMTQnKmChXHUwMDAwrn38llx1MDAwMK5WzKGaKylcdTAwMDCWWGuWgaVcdTAwMDBYXG6mwFx1MDAwNpSSqbxPw7QjqfmYVzCRviXZXHUwMDA0/YjfXHUwMDE24fB8eotBwdowpFx1MDAwYr1cdTAwMTiVz3pcdTAwMTjjTYNcdTAwMTmPXHUwMDE4mStVqUrJTVx1MDAwNPE53LlcdTAwMTG4jbSGeLs47Vx1MDAwYlotXHUwMDFip812vJqI7Omd6oDBs7Xh9tXjdeess3Z54s+eT1x1MDAwYlx1MDAxMkJoemVG88aRI8Gs4lx1MDAwNINiJTqP4feQT+vylvr1IX0xi1x1MDAxM1x1MDAwMI6aJunko1x1MDAwNNNC0ezVZEtGgzFcdJZSxZGJXHUwMDExqFNMvdh+4uOOwndt9Zntb39cIlx1MDAxN+7R6lE0nN5UZNgxhlwiXHUwMDAyjcNcdTAwMTHJiLPSjtBcZnHNJDLZ4zlxXHUwMDA2QXbSqbIpfixPp1x1MDAxNVx1MDAxNcvz76GiLmfJwdOMM5DNolx1MDAxY1x1MDAxZFxmdnuZQFx1MDAxM1xmsmzqXHUwMDAwKjdcbsVcdTAwMDV5XHUwMDFhbqjO5sHaIKQqfFqNXHUwMDA2f7+pUWh975eocbZ23FhZP4zu7878XHUwMDBidiGu9qs0NqtCt31ebcYm+IClwJZcYjua2YCtXG6CUb9Bgt1rJdBeTVx1MDAwZl7gQi4oxoX5s6w8TFx1MDAwZUyNtEjn5b2RLspcdTAwMTmY9fVaw9zyXHUwMDFlkmZf0IZZXHUwMDEzM9/1aozMvZZUXHUwMDE3+nz1VNdcdTAwMDP/PNLxlUc70+fbMepcYoNd9Vx1MDAxZLuJ1P7Qylx1MDAwNrXoXHUwMDE5vIlxsohWTm75UMpcdK7dXHUwMDE5lLJSXHUwMDAyLHtVXHUwMDE4qFx1MDAxMaxcdTAwMTTXYJxcbsxcdTAwMTWuXidcdTAwMGauVp+6nd7h54N24+q6fbPbXHUwMDFjXHUwMDA07cV18lx1MDAwMrrT+t6bu83Nk0/tLbGFVm56+497V+crjVxudTKZKe5uXHUwMDAxt31ebVx1MDAxOXySOoQqKimVmCM6WeQyUsrchmspXHUwMDFjKYSQXGJGg8CD/ChymVx1MDAwNcuNXHUwMDE5sIw0kuD6XHUwMDE1OoyYcVx1MDAwYpjhISTFa4SBZpPfnJZu/99//vdPVlx1MDAxYnT8dPb622nqXHUwMDE3VGRWU1x1MDAxN3e/XHUwMDFhbb3d7O5ds6143+1cZleaq2ir8XnFnVpbMyknLO1MrkrWic7D+qMmtSqIN6eHONec4lx0rZtCuCYqezlcdTAwMWRcdTAwMTNcdTAwMTJEcl29ut5e94afXHUwMDFlb3tbjYvL/fud/kEjOlhcXF0voFat763rXnSng/teXHUwMDE339w/tL8+tHdcdTAwMWb33qNcdTAwMGJtn1db0ZpcdTAwMTY2YGed6Fx1MDAwMjtcXFx1MDAxNFx1MDAxON5cdTAwMWZcdTAwMWG6XHUwMDE0vq1cdTAwMTk0NEVcdTAwMDLULCneXHUwMDE3leUhMKwolpKQ+WrKX9eP9pp+771Vob6gXHUwMDFic1x1MDAwZXX5XHUwMDE4qtHVvZOzzue7/k33cmd3Z79/XHUwMDExn8SbfCo0U+IgwUA2lGJIcj5ZxyYwc8xcdTAwMTFcdTAwMDVCXHUwMDBiU2IjXG5i3YQ5zGy/MlOIrrgu2n799cH9Wrr5Zlx1MDAwNt3MYY1Eusw/wbampVx1MDAxNeagzFx1MDAwNVZcdTAwMTN+VUXQxohcIpZcdTAwMTbXWaF9/GuUoFx1MDAxNnezXHUwMDFh4NpD+dY0S7CI4d0mdc5cdTAwMWNcYpJO5lx1MDAxZSVOIP5CPlx1MDAxM3KwqYOERVRaXGKNMC3IaDI1XGJSa440XHUwMDA3+5pcdTAwMTP2XHUwMDBiZDS9RS3Ml1n2qThlSqNCXHUwMDFjI8sxRlxmXHUwMDAxiLV4P6HuapMvLfI4as+LYvLKnO08ZU64Pfei9pPrYcp6aVx1MDAwZt9X1Mt8uqil2/+cYp2jT+s+2vXq3Wh4dsc+3fH11bDAsio9I4CZ2KY2me5cdTAwMTRcdTAwMDPuJzna5Jxqjlx1MDAwNdZaUc5cdTAwMTP+XHUwMDFkXHUwMDA3QcD+MlXNnCmEwfxcdTAwMTJcdTAwMDWZMVx1MDAxZqdcdTAwMDRcdTAwMTQy8m0xI89ySlx1MDAwMMVmd5FcdTAwMTduXlx1MDAxMExLqVphXHUwMDA15DWnMzXuX0F0YYWc7+1cdTAwMWNHT/XHK1x1MDAxMoWXfuvk6SxNV1lTf9xgXHUwMDBmWlx1MDAxY7VcdTAwMDdb267Hn64/0/OGqp/fXHUwMDA3XHUwMDE36j3uMdjHb9tjwMSh0lx1MDAwNC0kpzqd6TFOMrXiXHUwMDEwa+VcdTAwMTRcdTAwMWPQ8XEuQCH0utNcdTAwMWJD4FmC36h0LtvMXGYpjaDsuVmKw3yiKlx1MDAwZs6qaEdhXHUwMDBmdLGpol977LuDwfL32Hxt2P/xUyt8KHZ2fnLa6Vx1MDAwYoot61x1MDAwNM02rGqcIzsv2Zwjo1lcdTAwMTlh5rRcdTAwMTYhKUZcdTAwMTm9a3wju951sGJwh2ImNX1cIvCROyWAUHCwXHUwMDA0YrNcdTAwMDQ5/kGeUW+GJCBcdTAwMDGeqix0jLBkpVx0qdRcdTAwMWPlIVx1MDAwNHpcdTAwMDXHiMi5kswrrkorXHUwMDE1xlFzTlx1MDAwZXN2+8xukZ1cdTAwMWJ+tlv0XHUwMDBmPSagcY3r9Vhuy8ujrcbGWexdf5LTXHUwMDFmXHTMgeOYIJRIpqUmkmfCQ1x1MDAxOI3OXG5cdTAwMDDsXHUwMDEwhNXH8WTVXHUwMDExXlCB46E4XHUwMDAzNFx1MDAxN2cyp8srcuVthFHCOa58XHUwMDFi1ttcdTAwMTen3FtcdTAwMGKlqIefLlx1MDAxYfuPO/Hd+uKeR/9cdTAwMTI3TlZ2j46C/WPqtY+e3G1cdTAwMTlX6HnQ1E7YQp6HffxcdTAwMTbPg1x1MDAxM2SO7DCuO4a/uVxiXHUwMDAwf1x1MDAwMYZcdTAwMWank82Lw3CWLEVEwPJghcd7YipLM5ukyXug88HtVSyPX+Vwslx1MDAxN5TbW1x1MDAxZU5m5ySb11x1MDAwMfB0sJFcIqE1XHUwMDE2XFxlvVx1MDAwZWbHOnI0ZVxuXHUwMDEzxFx1MDAxOFwiWOmiYF/mTCj84XRcdTAwMTRivz899lx0XHUwMDA1L1HwwpQnbUG+xliZqqHqoT+b0nolp6NcXFx1MDAxOEfNOTnMXHUwMDE58zM7XHUwMDFkdkr4cDp+itPRf1x1MDAxNP2VePf0yX98Wlx0XHUwMDA2N5u0j6Y7mVx1MDAxMSNcdTAwMDXESkyxXHUwMDBlXHUwMDE3xnGYZD+JmcNMXHUwMDFltzJcdTAwMGWJkoV53FgrRTHlXHUwMDFhcEVcdTAwMGLYb3S8XHUwMDA2Zlx1MDAxOFMwpaSpXHUwMDA0Rlx1MDAxNVdo/EapJXfT+iEkh4Txb9xcdTAwMDHvXHUwMDBmVlx1MDAxM1x1MDAxNUVnYaFF9vLYXHUwMDBmMedzmi2vytlx7pyTStmxVFRHT1x1MDAxN1xi6fTsWH500Ls/McxaXHUwMDAxY6NcdTAwMGVNuaUqXHUwMDEziINaqzJ/ZIGr5yTwgkRxWFx1MDAxM4zJOFdcdTAwMWMx/vH7uFxuSSOa3nDS5uQuc+pKXHUwMDAxOVx1MDAxMFp6pivmmChN6Fxc3DDRj3ebyVIqkKPHXHUwMDBiRHFx4+lcdTAwMTVSRMBcdTAwMDRcdTAwMDTjXHUwMDBmYylcZvDMoUmcTVx1MDAwZcREXHUwMDE2U3/yLDdjnou9+GWuQeDsXHUwMDE4lHVcYvljJl8j52XCNa3WXHUwMDA2tPCzveTBeug2lZbK21x1MDAxMUFbK/RcdTAwMDBcdTAwMGb8e1x0z+gjeOG5s1x1MDAwNlx1MDAxNvxH8lx1MDAxMVapKOdcdTAwMDdBJ1x1MDAwNFx1MDAxZM9A0NpQbXpjMkXQurSWR4HZXHUwMDA3xs1cXMV6v1xmP5fI46i5QFx1MDAxNFx1MDAxNyfoV+C2mVx02sZuU1x1MDAxMbS97GFegiaagMP4/E9ytmjRXHUwMDEwVH4lflx1MDAxZoK2147ZXGKaSWytyTK+tzlcdTAwMTmnlKFcdTAwMTVytPltlIhQsOlAklxuXGL6g45cdTAwMGLo+H6WTVx1MDAwNkaEQMWJ3+lcdTAwMDT9XGZcdTAwMWYzs2RIonl+XHTCr8HHpdKXeXxe7n1cdTAwMDXaerWTvyuKLE7nx//xPJlLbr9/XHUwMDEyg3CMJ3Hp3vdcdTAwMWXWyjdcYv54XHUwMDFlnoGtN1xuK33749v/XHUwMDAzf9FPsyJ9Interpolation stagesTX DSP chainFactors:1, 2, 4, 8, 16, 32EnableDC blockfâ‚›/4 shiftDecimation stagesRX DSP chainModes:Bypass, shift up, shift downFactors:1, 2, 4, 8, 16, 32 \ No newline at end of file diff --git a/docs/images/gateware-tx-only-basic-block-diagram.png b/docs/images/gateware-tx-only-basic-block-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd1639cb80bcd1972f0980927a186a5711fcd7e GIT binary patch literal 137152 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 OhulR)>1@f{?*9jG19NNu literal 0 HcmV?d00001 diff --git a/docs/images/gateware-tx-only-basic-block-diagram.svg b/docs/images/gateware-tx-only-basic-block-diagram.svg new file mode 100644 index 00000000..61aea1e2 --- /dev/null +++ b/docs/images/gateware-tx-only-basic-block-diagram.svg @@ -0,0 +1,4 @@ + + +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1dWVfbyFx1MDAxMn7Pr+BwX2NNV+89b1x1MDAwZWZcdTAwMGbgXHUwMDA0wnbnXHUwMDFljrBlW8FcdTAwMWK2WefMf7/VYpFkLVi2SFx1MDAxOFx1MDAwZc5cdTAwMTlcdTAwMDYsud0tVdX31db6+9PS0vLkbugt/7m07N023K7fXHUwMDFjuTfLn+37195o7Fx1MDAwZvp4iFx1MDAwNn+PXHUwMDA3V6NGcGZnMlx1MDAxOY7//OOP8Fx1MDAxM05j0Hv4lNf1el5/Msbz/ot/Ly39XHUwMDFk/MQjftN+9qRzRLfvrs9aldr55lmr63U69DD4aHDS02RGXmPi9ttdLzx0i+9cdTAwMGKmXHUwMDFkLqUmwFxyVVTI56N3eLTCtUOEMvjPXHUwMDFl44w+XHUwMDFmvvGbk1x1MDAwZZ7CXHJzJDFcdTAwMDZcZlx1MDAwM8KUXHUwMDExz2d0PL/dmdjVanBcdTAwMDRcdTAwMTBgYLTSXFzw51NcdTAwMWUm9OdcdTAwMTJ5fmc8XHUwMDE5XHIuvJVBdzCys/5cdTAwMGZ49l8453O3cdFcdTAwMWVccq76zfCcVvBcbs9p+d3u/uQuXHUwMDE4XHUwMDE5LzJe0OWp8Y9cdTAwMWUnT6fez/pcdTAwMTR+YbvT98bj2FxcXHUwMDA3Q7fhT+x1XHUwMDAyXHUwMDEyvmtnN9xsXHUwMDA29+t/4ZxGbs/btDesf9XtPr/t95uevVxyy6eT2Lf1m4/fXHUwMDE2O3vseXZcdTAwMDQuhKRcdTAwMTJYeFx1MDAxZEPJXHUwMDAyzlx1MDAxM2/vXHUwMDBl+oGYUaY5lYqFc/XHNVx1MDAxNK9JMGrL7Y698DrbOaxGRC9cXMrVsOk+fFx1MDAwNJQmRHIuNZXht3b9/sX0zLuDxkX4LcG7/3xOk+bRcCCkX/0+cXt3o9stX1xc/ty4nFmaUYJcdTAwMWRcXJ9cdTAwMTE4J8VcdTAwMTLSLIgjQYNcdTAwMTJaXHQqjVx1MDAwMJFcdTAwMTBnXG7E4dxoglx1MDAxZlacgNBp8sxcdTAwMWSiXGKj6nGcXHUwMDBmeU7K883s8lxmkqBcdTAwMDFiaIZSXHUwMDA0WmuWJc/AlVJGXHUwMDEySstcdTAwMTZohWpcdTAwMDTlXGL05Y/zmvzaOrrbv9lf/fG9PoZcdTAwMWVtzSzQhmhHXHUwMDEzNL7mQaBFXFyggaEgKmE4x4tAUPp1QqCBUkeDkIZcdTAwMTE0vYpcdTAwMDMkXHUwMDA1WkvHXHUwMDAwxXHAXHUwMDFhci3LN9BcctMw/3KBvl2fXaKNNoRcdTAwMWJQJkWgXHUwMDExZjNccrRCRbDGdFx1MDAxZXl+PFx1MDAxMFxuYERcYpubt+eN25VKf6d3WIP6tlu/7IX3IyaI7mg0uFl+PvLP57xx+fBgtXa8u/al8XWdTepcdTAwMTdmZ6VeT1x1MDAxZnfi3U5mXHUwMDFkdmN1u9JcdTAwMWXsgFdpfTv1XHUwMDFhTd1cdTAwMTGVXHUwMDE5p/v4W45yS1xuXHUwMDAwpFx1MDAxY+XOX3987XG9ZsRRmlJcdTAwMGVSXHUwMDE4gqBcdTAwMWPTa+EwhFx1MDAxNo34hEqJJyRZl0KzIPGlXHUwMDE1XHUwMDAxUKBTUEqQcrV4MnL746E7Qnn7t2vyYVx1MDAwMWxCUqvxO3lcdTAwMDKFXHUwMDAyu1x0WapcZlx1MDAwNJmEQcqr59HlPPFFaKJFxDeURiuFuP5adeWvvt+feKOW24jc89agP9n371x1MDAwM0NEYu+uuT2/ay++jFxyVu36bXtcdTAwMTmWXHUwMDFinlx1MDAxZG05ei0mPnoyzyf0/GYzXG5tXHJcdTAwMWPU9fveaHNcdTAwMTaMXHUwMDFjjPy233e7XHUwMDA34Vx1MDAwMpZS5u9eTVx1MDAwNt+98cNcbiajKy96kbyNJ71cdTAwMDCHilx1MDAxY5Xec8l4TZ9sn8ijterd2d7JeOvLzcx4XHJEK4crJlx1MDAwNFx1MDAwNfxP6lx1MDAxMFx1MDAwMeJcZlRJho5cdTAwMTXh4Vx1MDAxNU0wUCaJYFxuSKpH9cRAn8b5oKBJPb+bXc2loFxcXHUwMDEzodNcdTAwMTgoRKcypeZcZum/Mni3SmegPDroQiDl1WuXwr8+r427O2v7nFxm1EYtJUDwgKQxaZbGilx1MDAxOaM4XHUwMDE1hmyGx9knopOD+IRXTkmpXHUwMDA0ISohy0FsgFx1MDAwMyP4ccmUSeGe6LEhe2XCaG5cdTAwMThcdTAwMThpXG5IMnIlz8jfglpNd9zxfq04388uzmhDhNaKitRcdTAwMTBcdTAwMDFVmbBlPyiFQlwiWrZAS4hqV1x1MDAwMdhcdTAwMWFcdTAwMGX8aYZcdTAwMWL+tlx1MDAxNF7s4I/n3//3OfXsTIFcXPzTlYQkh8MlWOl44o4mX/De+v02Li3kwE/hvlx1MDAwN2Akl0ffxydDz9vaaVx1MDAxZmxfra2uTX6sRojwcm/QXGbEcTA69yfRXHUwMDAzLf/Wa9btpYtduSWEP5y9RmZCXHUwMDE5OoOE6NhcdTAwMTVwUNWRd+JcdTAwMGZtg3WSJtbwzNyXvX7z5Vx1MDAxNZyd7t9vK1mpXTSPdy4nZEf3XHUwMDBlq4utwE7SMCaQXGLh3WAglfpcdTAwMWNfoMTpXHUwMDEzqVxies5cXDCes4bgPlSt5et4bkL3cIXRY2hM/CnM97rng5tAaGOEI5j7vtd+0oy4jtrv3Fx1MDAxY+9cdTAwMGa9hu92U75z+liObb9yx+7N6uHJqM/94dHq171TVJ2Z2VxuMlxmh0lcdTAwMTHgjaFgWNxccrHhXHUwMDA1oyVFT1x1MDAwM1x1MDAwZqE/YpLxsjC8XHUwMDAw3KpFhPCE4Vx1MDAwNeVoXG5cXMDDMKFcdTAwMTUoh6uc01x1MDAxNj0//3dzXHUwMDE1d292465cdTAwMTh6jMZAgpRcdTAwMDRcdTAwMWWJnn732SNcdTAwMTFcdTAwMDSdTaUjYeOSolx1MDAwYt+q+6fX3e5pd91cdTAwMTO7ZP1rrb/75XTxKMCXoYSd9sl6b6XfXHUwMDFh3a70XHUwMDBlflx1MDAxZZ2dlFx1MDAxMLR41ehcdTAwMDLapWhcdTAwMGVkIeKWf11zolx1MDAwYqiHXGJUknK85VowmOJt6KFQzvEkPFx1MDAwNCTqPDxptbVcdTAwMGL2RYlcdTAwMDSF3C4tXGJectT73YRcdTAwMTfc+uyqjHdHaIZUXCKNpymaXHUwMDFkXWBKU/wknytSWKb4htL46Jzvr9c3I8bs91x1MDAwNVx1MDAxNV6AxumgwtS8y1x0JuSzn5fgWVx1MDAwMXWEsv49gEbaOVx1MDAxNUtAdKaKXHUwMDEy9LtcYrJNyZNqjCbf4UTbmCc6aUalhVx1MDAxMlx1MDAxMJzRXHUwMDA2IDJIxH8jdahIXHUwMDFmsf9nlf5WXHUwMDAwnZVkQHgkJ1x1MDAxNWp0ip6H8UK0tpJxKFx1MDAxZJ3pl+vOlu/vbfL7g1x1MDAxZjeuvzG+3TxaXHUwMDFjnV8rpVDb2dGdXHUwMDFmN8c9Ksff+DfSOb1cdTAwMWJccktcdTAwMTg3P1xukjvuy6hfZk4h/37loL6i0kHf3qZcdTAwMGVcYnqgLJ78RtRXSNSRiFx1MDAxM6XQPU1cdTAwMDZrgDKHoTkhaHFshFKnRGs+UD/TRFx1MDAxNEgqIEtcdTAwMTeUKJpcdTAwMTZtZJEo27SN0Fx1MDAxY1x1MDAxMVx1MDAxYUWuZNCXXHUwMDAwplC6O1x1MDAwMfpcdTAwMDfHS7X9+lKjg/j7XHUwMDE2sP9cdTAwMDXcncb+9OmXlE9o9C5MRV+snVS+XHUwMDFlkWHnpNW6c4vlXHUwMDEzmKRcYiqGXHRGWLJcdTAwMDBcdTAwMDCUhXZcclLbQoCUXHUwMDAygFlIwEdcdTAwMDHAy1x1MDAxYX4+u4JcdTAwMWJjY+ZCpbnoSmXm/1x1MDAwMZDOqVfgXHUwMDAwrePeXHUwMDE5r21vVlV3p3V4Nmx+XHUwMDFmy/5MXHUwMDFjYFx1MDAxNuwzlJWUqsifZ1x1MDAwZfZcdTAwMDFw1EPNlEDmRY1JeLxcdTAwMDH2gWKaaMaS2MeRadt8upBcdTAwMWNNIScyqSNcdTAwMWbQl6VcdTAwMTiNXHUwMDAyXHUwMDBlL1x1MDAxN2jIXHUwMDE0TYU+aVx1MDAxMvUyYalcdTAwMTf6J8Bp6bWLhaU3gX216spbgLxcdTAwMTdwJiWHXjbSddarw7X7U7g9ISdb123drzY6tVmUN6zaZFpCJDlcdTAwMTVAnHVQXHUwMDA16i6VXG5cZqE0XHUwMDE5g+bCQZXHXHUwMDE3ZYpIXCLSXHUwMDEy5lx1MDAxZsqbobx+XHUwMDAx5Vx1MDAxNURcIlx1MDAxN6EyzbeNXHUwMDE2TCaiVUYh9aCR9O/CyquMkYZwXHUwMDFkLVx1MDAwZp1DeXdWfpShvF2vNclR3clgmKW3sclOK2lsduUoabUy+rruXunhXHUwMDEwXHUwMDBltzfOXHUwMDBmvtfHp7fFXHUwMDEzRviDo1x1MDAwN1x1MDAxM1x1MDAxYc5AXHUwMDFh0YFcdTAwMDRNqOZcZpVVKJVcdTAwMTKRouBIQVx1MDAwNVFcYrKC6rR2XHUwMDAxYVx1MDAxY0VcdTAwMDVcdTAwMTCKjlx0cl4ovb76XSSMfs6ut1xcKim0Sk1cdTAwMThBNFx1MDAxMjCtt9JmWVx1MDAxMSfnqq/O46NcdTAwMWLrXHUwMDFiP7bWmrdbo1x1MDAxM3Laud+XP6/pZCY++jlvWH5M2+JwZWV44v0gXHUwMDA3q3t393ftVvqwxWM83FxubEldXHUwMDBl+evPgUpkpo5VXGZBKENPjdO4XHUwMDAycuKgXmomhFCcccZcdTAwMTJcbshcdTAwMWUzO1xmzSeApFx1MDAxZjGeXHUwMDAyOncxu85cdEBnQ0GGzrFcdTAwMDSCPumcsE4+46JsnovSi+Z5XHUwMDExqNyvb75cdTAwMDWe+1x1MDAwMoAl0jrRWZdcdTAwMDOh+UU5LzfcKUei30GYXHUwMDA0zqJcdTAwMTj5IKTa1kqgXHUwMDBmaoRcIlx1MDAwMmSy9PstJHXeXHUwMDA1gvZcdTAwMGIwX0lcdTAwMDXTQrFEdYW92FwiU5tcdTAwMDG5kP3ofFx1MDAxZEp5UHd43K7y6ztYa1Uru1d+W55V6dXiXGK6QJbktZA5d1xctbk7+tm567fO6u6WafRcdTAwMDa1853GW0T8/PuVg/hCMocjoFvnXHUwMDE2tXmKcCuHRStwk3hvPlx1MDAxYUXmt1x1MDAxMINcdTAwMDJcdTAwMDXkxCBoc57mXHUwMDFha8h2jVx1MDAwNUXfWMvS20RcbstuXHUwMDFh3i9Z7Fx1MDAxZFxyun/1R17bXHUwMDFmI26P31x1MDAwMlx1MDAwNXhcdTAwMDGAUyjA00KWUtZRXHUwMDBlKdg9uV7dVVfnK2ZcXN1ru1x1MDAxNXHXXTVcdTAwMDVIgXaQXHUwMDE1olx1MDAwMFx0ooUgcVLwXFzpXHUwMDAxVFx1MDAxMilcdTAwMTlJiYHNWIdcdTAwMTmwXHUwMDAy+ThM2Vme98Bcbs5cdC+Q5lx1MDAxMVx1MDAxYVx1MDAxOZZWaawgr2tcdTAwMDTQgdNcZq136Yme+2q9W7tgrcvL3YveNdnj1fX13bdbivlaxVx1MDAxZa9KXHUwMDBiXHUwMDE43vBIw+ZCtCD/fuXRXHUwMDAyzlx1MDAxZMM0XHUwMDAxQ0BcdTAwMWGlp1x1MDAxYkhpkO/S0iD0c5PkXHUwMDA1QIxcdTAwMDOWXHUwMDE4cHRcdTAwMGJcdTAwMDVDT+ODXHUwMDE4XHUwMDE0MFx1MDAxMqJIylx1MDAwYijaXGLC0kJcdTAwMDHGZFeESVx1MDAxYlx1MDAwN2DczGUkcms8XHREMtPz1nguuf3mX/1Wd3DzhK5vgVx1MDAxYryAw6lVn3YlS+lcdTAwMGIph1x1MDAxY+Sbz7xcdTAwMGU8ro1ccqnbflEwXHUwMDEygMaLuqhxtFx1MDAwNlx0RFx1MDAxMqJcdTAwMTghPFx1MDAxOS7gXHUwMDE07Vx1MDAwNFwije2JMUhcZlJcdTAwMDJ+xGFCcVx1MDAwM0QowZlBb6JkZvCOXHUwMDE0X1x1MDAxNlB8o9A3UzK1XHSPmeyeUlx1MDAwNYozWXpcdTAwMGKe3SbEzFx1MDAxNVx1MDAwMiy1XHUwMDA1L1Mgg08nRTFcdTAwMWMvXHUwMDAxzTN20eVXgi/N2UXHtVx1MDAxNkxcdTAwMWGQttxExlx1MDAxN8HxLcRVSjkulFx1MDAxMylcdTAwMTKLKNhGl2/W5lpChUytQVx1MDAxMJh/XHUwMDE15TXSxUWv3E6652O9wbW34z9cXJfxkT/pPOrQXGaULT+UlW/MtUNsZS1Rhismp7KnXHUwMDAyXHUwMDE1Qyn8XHUwMDFmXHUwMDA1XHUwMDA0aZKs5OPMkdx2/WvEXHUwMDAy9O9Dzlx1MDAxNzHlglovUVx1MDAwN9xcdTAwMGaoXGLN/Yclj1vyalx1MDAwMUuO7rTSmvLUgl3I3KBKck1RwUTZXGZu/mROuZY8S1x1MDAxZe2rklx1MDAxNMXFLXl+8mcuM0hcdTAwMWMz9YpcdTAwMWJBQUhy4lx1MDAwNa33K7Rxk0KzTFjnNFx1MDAwYjy3jV7AmG5cdTAwMWXX709cdTAwMGUre17728aP4y3d3NeHdzNcdTAwMTlTQMFcbsJZRFx1MDAxYkmpijvASIxRSTjyXGZcdTAwMDQ3uz9h0ppcbuKgXHUwMDFiJLgtnea2XHUwMDExL5VcdTAwMThLZCN24zXNiVaRnbs+rGncmq5ntD9EVOLxZtKkdD5aWfRf8Fx1MDAwNZBcdTAwMWFOU5CdZbNmUUP5SXM0XqDnXG6il2pnK5miXHUwMDFhXHUwMDFjTUrp7IZ2mjBlXHUwMDFjmcN+xEcu33Lk95/lWVx1MDAwZUNobE/FeLDdXHUwMDE2yCiuXHUwMDEwuzSyXmJ0soiNMUcjsFFcdFx1MDAxNKVcdTAwMTWdqjTLgZZJMYFcdTAwMWW7oFx1MDAwNIcrO9j+jkzHRoEsXHUwMDFiVczqQFqWXHJyNlxu1XaLPVx1MDAxNt0y4X1cdTAwMTmITIlcZo6mXGLj4lQsf8+2X8Bpftf2M1Bolm/QcuYnXfIsp1wiMtqQnjCc1G5MgpRLWv812WTDjGOzJ1x1MDAwMu0u+r+gU1JcdTAwMGVg5dgoqShcdTAwMTAweKJX+eBcXFmGc3t2w0mJIULI1PolJjI7TjXj2lD5bolVpkRcdTAwMDZH04RxccP5XHUwMDFhO2KVbzhfIeD4rzech7VDQfdcdTAwMGW/NM9cdTAwMGX863bj5ujn3uF90nBmt/Liolx1MDAxNWF2/1ZBpzenp1x1MDAwMl1Wg/ydXHUwMDEyxHEpk1x1MDAwZSu1e4dcdTAwMDa5O6FtXHUwMDAxR4rDXG7KMTYgg4Mj22FFon+pdvG99vKek5PZbSc3XHUwMDEyrWdKwsZOJVx1MDAxYadNsE6Bt5lqVnq/PouFXCJcdTAwMTYqQKjLjVx1MDAxZd+ofyX3d3u1q+3at9HwZqZOXHUwMDA0XHUwMDAwwlx1MDAxZEVcdTAwMTlcdTAwMTOGojU301x1MDAwMVx1MDAxOIFUgTMpKDrmeO1SWoFcZnPAxtvQ9GpcdTAwMWHdMOWjXHUwMDAw4WX5bVx1MDAxN4leS22j1ynia0y29FL7XHUwMDEwhPkqlHJlV5DFNpiyuz837DlLzUHP9ft/9TdtyUDf7cbeLqNcdTAwMTjhldr6XG6voJwqXHUwMDA0Q/zNzbPxz9FR62qwf7x174trOlOtXHUwMDEx047NSlx1MDAxOSE1J2bqoSpP/bnI+I2hNOVcdTAwMTFBgj7UIHOtJUqU/Og5KqLpnVwiXHUwMDE1XHUwMDA3zCA3YKnd9Ty74lx1MDAwMFDZXHSa8TfYoLtWX6++YVWOT69cdTAwMWM9/drouo3xxvnWceP05rJd0Y29085MkFx1MDAxY6ZEwFD7lKh0TaWSmyBcdTAwMDGdxGRCXHUwMDFkavd9NEF41KSB8oeqZqpqkV56y3qMplwijVVKmq2qXHUwMDAy7EbalIZB57eiqlx1MDAwN/ZGNjz/Olq/9+Y0NnWW5Shu/sayuZUhti3ebpCApNpwXHUwMDEwkeS+vUJKOsCUJFx1MDAwMlx1MDAwNEHf0SRcdTAwMTVXcO2gX4lcdTAwMTiLzlxukvE0iGVcdTAwMGXRyNaNsU+mYEZcdTAwMTZpXG6cYaf913hmxK/fZf9cdTAwMWNcbniGtl9DaZO+yz4kg23PSqzwXHUwMDE2oN9UOrvmaFx1MDAxZFwi24T+rrha7Oyk3L308UxhfoXhmCONwnshhELHXHUwMDA0UVGGw32aXHUwMDFh9jfWXHUwMDFiXHUwMDA2xXighVKgiVx1MDAwNClD0bEvcFxilVx1MDAxNGwnXHUwMDE5Q3k0yTBlwfDfa2R3okuwu1xmi6lcdTAwMTUgXHUwMDA2MXRcdTAwMGZcdTAwMTgwQDeR5SzhXHUwMDE37dqf1WnzaCtodP5cdTAwMGZzmlrylCbkXG5vTHxcdTAwMWZWkVx1MDAxOC1Xkl9cdTAwMWH9+fdki0uijjJuXHUwMDFiplxuKV/sadH3tcHwsHPNv26Tia8u91vfZn+EZ1x1MDAxOCZliqAsTz3y0D75SNoqXGKl7VZvkNJcbvdG4qTvolx1MDAxYlx1MDAwZdzZ0Vx1MDAxMFx1MDAxZFx1MDAwNqqMYmlcdTAwMGY9RI3O3NtNMmmE3Wq+ZDBcdTAwMTTAlSmpfTu/oazIM5Sm9nuYfoZSSpyUSUdcbtD2cSyE23BqUp6fXHUwMDFmPfM4StnU7lx1MDAxNWT5d1C7XHUwMDAyO3hcdTAwMDJcdTAwMTgpiEhNmNLszk6BUkeUnOtcdJ6xaaQ2dLPfnzLldIHTK1x1MDAwMlx1MDAxMUpwu/s0XHUwMDAx9GTgRe5WQe1JXHUwMDE3/ldcdTAwMTkvoUeLk8Ff8FxiJ1x1MDAxNb8nU89wQlx1MDAxMFxclFxyvkIyeJrQxtdcdTAwMTB5hlx1MDAxMyOESfX22SCLzj+DXHLGtSFXfmehg/nC/NLwr8FcdTAwMDc/PY6y7Fx1MDAwZYf7XHUwMDEzNGDPQrV87Xs3X7KjXHUwMDA2n1x1MDAxZe+ohVx1MDAxNi8oXHUwMDFj/ufTP/9cdTAwMDf5elfzIn0=DACinterfaceSGPIOTX DSP chainDACMCUSPISPI controlregistersSGPIO andflow controlDAC clock domainInternal clock domainFPGATransceiver \ No newline at end of file diff --git a/docs/source/gateware.rst b/docs/source/gateware.rst new file mode 100644 index 00000000..f560b583 --- /dev/null +++ b/docs/source/gateware.rst @@ -0,0 +1,118 @@ +================================================ +Gateware +================================================ + +One of the significant hardware changes in :ref:`HackRF Pro ` is the replacement of the CPLD with a FPGA. While the older CPLD primarily provided glue logic between the MCU and RF front end, the FPGA in HackRF Pro introduces more logic and DSP capability. This enables offloading digital signal processing tasks from the MCU. + +FPGAs are highly flexible devices whose behavior is defined by *gateware*: hardware descriptions that configure the internal logic fabric. HackRF Pro gateware is written in `Amaranth HDL `__, a Python-based hardware description language. + +The specific FPGA device used in HackRF Pro is the Lattice iCE40UP5K, which features +5280 LUT4s and 8 dedicated DSP (multiply-accumulate) blocks. We rely on the `open-source iCE40 FPGA toolchain `__ to build the required bitstreams that are bundled in the firmware. + +All gateware source code lives under `firmware/fpga/` in the HackRF repository. Top-level designs reside in `firmware/fpga/top/` and are the primary entry points for different operational modes. + +By default, a standard gateware configuration is loaded at boot. However, the firmware can dynamically reconfigure the FPGA at runtime to switch between different gateware variants. + + +Standard gateware +~~~~~~~~~~~~~~~~~ + +The standard gateware is used by default when the firmware has not requested an alternative bitstream. + +The standard gateware provides a balanced configuration optimized for general-purpose operation. It implements configurable digital signal processing paths for the reception and transmission paths, capable of (limited) frequency translation and supporting a wide range of sample rates. + +Block diagram +^^^^^^^^^^^^^ + +.. image:: ../images/gateware-basic-block-diagram.png + :align: center + +.. image:: ../images/gateware-standard-dsp-chain.png + :align: center + :width: 75% + +Features +^^^^^^^^ +* 8-bit I, 8-bit Q data format +* Receiver signal chain: + * Optional DC offset removal (DC blocker) + * Configurable fs/4 shifter (quarter sample rate): bypass, shift up or shift down + * Configurable decimation rates: 1x, 2x, 4x, 8x, 16x, 32x +* Transmitter signal chain: + * Configurable interpolation rates: 1x, 2x, 4x, 8x, 16x, 32x +* SPI control interface for register configuration +* Double data rate (DDR) interface to RF transceiver +* Interface to MCU (SGPIO) + + +Half-precision gateware +~~~~~~~~~~~~~~~~~~~~~~~ + +The half-precision gateware reduces sample width to 4 bits per I/Q component, enabling higher throughput within the constraints of the USB interface (up to 40 Msps). + +This configuration is intended for applications where bandwidth is more critical than dynamic range, such as wideband spectrum monitoring. + +Block diagram +^^^^^^^^^^^^^ + +.. image:: ../images/gateware-basic-block-diagram.png + :align: center + +.. image:: ../images/gateware-halfprec-dsp-chain.png + :align: center + :width: 75% + +Features +^^^^^^^^ +* 4-bit I, 4-bit Q data format +* Receiver signal chain: + * Optional DC offset removal (DC blocker) + * Round to 4-bit I/Q +* Transmitter signal chain: + * Extend width to 8-bit I/Q +* SPI control interface for register configuration +* Double data rate (DDR) interface to RF transceiver +* Interface to MCU (SGPIO) + +Extended-precision gateware (RX and TX) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The extended-precision gateware increases internal signal processing precision and output sample width to improve signal quality. The main drawback is that the minimum decimation or interpolation factor is 16x. Due to increased logic requirements, this gateware is split in two top-level designs (RX and TX). + +Samples are 16-bit I/Q, while the effective number of bits (ENOB) depends on the selected configuration and typically ranges between 9 and 11 bits. + +The increased dynamic range of the output makes it particularly useful for weak and/or narrowband signals. + +Block diagram (RX) +^^^^^^^^^^^^^^^^^^ + +.. image:: ../images/gateware-rx-only-basic-block-diagram.png + :align: center + +.. image:: ../images/gateware-ext-prec-rx-dsp-chain.png + :align: center + :width: 75% + +Block diagram (TX) +^^^^^^^^^^^^^^^^^^ + +.. image:: ../images/gateware-tx-only-basic-block-diagram.png + :align: center + +.. image:: ../images/gateware-ext-prec-tx-dsp-chain.png + :align: center + :width: 75% + +Features +^^^^^^^^ +* 16-bit I, 16-bit Q data format +* Receiver signal chain (RX extended-precision gateware): + * Optional DC offset removal (DC blocker) + * Configurable mixer (in fs/128 steps) + * Configurable decimation rates: 16x, 32x, 64x, 128x +* Transmitter signal chain (TX extended-precision gateware): + * Configurable interpolation rates: 16x, 32x, 64x, 128x +* SPI control interface for register configuration +* Double data rate (DDR) interface to RF transceiver +* Interface to MCU (SGPIO) + diff --git a/docs/source/hackrf_pro.rst b/docs/source/hackrf_pro.rst index 18e4a151..e47dbb0b 100644 --- a/docs/source/hackrf_pro.rst +++ b/docs/source/hackrf_pro.rst @@ -2,6 +2,8 @@ HackRF Pro ================================================ +.. _hackrf_pro: + .. image:: ../images/hackrf-pro-preliminary-photo.jpg :alt: HackRF Pro diff --git a/docs/source/index.rst b/docs/source/index.rst index ce845e66..401fbb3b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -57,6 +57,13 @@ Welcome to HackRF's documentation! setting_gain virtual_machines + +.. toctree:: + :maxdepth: 2 + :caption: Gateware + + gateware + .. toctree:: :maxdepth: 2 :caption: Opera Cake Documentation From e32695e24fde864b46a9664ccddf42a2b91594d0 Mon Sep 17 00:00:00 2001 From: mndza Date: Wed, 3 Jun 2026 10:12:36 +0200 Subject: [PATCH 135/161] gateware: CIC defaults to the minimum resampling factor --- firmware/fpga/build/praline_fpga.bin | Bin 204769 -> 204382 bytes firmware/fpga/dsp/cic.py | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/firmware/fpga/build/praline_fpga.bin b/firmware/fpga/build/praline_fpga.bin index a6b024858f10f08f84cbc6cffa578935c0ae469a..64dd9b4b9bc6f457a2d440b385132e270e0e864b 100644 GIT binary patch literal 204382 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

8B0P02b};H!|8ra@ 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 KlPVz#ME?NOoQ1mp literal 204769 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 diff --git a/firmware/fpga/dsp/cic.py b/firmware/fpga/dsp/cic.py index fa0f3fa0..99f5376a 100644 --- a/firmware/fpga/dsp/cic.py +++ b/firmware/fpga/dsp/cic.py @@ -240,6 +240,7 @@ class ProgrammableShift(wiring.Component): # Implement the map itself (should it be done outside?) max_shift = max(self.shift_map.values()) + min_shift = min(self.shift_map.values()) value_scaled = [ Signal(signed(self.width_in + max_shift)) for _ in range(self.num_channels) ] scaled_valid = Signal() @@ -255,7 +256,9 @@ class ProgrammableShift(wiring.Component): for k, v in self.shift_map.items(): with m.Case(k): m.d.sync += value_scaled[c].eq(self.input.payload[c] << (max_shift - v)) - + with m.Default(): + m.d.sync += value_scaled[c].eq(self.input.payload[c] << (max_shift - min_shift)) + with m.If(~self.output.valid | self.output.ready): m.d.comb += scaled_ready.eq(1) m.d.sync += self.output.valid.eq(scaled_valid) From 595ea6c072fdbed6e77b1ddf9cbba7dc0342eaaf Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 29 May 2026 06:30:57 +0100 Subject: [PATCH 136/161] Make switching SPI bus modes automatic. --- firmware/common/fpga.c | 5 ----- firmware/common/fpga_image.c | 3 --- firmware/common/fpga_selftest.c | 2 -- firmware/common/ice40_spi.c | 11 ++++------ firmware/common/ice40_spi.h | 1 - firmware/common/max2831.c | 2 +- firmware/common/max2831.h | 1 + firmware/common/max2837.c | 4 ++-- firmware/common/max2837.h | 1 + firmware/common/max2839.c | 4 ++-- firmware/common/max2839.h | 1 + firmware/common/max283x.c | 9 +++------ firmware/common/max283x.h | 1 - firmware/common/max5864.c | 7 +------ firmware/common/max5864.h | 1 - firmware/common/pins.c | 2 -- firmware/common/rf_path.c | 10 --------- firmware/common/rffc5071.c | 4 ++-- firmware/common/spi_bus.c | 14 ++++++++++++- firmware/common/spi_bus.h | 7 ++++++- firmware/common/ui_rad1o.c | 6 ------ firmware/common/w25q80bv.c | 36 ++++++++++++++++++++++++--------- 22 files changed, 63 insertions(+), 69 deletions(-) diff --git a/firmware/common/fpga.c b/firmware/common/fpga.c index f68b6525..ec4ff2ec 100644 --- a/firmware/common/fpga.c +++ b/firmware/common/fpga.c @@ -25,7 +25,6 @@ #include #include "ice40_spi.h" -#include "max283x.h" /* Driver instance. */ fpga_driver_t fpga = { @@ -57,9 +56,7 @@ void fpga_setup(fpga_driver_t* const drv) uint8_t fpga_reg_read(fpga_driver_t* const drv, uint8_t r) { uint8_t v; - ssp1_set_mode_ice40(); v = ice40_spi_read(drv->bus, r); - ssp1_set_mode_max283x(); drv->regs[r] = v; return v; } @@ -67,9 +64,7 @@ uint8_t fpga_reg_read(fpga_driver_t* const drv, uint8_t r) void fpga_reg_write(fpga_driver_t* const drv, uint8_t r, uint8_t v) { drv->regs[r] = v; - ssp1_set_mode_ice40(); ice40_spi_write(drv->bus, r, v); - ssp1_set_mode_max283x(); FPGA_REG_SET_CLEAN(drv, r); } diff --git a/firmware/common/fpga_image.c b/firmware/common/fpga_image.c index 3ebadad8..6b440337 100644 --- a/firmware/common/fpga_image.c +++ b/firmware/common/fpga_image.c @@ -26,7 +26,6 @@ #include "fpga.h" #include "ice40_spi.h" #include "lz4_blk.h" -#include "max283x.h" #include "selftest.h" struct fpga_image_read_ctx { @@ -86,7 +85,6 @@ bool fpga_image_load(struct fpga_loader_t* loader, unsigned int index) // A callback function is used by the FPGA programmer // to obtain consecutive gateware chunks. ice40_spi_target_init(&ice40); - ssp1_set_mode_ice40(); struct fpga_image_read_ctx fpga_image_ctx = { .loader = loader, .addr = loader->start_addr + bitstream_offset, @@ -96,7 +94,6 @@ bool fpga_image_load(struct fpga_loader_t* loader, unsigned int index) loader->out_buffer, fpga_image_read_block_cb, &fpga_image_ctx); - ssp1_set_mode_max283x(); // Update selftest result. selftest.fpga_image_load = success ? PASSED : FAILED; diff --git a/firmware/common/fpga_selftest.c b/firmware/common/fpga_selftest.c index 06f2e8ee..b9d7025e 100644 --- a/firmware/common/fpga_selftest.c +++ b/firmware/common/fpga_selftest.c @@ -71,10 +71,8 @@ bool fpga_spi_selftest(void) // Test writing a register and reading it back. uint8_t reg = 6; uint8_t write_value = 0xA5; - ssp1_set_mode_ice40(); ice40_spi_write(&ice40, reg, write_value); uint8_t read_value = ice40_spi_read(&ice40, reg); - ssp1_set_mode_max283x(); // Update selftest result. selftest.fpga_spi = (read_value == write_value) ? PASSED : FAILED; diff --git a/firmware/common/ice40_spi.c b/firmware/common/ice40_spi.c index 2685092a..ce646fd6 100644 --- a/firmware/common/ice40_spi.c +++ b/firmware/common/ice40_spi.c @@ -40,11 +40,6 @@ ice40_spi_driver_t ice40 = { .bus = &spi_bus_ssp1, }; -void ssp1_set_mode_ice40(void) -{ - spi_bus_start(&spi_bus_ssp1, &ssp_config_ice40_fpga); -} - void ice40_spi_target_init(ice40_spi_driver_t* const drv) { const platform_scu_t* scu = platform_scu(); @@ -67,14 +62,14 @@ void ice40_spi_target_init(ice40_spi_driver_t* const drv) uint8_t ice40_spi_read(ice40_spi_driver_t* const drv, uint8_t r) { uint8_t value[3] = {r & 0x7F, 0, 0}; - spi_bus_transfer(drv->bus, value, 3); + spi_bus_transfer(drv->bus, &ssp_config_ice40_fpga, value, 3); return value[2]; } void ice40_spi_write(ice40_spi_driver_t* const drv, uint8_t r, uint16_t v) { uint8_t value[3] = {(r & 0x7F) | 0x80, v, 0}; - spi_bus_transfer(drv->bus, value, 3); + spi_bus_transfer(drv->bus, &ssp_config_ice40_fpga, value, 3); } static void spi_ssp1_wait_for_tx_fifo_not_full(void) @@ -107,6 +102,8 @@ bool ice40_spi_syscfg_program( size_t (*read_block_cb)(void* ctx), void* read_ctx) { + spi_bus_start(drv->bus, &ssp_config_ice40_fpga); + // Drive CRESET_B = 0, SPI_SS = 0, SPI_SCK = 1. gpio_clear(drv->gpio_creset); gpio_clear(drv->gpio_select); diff --git a/firmware/common/ice40_spi.h b/firmware/common/ice40_spi.h index a1362033..331814aa 100644 --- a/firmware/common/ice40_spi.h +++ b/firmware/common/ice40_spi.h @@ -48,4 +48,3 @@ bool ice40_spi_syscfg_program( /* Driver instance. */ extern ssp_config_t ssp_config_ice40_fpga; extern ice40_spi_driver_t ice40; -void ssp1_set_mode_ice40(void); diff --git a/firmware/common/max2831.c b/firmware/common/max2831.c index 05d5cb29..08e59a59 100644 --- a/firmware/common/max2831.c +++ b/firmware/common/max2831.c @@ -109,7 +109,7 @@ static void max2831_write(max2831_driver_t* const drv, uint8_t r, uint16_t v) { uint32_t word = (((uint32_t) v & 0x3fff) << 4) | (r & 0xf); uint16_t values[2] = {word >> 9, word & 0x1ff}; - spi_bus_transfer(drv->bus, values, 2); + spi_bus_transfer(drv->bus, drv->config, values, 2); } uint16_t max2831_reg_read(max2831_driver_t* const drv, uint8_t r) diff --git a/firmware/common/max2831.h b/firmware/common/max2831.h index 0885fd39..f1a28c54 100644 --- a/firmware/common/max2831.h +++ b/firmware/common/max2831.h @@ -50,6 +50,7 @@ typedef enum { typedef struct _max2831_driver_t { spi_bus_t* bus; + void* config; gpio_t gpio_enable; gpio_t gpio_rxtx; gpio_t gpio_rxhp; diff --git a/firmware/common/max2837.c b/firmware/common/max2837.c index 561a17f9..f512c6af 100644 --- a/firmware/common/max2837.c +++ b/firmware/common/max2837.c @@ -156,14 +156,14 @@ void max2837_setup(max2837_driver_t* const drv) static uint16_t max2837_read(max2837_driver_t* const drv, uint8_t r) { uint16_t value = (1 << 15) | (r << 10); - spi_bus_transfer(drv->bus, &value, 1); + spi_bus_transfer(drv->bus, drv->config, &value, 1); return value & 0x3ff; } static void max2837_write(max2837_driver_t* const drv, uint8_t r, uint16_t v) { uint16_t value = (r << 10) | (v & 0x3ff); - spi_bus_transfer(drv->bus, &value, 1); + spi_bus_transfer(drv->bus, drv->config, &value, 1); } uint16_t max2837_reg_read(max2837_driver_t* const drv, uint8_t r) diff --git a/firmware/common/max2837.h b/firmware/common/max2837.h index f32bde6e..a33179ac 100644 --- a/firmware/common/max2837.h +++ b/firmware/common/max2837.h @@ -43,6 +43,7 @@ typedef enum { typedef struct _max2837_driver_t { spi_bus_t* bus; + void* config; gpio_t gpio_enable; gpio_t gpio_rx_enable; gpio_t gpio_tx_enable; diff --git a/firmware/common/max2839.c b/firmware/common/max2839.c index 87917ed8..a938b733 100644 --- a/firmware/common/max2839.c +++ b/firmware/common/max2839.c @@ -164,14 +164,14 @@ void max2839_setup(max2839_driver_t* const drv) static uint16_t max2839_read(max2839_driver_t* const drv, uint8_t r) { uint16_t value = (1 << 15) | (r << 10); - spi_bus_transfer(drv->bus, &value, 1); + spi_bus_transfer(drv->bus, drv->config, &value, 1); return value & 0x3ff; } static void max2839_write(max2839_driver_t* const drv, uint8_t r, uint16_t v) { uint16_t value = (r << 10) | (v & 0x3ff); - spi_bus_transfer(drv->bus, &value, 1); + spi_bus_transfer(drv->bus, drv->config, &value, 1); } uint16_t max2839_reg_read(max2839_driver_t* const drv, uint8_t r) diff --git a/firmware/common/max2839.h b/firmware/common/max2839.h index 0e397294..6430ce37 100644 --- a/firmware/common/max2839.h +++ b/firmware/common/max2839.h @@ -46,6 +46,7 @@ typedef enum { typedef struct _max2839_driver_t { spi_bus_t* bus; + void* config; gpio_t gpio_enable; gpio_t gpio_rxtx; void (*target_init)(struct _max2839_driver_t* const drv); diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index e7221bb0..c0478ae0 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -29,7 +29,6 @@ #include "fixed_point.h" #include "platform_detect.h" #include "platform_gpio.h" -#include "spi_bus.h" #ifdef IS_PRALINE #include "max2831_target.h" @@ -56,14 +55,10 @@ ssp_config_t ssp_config_max283x = { max283x_driver_t max283x = {}; -void ssp1_set_mode_max283x(void) -{ - spi_bus_start(&spi_bus_ssp1, &ssp_config_max283x); -} - #ifdef IS_PRALINE max2831_driver_t max2831 = { .bus = &spi_bus_ssp1, + .config = &ssp_config_max283x, .target_init = max2831_target_init, .set_mode = max2831_target_set_mode, }; @@ -72,6 +67,7 @@ max2831_driver_t max2831 = { #ifdef IS_NOT_PRALINE max2837_driver_t max2837 = { .bus = &spi_bus_ssp1, + .config = &ssp_config_max283x, .target_init = max2837_target_init, .set_mode = max2837_target_set_mode, }; @@ -80,6 +76,7 @@ max2837_driver_t max2837 = { #ifdef IS_HACKRF_ONE max2839_driver_t max2839 = { .bus = &spi_bus_ssp1, + .config = &ssp_config_max283x, .target_init = max2839_target_init, .set_mode = max2839_target_set_mode, }; diff --git a/firmware/common/max283x.h b/firmware/common/max283x.h index 4db5edfe..7c9c03dd 100644 --- a/firmware/common/max283x.h +++ b/firmware/common/max283x.h @@ -143,4 +143,3 @@ void max283x_rx_calibration(max283x_driver_t* const drv); /* Driver instance. */ extern ssp_config_t ssp_config_max283x; extern max283x_driver_t max283x; -void ssp1_set_mode_max283x(void); diff --git a/firmware/common/max5864.c b/firmware/common/max5864.c index 88d781b9..5f3417fc 100644 --- a/firmware/common/max5864.c +++ b/firmware/common/max5864.c @@ -48,14 +48,9 @@ max5864_driver_t max5864 = { .target_init = max5864_target_init, }; -void ssp1_set_mode_max5864(void) -{ - spi_bus_start(max5864.bus, &ssp_config_max5864); -} - static void max5864_write(max5864_driver_t* const drv, uint8_t value) { - spi_bus_transfer(drv->bus, &value, 1); + spi_bus_transfer(drv->bus, &ssp_config_max5864, &value, 1); } static void max5864_init(max5864_driver_t* const drv) diff --git a/firmware/common/max5864.h b/firmware/common/max5864.h index a617cdf0..0f561685 100644 --- a/firmware/common/max5864.h +++ b/firmware/common/max5864.h @@ -42,4 +42,3 @@ void max5864_xcvr(max5864_driver_t* const drv); /* Driver instance. */ extern ssp_config_t ssp_config_max5864; extern max5864_driver_t max5864; -void ssp1_set_mode_max5864(void); diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 3010cd9f..1f98fd9a 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -318,8 +318,6 @@ void pins_setup(void) } #endif - ssp1_set_mode_max283x(); - mixer_bus_setup(&mixer); #ifdef IS_H1_R9 diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index b497a197..27dd548f 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -494,11 +494,9 @@ void rf_path_pin_setup(rf_path_t* const rf_path) void rf_path_init(rf_path_t* const rf_path) { - ssp1_set_mode_max5864(); max5864_setup(&max5864); max5864_shutdown(&max5864); - ssp1_set_mode_max283x(); max283x_setup(&max283x); max283x_start(&max283x); @@ -536,9 +534,7 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d } else { mixer_enable(&mixer); } - ssp1_set_mode_max5864(); max5864_tx(&max5864); - ssp1_set_mode_max283x(); max283x_tx(&max283x); break; @@ -553,9 +549,7 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d } else { mixer_enable(&mixer); } - ssp1_set_mode_max5864(); max5864_rx(&max5864); - ssp1_set_mode_max283x(); max283x_rx(&max283x); break; @@ -564,9 +558,7 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d case RF_PATH_DIRECTION_RX_CALIBRATION: rf_path->switchctrl &= ~SWITCHCTRL_TX; mixer_disable(&mixer); - ssp1_set_mode_max5864(); max5864_xcvr(&max5864); - ssp1_set_mode_max283x(); if (direction == RF_PATH_DIRECTION_TX_CALIBRATION) { max283x_tx_calibration(&max283x); } else { @@ -581,9 +573,7 @@ void rf_path_set_direction(rf_path_t* const rf_path, const rf_path_direction_t d /* Set RF path to receive direction when "off" */ rf_path->switchctrl &= ~SWITCHCTRL_TX; mixer_disable(&mixer); - ssp1_set_mode_max5864(); max5864_standby(&max5864); - ssp1_set_mode_max283x(); max283x_set_mode(&max283x, MAX283x_MODE_STANDBY); break; } diff --git a/firmware/common/rffc5071.c b/firmware/common/rffc5071.c index 2a1866d2..0644a1fa 100644 --- a/firmware/common/rffc5071.c +++ b/firmware/common/rffc5071.c @@ -202,7 +202,7 @@ static uint16_t rffc5071_spi_read(rffc5071_driver_t* const drv, uint8_t r) (void) drv; uint16_t data[] = {0x80 | (r & 0x7f), 0xffff}; - spi_bus_transfer(drv->bus, data, 2); + spi_bus_transfer(drv->bus, drv->bus->config, data, 2); return data[1]; } @@ -211,7 +211,7 @@ static void rffc5071_spi_write(rffc5071_driver_t* const drv, uint8_t r, uint16_t (void) drv; uint16_t data[] = {0x00 | (r & 0x7f), v}; - spi_bus_transfer(drv->bus, data, 2); + spi_bus_transfer(drv->bus, drv->bus->config, data, 2); } uint16_t rffc5071_reg_read(rffc5071_driver_t* const drv, uint8_t r) diff --git a/firmware/common/spi_bus.c b/firmware/common/spi_bus.c index 1b05c298..a667db8a 100644 --- a/firmware/common/spi_bus.c +++ b/firmware/common/spi_bus.c @@ -31,17 +31,29 @@ void spi_bus_start(spi_bus_t* const bus, const void* const config) void spi_bus_stop(spi_bus_t* const bus) { bus->stop(bus); + bus->config = NULL; } -void spi_bus_transfer(spi_bus_t* const bus, void* const data, const size_t count) +void spi_bus_transfer( + spi_bus_t* const bus, + const void* const config, + void* const data, + const size_t count) { + if (config != bus->config) { + spi_bus_start(bus, config); + } bus->transfer(bus, data, count); } void spi_bus_transfer_gather( spi_bus_t* const bus, + const void* const config, const spi_transfer_t* const transfers, const size_t count) { + if (config != bus->config) { + spi_bus_start(bus, config); + } bus->transfer_gather(bus, transfers, count); } diff --git a/firmware/common/spi_bus.h b/firmware/common/spi_bus.h index 07d5787d..bc9c7dee 100644 --- a/firmware/common/spi_bus.h +++ b/firmware/common/spi_bus.h @@ -46,8 +46,13 @@ typedef struct _spi_bus_t { void spi_bus_start(spi_bus_t* const bus, const void* const config); void spi_bus_stop(spi_bus_t* const bus); -void spi_bus_transfer(spi_bus_t* const bus, void* const data, const size_t count); +void spi_bus_transfer( + spi_bus_t* const bus, + const void* const config, + void* const data, + const size_t count); void spi_bus_transfer_gather( spi_bus_t* const bus, + const void* const config, const spi_transfer_t* const transfers, const size_t count); diff --git a/firmware/common/ui_rad1o.c b/firmware/common/ui_rad1o.c index acaa2153..a6d3e544 100644 --- a/firmware/common/ui_rad1o.c +++ b/firmware/common/ui_rad1o.c @@ -26,7 +26,6 @@ #include #include "clock_gen.h" -#include "max283x.h" #include "rf_path.h" #include "transceiver_mode.h" @@ -195,9 +194,6 @@ static void ui_update(void) rad1o_lcdNl(); rad1o_lcdDisplay(); - - // Don't ask... - ssp1_set_mode_max283x(); } static void rad1o_ui_init(void) @@ -211,8 +207,6 @@ static void rad1o_ui_deinit(void) { rad1o_lcdDeInit(); enabled = false; - // Don't ask... - ssp1_set_mode_max283x(); } static void rad1o_ui_set_frequency(uint64_t frequency) diff --git a/firmware/common/w25q80bv.c b/firmware/common/w25q80bv.c index 11c6d5c0..9afa9bea 100644 --- a/firmware/common/w25q80bv.c +++ b/firmware/common/w25q80bv.c @@ -67,6 +67,22 @@ w25q80bv_driver_t spi_flash = { .target_init = w25q80bv_target_init, }; +static void w25q80bv_transfer( + w25q80bv_driver_t* const drv, + void* const data, + const size_t count) +{ + spi_bus_transfer(drv->bus, &ssp_config_w25q80bv, data, count); +} + +static void w25q80bv_transfer_gather( + w25q80bv_driver_t* const drv, + const spi_transfer_t* const transfers, + size_t size) +{ + spi_bus_transfer_gather(drv->bus, &ssp_config_w25q80bv, transfers, size); +} + /* * Set up pins for GPIO and SPI control, configure SSP0 peripheral for SPI. * SSP0_CS is controlled by GPIO in order to handle various transfer lengths. @@ -96,7 +112,7 @@ void w25q80bv_setup(w25q80bv_driver_t* const drv) uint8_t w25q80bv_get_status(w25q80bv_driver_t* const drv) { uint8_t data[] = {W25Q80BV_READ_STATUS1, 0xFF}; - spi_bus_transfer(drv->bus, data, ARRAY_SIZE(data)); + w25q80bv_transfer(drv, data, ARRAY_SIZE(data)); return data[1]; } @@ -104,7 +120,7 @@ uint8_t w25q80bv_get_status(w25q80bv_driver_t* const drv) uint8_t w25q80bv_get_device_id(w25q80bv_driver_t* const drv) { uint8_t data[] = {W25Q80BV_DEVICE_ID, 0xFF, 0xFF, 0xFF, 0xFF}; - spi_bus_transfer(drv->bus, data, ARRAY_SIZE(data)); + w25q80bv_transfer(drv, data, ARRAY_SIZE(data)); return data[4]; } @@ -124,7 +140,7 @@ void w25q80bv_get_unique_id(w25q80bv_driver_t* const drv, w25q80bv_unique_id_t* 0xFF, 0xFF, 0xFF}; - spi_bus_transfer(drv->bus, data, ARRAY_SIZE(data)); + w25q80bv_transfer(drv, data, ARRAY_SIZE(data)); for (size_t i = 0; i < 8; i++) { unique_id->id_8b[i] = data[5 + i]; @@ -141,7 +157,7 @@ void w25q80bv_write_enable(w25q80bv_driver_t* const drv) w25q80bv_wait_while_busy(drv); uint8_t data[] = {W25Q80BV_WRITE_ENABLE}; - spi_bus_transfer(drv->bus, data, ARRAY_SIZE(data)); + w25q80bv_transfer(drv, data, ARRAY_SIZE(data)); while (!(w25q80bv_get_status(drv) & W25Q80BV_STATUS_WEL)) {} } @@ -159,7 +175,7 @@ void w25q80bv_chip_erase(w25q80bv_driver_t* const drv) w25q80bv_write_enable(drv); uint8_t data[] = {W25Q80BV_CHIP_ERASE}; - spi_bus_transfer(drv->bus, data, ARRAY_SIZE(data)); + w25q80bv_transfer(drv, data, ARRAY_SIZE(data)); } /* write up a 256 byte page or partial page */ @@ -190,7 +206,7 @@ static void w25q80bv_page_program( const spi_transfer_t transfers[] = {{header, ARRAY_SIZE(header)}, {data, len}}; - spi_bus_transfer_gather(drv->bus, transfers, ARRAY_SIZE(transfers)); + w25q80bv_transfer_gather(drv, transfers, ARRAY_SIZE(transfers)); } /* write an arbitrary number of bytes */ @@ -265,7 +281,7 @@ void w25q80bv_read( const spi_transfer_t transfers[] = {{header, ARRAY_SIZE(header)}, {data, len}}; - spi_bus_transfer_gather(drv->bus, transfers, ARRAY_SIZE(transfers)); + w25q80bv_transfer_gather(drv, transfers, ARRAY_SIZE(transfers)); } void w25q80bv_clear_status(w25q80bv_driver_t* const drv) @@ -273,16 +289,16 @@ void w25q80bv_clear_status(w25q80bv_driver_t* const drv) w25q80bv_wait_while_busy(drv); w25q80bv_write_enable(drv); uint8_t data[] = {W25Q80BV_WRITE_STATUS, 0x00, 0x00}; - spi_bus_transfer(drv->bus, data, ARRAY_SIZE(data)); + w25q80bv_transfer(drv, data, ARRAY_SIZE(data)); } void w25q80bv_get_full_status(w25q80bv_driver_t* const drv, uint8_t* data) { uint8_t cmd[] = {W25Q80BV_READ_STATUS1, 0xFF}; - spi_bus_transfer(drv->bus, cmd, ARRAY_SIZE(cmd)); + w25q80bv_transfer(drv, cmd, ARRAY_SIZE(cmd)); data[0] = cmd[1]; cmd[0] = W25Q80BV_READ_STATUS2; cmd[1] = 0xFF; - spi_bus_transfer(drv->bus, cmd, ARRAY_SIZE(cmd)); + w25q80bv_transfer(drv, cmd, ARRAY_SIZE(cmd)); data[1] = cmd[1]; } From ff052af3526dd915ac8a0728a2196ab58fc0cf16 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 8 Jun 2026 16:17:11 +0100 Subject: [PATCH 137/161] si5351c: Move the clk array into the driver struct. --- firmware/common/si5351c.c | 74 +++++++++++++++++++-------------------- firmware/common/si5351c.h | 15 ++++---- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 353a4d2e..2fe487a3 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -51,8 +51,6 @@ si5351c_driver_t si5351c = { .i2c_address = 0x60, }; -si5351c_clk_t clk[8]; - uint8_t clkout_id; uint8_t mcu_clkin_id; @@ -267,12 +265,12 @@ void si5351c_configure_multisynth( void si5351c_configure_clock_control(si5351c_driver_t* const drv) { for (int i = 0; i < 8; i++) { - set_CLK_PDN(drv, i, clk[i].power_down); - set_MS_INT(drv, i, clk[i].mode); - set_MS_SRC(drv, i, clk[i].pll); - set_CLK_SRC(drv, i, clk[i].source); - set_CLK_IDRV(drv, i, clk[i].drive); - set_CLK_INV(drv, i, clk[i].invert); + set_CLK_PDN(drv, i, drv->clk[i].power_down); + set_MS_INT(drv, i, drv->clk[i].mode); + set_MS_SRC(drv, i, drv->clk[i].pll); + set_CLK_SRC(drv, i, drv->clk[i].source); + set_CLK_IDRV(drv, i, drv->clk[i].drive); + set_CLK_INV(drv, i, drv->clk[i].invert); } si5351c_regs_commit(drv); @@ -284,15 +282,15 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) set_CLK_OEB( drv, i, - clk[i].output_enable ? SI5351C_OUTPUT_ENABLE : - SI5351C_OUTPUT_DISABLE); + drv->clk[i].output_enable ? SI5351C_OUTPUT_ENABLE : + SI5351C_OUTPUT_DISABLE); } si5351c_regs_commit(drv); #ifdef IS_H1_R9 if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); - if (clk[clkout_id].output_enable) { + if (drv->clk[clkout_id].output_enable) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); @@ -355,8 +353,8 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable) { - clk[clkout_id].output_enable = enable; - clk[clkout_id].power_down = !enable; + drv->clk[clkout_id].output_enable = enable; + drv->clk[clkout_id].power_down = !enable; /* Configure clock to 10MHz */ si5351c_configure_multisynth(drv, clkout_id, 80 * 128 - 512, 0, 1, 0); @@ -375,8 +373,8 @@ void si5351c_mcu_clkin_enable(si5351c_driver_t* const drv, bool enable) #endif #ifdef IS_NOT_H1_R9 if (IS_NOT_H1_R9) { - clk[mcu_clkin_id].output_enable = enable; - clk[mcu_clkin_id].power_down = !enable; + drv->clk[mcu_clkin_id].output_enable = enable; + drv->clk[mcu_clkin_id].power_down = !enable; /* Configure clock to 40MHz */ if (mcu_clkin_id >= 6) { @@ -485,7 +483,7 @@ void si5351c_init(si5351c_driver_t* const drv) }; /* CLK0: MAX5864/CPLD */ - clk[0] = (si5351c_clk_t){ + drv->clk[0] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_FRAC, .pll = SI5351C_PLL_A, @@ -493,7 +491,7 @@ void si5351c_init(si5351c_driver_t* const drv) .drive = SI5351C_DRIVE_8MA, }; /* CLK1: CPLD */ - clk[1] = (si5351c_clk_t){ + drv->clk[1] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_INT, .pll = SI5351C_PLL_A, @@ -502,7 +500,7 @@ void si5351c_init(si5351c_driver_t* const drv) .invert = true, }; /* CLK2: SGPIO */ - clk[2] = (si5351c_clk_t){ + drv->clk[2] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_INT, .pll = SI5351C_PLL_A, @@ -510,10 +508,10 @@ void si5351c_init(si5351c_driver_t* const drv) .drive = SI5351C_DRIVE_2MA, }; /* CLK3: CLKOUT */ - clk[3] = clkout; + drv->clk[3] = clkout; clkout_id = 3; /* CLK4: RFFC5072 (MAX2837 on rad1o) */ - clk[4] = (si5351c_clk_t){ + drv->clk[4] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_INT, .pll = SI5351C_PLL_A, @@ -522,7 +520,7 @@ void si5351c_init(si5351c_driver_t* const drv) .invert = true, }; /* CLK5: MAX2837 (MAX2871 on rad1o) */ - clk[5] = (si5351c_clk_t){ + drv->clk[5] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_INT, .pll = SI5351C_PLL_A, @@ -530,15 +528,15 @@ void si5351c_init(si5351c_driver_t* const drv) .drive = SI5351C_DRIVE_4MA, }; /* CLK6: none */ - clk[6] = powered_down; + drv->clk[6] = powered_down; /* CLK7: LPC43xx */ - clk[7] = mcu_clkin; + drv->clk[7] = mcu_clkin; mcu_clkin_id = 7; #ifdef IS_H1_R9 if (IS_H1_R9) { /* CLK0: MAX5864/CPLD/SGPIO (sample clocks) */ - clk[0] = (si5351c_clk_t){ + drv->clk[0] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_INT, .pll = SI5351C_PLL_A, @@ -546,7 +544,7 @@ void si5351c_init(si5351c_driver_t* const drv) .drive = SI5351C_DRIVE_6MA, }; /* CLK1: RFFC5072/MAX2839 */ - clk[1] = (si5351c_clk_t){ + drv->clk[1] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_FRAC, .pll = SI5351C_PLL_A, @@ -554,21 +552,21 @@ void si5351c_init(si5351c_driver_t* const drv) .drive = SI5351C_DRIVE_4MA, }; /* CLK2: CLKOUT/LPC4320 */ - clk[2] = clkout; + drv->clk[2] = clkout; clkout_id = 2; mcu_clkin_id = 2; /* Other outputs not present on Si5351A */ - clk[3] = powered_down; - clk[4] = powered_down; - clk[5] = powered_down; - clk[6] = powered_down; - clk[7] = powered_down; + drv->clk[3] = powered_down; + drv->clk[4] = powered_down; + drv->clk[5] = powered_down; + drv->clk[6] = powered_down; + drv->clk[7] = powered_down; } #endif #ifdef IS_PRALINE if (IS_PRALINE) { /* CLK0: AFE_CLK */ - clk[0] = (si5351c_clk_t){ + drv->clk[0] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_FRAC, .pll = SI5351C_PLL_A, @@ -576,7 +574,7 @@ void si5351c_init(si5351c_driver_t* const drv) .drive = SI5351C_DRIVE_4MA, }; /* CLK1: SCT_CLK and FPGA_CLK */ - clk[1] = (si5351c_clk_t){ + drv->clk[1] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_FRAC, .pll = SI5351C_PLL_A, @@ -585,10 +583,10 @@ void si5351c_init(si5351c_driver_t* const drv) }; /* CLK3: CLKOUT */ clkout.pll = SI5351C_PLL_B; - clk[3] = clkout; + drv->clk[3] = clkout; clkout_id = 3; /* CLK4: XCVR_CLK */ - clk[4] = (si5351c_clk_t){ + drv->clk[4] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_INT, .pll = SI5351C_PLL_B, @@ -597,7 +595,7 @@ void si5351c_init(si5351c_driver_t* const drv) .invert = true, }; /* CLK5: MIX_CLK */ - clk[5] = (si5351c_clk_t){ + drv->clk[5] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_INT, .pll = SI5351C_PLL_B, @@ -606,7 +604,7 @@ void si5351c_init(si5351c_driver_t* const drv) }; if ((detected_revision() & ~BOARD_REV_GSG) < BOARD_REV_PRALINE_R1_1) { /* CLK2: FPGA_CLK (not shared with SCT_CLK on older boards) */ - clk[2] = (si5351c_clk_t){ + drv->clk[2] = (si5351c_clk_t){ .output_enable = true, .mode = SI5351C_MODE_FRAC, .pll = SI5351C_PLL_A, @@ -615,7 +613,7 @@ void si5351c_init(si5351c_driver_t* const drv) }; } else { /* CLK2: MCU_CLK */ - clk[2] = mcu_clkin; + drv->clk[2] = mcu_clkin; mcu_clkin_id = 2; } } diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index dad74a17..c3a0558e 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -89,13 +89,6 @@ typedef enum { SI5351C_INPUT_CLKIN = 1, } si5351c_input_t; -typedef struct { - i2c_bus_t* const bus; - uint8_t i2c_address; - uint8_t regs[SI5351C_CACHED_REGS]; - uint32_t regs_dirty[(SI5351C_CACHED_REGS + 31) / 32]; -} si5351c_driver_t; - typedef struct { bool output_enable; bool power_down; @@ -106,6 +99,14 @@ typedef struct { bool invert; } si5351c_clk_t; +typedef struct { + i2c_bus_t* const bus; + uint8_t i2c_address; + si5351c_clk_t clk[8]; + uint8_t regs[SI5351C_CACHED_REGS]; + uint32_t regs_dirty[(SI5351C_CACHED_REGS + 31) / 32]; +} si5351c_driver_t; + void si5351c_disable_all_outputs(si5351c_driver_t* const drv); void si5351c_disable_oeb_pin_control(si5351c_driver_t* const drv); void si5351c_power_down_all_clocks(si5351c_driver_t* const drv); From a716b642eca1247a2e41a68fc594ecaa59be1f47 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 8 Jun 2026 16:24:15 +0100 Subject: [PATCH 138/161] si5351c: Move other globals into driver struct. --- firmware/common/si5351c.c | 42 +++++++++++++++++---------------------- firmware/common/si5351c.h | 4 ++++ 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 2fe487a3..9fbe8764 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -51,12 +51,6 @@ si5351c_driver_t si5351c = { .i2c_address = 0x60, }; -uint8_t clkout_id; -uint8_t mcu_clkin_id; - -static bool input_initialized = false; -static si5351c_input_t active_input; - /* write to single register */ void si5351c_write_single(si5351c_driver_t* const drv, uint8_t reg, uint8_t val) { @@ -290,7 +284,7 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) #ifdef IS_H1_R9 if (IS_H1_R9) { const platform_gpio_t* gpio = platform_gpio(); - if (drv->clk[clkout_id].output_enable) { + if (drv->clk[drv->clkout_id].output_enable) { gpio_set(gpio->h1r9_clkout_en); } else { gpio_clear(gpio->h1r9_clkout_en); @@ -310,7 +304,7 @@ void si5351c_set_int_mode( void si5351c_change_input(si5351c_driver_t* const drv, si5351c_input_t input) { - if (input_initialized && input == active_input) { + if (drv->input_initialized && input == drv->active_input) { return; } si5351c_disable_all_outputs(drv); @@ -334,8 +328,8 @@ void si5351c_change_input(si5351c_driver_t* const drv, si5351c_input_t input) } #endif si5351c_configure_pll_multisynth(drv, input); - active_input = input; - input_initialized = true; + drv->active_input = input; + drv->input_initialized = true; si5351c_reset_plls(drv, SI5351C_PLL_MASK_BOTH); } @@ -353,11 +347,11 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable) { - drv->clk[clkout_id].output_enable = enable; - drv->clk[clkout_id].power_down = !enable; + drv->clk[drv->clkout_id].output_enable = enable; + drv->clk[drv->clkout_id].power_down = !enable; /* Configure clock to 10MHz */ - si5351c_configure_multisynth(drv, clkout_id, 80 * 128 - 512, 0, 1, 0); + si5351c_configure_multisynth(drv, drv->clkout_id, 80 * 128 - 512, 0, 1, 0); si5351c_configure_clock_control(drv); si5351c_enable_clock_outputs(drv); @@ -373,18 +367,18 @@ void si5351c_mcu_clkin_enable(si5351c_driver_t* const drv, bool enable) #endif #ifdef IS_NOT_H1_R9 if (IS_NOT_H1_R9) { - drv->clk[mcu_clkin_id].output_enable = enable; - drv->clk[mcu_clkin_id].power_down = !enable; + drv->clk[drv->mcu_clkin_id].output_enable = enable; + drv->clk[drv->mcu_clkin_id].power_down = !enable; /* Configure clock to 40MHz */ - if (mcu_clkin_id >= 6) { + if (drv->mcu_clkin_id >= 6) { // MCU_CLKIN on CLK6 or CLK7, integer only MS. - si5351c_configure_multisynth(drv, mcu_clkin_id, 20, 0, 0, 0); + si5351c_configure_multisynth(drv, drv->mcu_clkin_id, 20, 0, 0, 0); } else { // MCU_CLKIN on CLK0 to CLK5, fractional-capable MS. si5351c_configure_multisynth( drv, - mcu_clkin_id, + drv->mcu_clkin_id, 20 * 128 - 512, 0, 1, @@ -509,7 +503,7 @@ void si5351c_init(si5351c_driver_t* const drv) }; /* CLK3: CLKOUT */ drv->clk[3] = clkout; - clkout_id = 3; + drv->clkout_id = 3; /* CLK4: RFFC5072 (MAX2837 on rad1o) */ drv->clk[4] = (si5351c_clk_t){ .output_enable = true, @@ -531,7 +525,7 @@ void si5351c_init(si5351c_driver_t* const drv) drv->clk[6] = powered_down; /* CLK7: LPC43xx */ drv->clk[7] = mcu_clkin; - mcu_clkin_id = 7; + drv->mcu_clkin_id = 7; #ifdef IS_H1_R9 if (IS_H1_R9) { @@ -553,8 +547,8 @@ void si5351c_init(si5351c_driver_t* const drv) }; /* CLK2: CLKOUT/LPC4320 */ drv->clk[2] = clkout; - clkout_id = 2; - mcu_clkin_id = 2; + drv->clkout_id = 2; + drv->mcu_clkin_id = 2; /* Other outputs not present on Si5351A */ drv->clk[3] = powered_down; drv->clk[4] = powered_down; @@ -584,7 +578,7 @@ void si5351c_init(si5351c_driver_t* const drv) /* CLK3: CLKOUT */ clkout.pll = SI5351C_PLL_B; drv->clk[3] = clkout; - clkout_id = 3; + drv->clkout_id = 3; /* CLK4: XCVR_CLK */ drv->clk[4] = (si5351c_clk_t){ .output_enable = true, @@ -614,7 +608,7 @@ void si5351c_init(si5351c_driver_t* const drv) } else { /* CLK2: MCU_CLK */ drv->clk[2] = mcu_clkin; - mcu_clkin_id = 2; + drv->mcu_clkin_id = 2; } } #endif diff --git a/firmware/common/si5351c.h b/firmware/common/si5351c.h index c3a0558e..666a79f4 100644 --- a/firmware/common/si5351c.h +++ b/firmware/common/si5351c.h @@ -103,6 +103,10 @@ typedef struct { i2c_bus_t* const bus; uint8_t i2c_address; si5351c_clk_t clk[8]; + uint8_t clkout_id; + uint8_t mcu_clkin_id; + bool input_initialized; + si5351c_input_t active_input; uint8_t regs[SI5351C_CACHED_REGS]; uint32_t regs_dirty[(SI5351C_CACHED_REGS + 31) / 32]; } si5351c_driver_t; From c77c962b7185593cf975134e924e066c33791d7d Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 18 May 2026 18:25:25 +0100 Subject: [PATCH 139/161] Move max283x-related bits out of pins_setup. --- firmware/common/max283x.c | 5 +++++ firmware/common/pins.c | 14 -------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/firmware/common/max283x.c b/firmware/common/max283x.c index c0478ae0..f9486921 100644 --- a/firmware/common/max283x.c +++ b/firmware/common/max283x.c @@ -26,6 +26,8 @@ #include #include +#include + #include "fixed_point.h" #include "platform_detect.h" #include "platform_gpio.h" @@ -88,8 +90,10 @@ void max283x_setup(max283x_driver_t* const drv) const platform_gpio_t* gpio = platform_gpio(); /* MAX283x GPIO PinMux */ + ssp_config_max283x.gpio_select = gpio->max283x_select; #ifdef IS_PRALINE if (IS_PRALINE) { + ssp_config_max283x.data_bits = SSP_DATA_9BITS; drv->type = MAX2831_VARIANT; max2831.gpio_enable = gpio->max283x_enable; max2831.gpio_rxtx = gpio->max283x_rx_enable; @@ -101,6 +105,7 @@ void max283x_setup(max283x_driver_t* const drv) #endif #ifdef IS_NOT_PRALINE if (IS_NOT_PRALINE) { + ssp_config_max283x.data_bits = SSP_DATA_16BITS; #ifdef IS_H1_R9 if (IS_H1_R9) { drv->type = MAX2839_VARIANT; diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 1f98fd9a..3fcbf037 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -24,12 +24,10 @@ #include "pins.h" #include -#include #include "cpld_jtag.h" #include "gpio.h" #include "leds.h" -#include "max283x.h" #include "max5864.h" #include "mixer.h" #include "platform_detect.h" @@ -266,18 +264,6 @@ void pins_setup(void) #endif /* Configure drivers and driver pins */ - ssp_config_max283x.gpio_select = gpio->max283x_select; -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - ssp_config_max283x.data_bits = SSP_DATA_16BITS; - } -#endif -#ifdef IS_PRALINE - if (IS_PRALINE) { - ssp_config_max283x.data_bits = SSP_DATA_9BITS; // send 2 words - } -#endif - ssp_config_max5864.gpio_select = gpio->max5864_select; ssp_config_w25q80bv.gpio_select = gpio->w25q80bv_select; From 9d6e43a1cb611fc9bbd1917915745adae38bb23f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 13 May 2026 23:11:47 +0100 Subject: [PATCH 140/161] Move max5864-related bits out of pins_setup. --- firmware/common/max5864.c | 3 +++ firmware/common/pins.c | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/firmware/common/max5864.c b/firmware/common/max5864.c index 5f3417fc..0b825fcd 100644 --- a/firmware/common/max5864.c +++ b/firmware/common/max5864.c @@ -27,6 +27,7 @@ #include #include "max5864_target.h" +#include "platform_gpio.h" #include "spi_bus.h" /* Driver instance. */ @@ -60,6 +61,8 @@ static void max5864_init(max5864_driver_t* const drv) void max5864_setup(max5864_driver_t* const drv) { + ssp_config_max5864.gpio_select = platform_gpio()->max5864_select; + max5864_init(drv); } diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 3fcbf037..d917f7d2 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -28,7 +28,6 @@ #include "cpld_jtag.h" #include "gpio.h" #include "leds.h" -#include "max5864.h" #include "mixer.h" #include "platform_detect.h" #include "platform_gpio.h" @@ -264,8 +263,6 @@ void pins_setup(void) #endif /* Configure drivers and driver pins */ - ssp_config_max5864.gpio_select = gpio->max5864_select; - ssp_config_w25q80bv.gpio_select = gpio->w25q80bv_select; spi_flash.gpio_hold = gpio->w25q80bv_hold; spi_flash.gpio_wp = gpio->w25q80bv_wp; From a7925bd74ea4229e8437991cd61af77bc306dd63 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 18 May 2026 16:31:54 +0100 Subject: [PATCH 141/161] Remove some superfluous spi_bus_setup calls. --- firmware/common/rom_iap.c | 2 -- firmware/hackrf_usb/hackrf_usb.c | 1 - firmware/hackrf_usb/usb_api_spiflash.c | 3 --- 3 files changed, 6 deletions(-) diff --git a/firmware/common/rom_iap.c b/firmware/common/rom_iap.c index 25dfa269..ce1f113b 100644 --- a/firmware/common/rom_iap.c +++ b/firmware/common/rom_iap.c @@ -23,7 +23,6 @@ #include #include "rom_iap.h" -#include "spi_bus.h" #include "w25q80bv.h" #define ROM_IAP_ADDR (0x10400100) @@ -77,7 +76,6 @@ isp_iap_ret_code_t iap_cmd_call(iap_cmd_res_t* iap_cmd_res) Alternative way to retrieve Part Id on MCU with no IAP Read Serial No => Read Unique ID in SPIFI (only compatible with W25Q80BV */ - spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv); w25q80bv_setup(&spi_flash); switch (iap_cmd_res->cmd_param.command_code) { diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 5d71fa80..197ffb8a 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -275,7 +275,6 @@ extern uint32_t _binary_fpga_bin_start; void fpga_loader_setup(void) { - spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv); w25q80bv_setup(&spi_flash); } diff --git a/firmware/hackrf_usb/usb_api_spiflash.c b/firmware/hackrf_usb/usb_api_spiflash.c index c698d599..21370bd0 100644 --- a/firmware/hackrf_usb/usb_api_spiflash.c +++ b/firmware/hackrf_usb/usb_api_spiflash.c @@ -26,7 +26,6 @@ #include #include -#include #include #include #include @@ -40,7 +39,6 @@ usb_request_status_t usb_vendor_request_erase_spiflash( const usb_transfer_stage_t stage) { if (stage == USB_TRANSFER_STAGE_SETUP) { - spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv); w25q80bv_setup(&spi_flash); /* only chip erase is implemented */ w25q80bv_chip_erase(&spi_flash); @@ -69,7 +67,6 @@ usb_request_status_t usb_vendor_request_write_spiflash( len, NULL, NULL); - spi_bus_start(spi_flash.bus, &ssp_config_w25q80bv); w25q80bv_setup(&spi_flash); return USB_REQUEST_STATUS_OK; } From 9c0a211f1362b57a40f09f2a99ad5f9deee33516 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 18 May 2026 16:34:24 +0100 Subject: [PATCH 142/161] Move w25q80bv-related bits out of pins_setup. --- firmware/common/pins.c | 5 ----- firmware/common/w25q80bv.c | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/firmware/common/pins.c b/firmware/common/pins.c index d917f7d2..3ce27b8d 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -35,7 +35,6 @@ #include "power.h" #include "rf_path.h" #include "sgpio.h" -#include "w25q80bv.h" #ifdef IS_PRALINE #include "clock_io.h" #include "ice40_spi.h" @@ -263,10 +262,6 @@ void pins_setup(void) #endif /* Configure drivers and driver pins */ - ssp_config_w25q80bv.gpio_select = gpio->w25q80bv_select; - spi_flash.gpio_hold = gpio->w25q80bv_hold; - spi_flash.gpio_wp = gpio->w25q80bv_wp; - sgpio_config.gpio_q_invert = gpio->q_invert; #ifdef IS_NOT_PRALINE diff --git a/firmware/common/w25q80bv.c b/firmware/common/w25q80bv.c index 9afa9bea..abd18624 100644 --- a/firmware/common/w25q80bv.c +++ b/firmware/common/w25q80bv.c @@ -35,6 +35,7 @@ #include #include "platform_detect.h" +#include "platform_gpio.h" #include "w25q80bv_target.h" #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -90,6 +91,11 @@ static void w25q80bv_transfer_gather( void w25q80bv_setup(w25q80bv_driver_t* const drv) { uint8_t device_id; + const platform_gpio_t* gpio = platform_gpio(); + + ssp_config_w25q80bv.gpio_select = gpio->w25q80bv_select; + spi_flash.gpio_hold = gpio->w25q80bv_hold; + spi_flash.gpio_wp = gpio->w25q80bv_wp; drv->page_len = 256U; if (detected_platform() == BOARD_ID_PRALINE) { From b533e1c5dce61c2a34571844737d6290af4433ef Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 13 May 2026 23:29:39 +0100 Subject: [PATCH 143/161] Move sgpio-related bits out of pins_setup. --- firmware/common/pins.c | 18 ------------------ firmware/common/sgpio.c | 16 ++++++++++++++++ firmware/hackrf_usb/hackrf_usb.c | 2 ++ firmware/hackrf_usb/usb_api_board_info.c | 2 ++ 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 3ce27b8d..2bd135a3 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -34,7 +34,6 @@ #include "platform_scu.h" #include "power.h" #include "rf_path.h" -#include "sgpio.h" #ifdef IS_PRALINE #include "clock_io.h" #include "ice40_spi.h" @@ -228,7 +227,6 @@ void pins_shutdown(void) rf_path_pin_shutdown(); } #endif - sgpio_pin_shutdown(&sgpio_config); /* enable input on SCL and SDA pins */ SCU_SFSI2C0 = SCU_I2C0_NOMINAL; @@ -262,14 +260,6 @@ void pins_setup(void) #endif /* Configure drivers and driver pins */ - sgpio_config.gpio_q_invert = gpio->q_invert; - -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - sgpio_config.gpio_trigger_enable = gpio->trigger_enable; - } -#endif - #ifdef IS_PRALINE if (IS_PRALINE) { ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; @@ -298,12 +288,6 @@ void pins_setup(void) mixer_bus_setup(&mixer); -#ifdef IS_H1_R9 - if (IS_H1_R9) { - sgpio_config.gpio_trigger_enable = gpio->h1r9_trigger_enable; - } -#endif - // initialize rf_path struct and assign gpio's #ifdef IS_HACKRF_ONE if (IS_HACKRF_ONE) { @@ -375,6 +359,4 @@ void pins_setup(void) /* Configure external clock in */ scu_pinmux(scu->PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1); - - sgpio_configure_pin_functions(&sgpio_config); } diff --git a/firmware/common/sgpio.c b/firmware/common/sgpio.c index 09a5d8cf..1528933a 100644 --- a/firmware/common/sgpio.c +++ b/firmware/common/sgpio.c @@ -29,6 +29,7 @@ #include #include "platform_detect.h" +#include "platform_gpio.h" #include "platform_scu.h" #include "sgpio.h" #ifdef IS_NOT_PRALINE @@ -91,8 +92,23 @@ void sgpio_pin_shutdown(sgpio_config_t* const config) void sgpio_configure_pin_functions(sgpio_config_t* const config) { + const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); + config->gpio_q_invert = gpio->q_invert; + +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + config->gpio_trigger_enable = gpio->trigger_enable; + } +#endif + +#ifdef IS_H1_R9 + if (IS_H1_R9) { + config->gpio_trigger_enable = gpio->h1r9_trigger_enable; + } +#endif + scu_pinmux(scu->PINMUX_SGPIO0, scu->PINMUX_SGPIO0_PINCFG); scu_pinmux(scu->PINMUX_SGPIO1, scu->PINMUX_SGPIO1_PINCFG); scu_pinmux(scu->PINMUX_SGPIO2, scu->PINMUX_SGPIO2_PINCFG); diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 197ffb8a..944b9556 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -429,11 +429,13 @@ int main(void) board_id_t board_id = detected_platform(); pins_shutdown(); + sgpio_pin_shutdown(&sgpio_config); if (board_id != BOARD_ID_RAD1O) { clock_gen_shutdown(); } delay_us_at_mhz(10000, 96); pins_setup(); + sgpio_configure_pin_functions(&sgpio_config); #ifdef IS_PRALINE if (IS_PRALINE) { enable_3v3aux_power(); diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index 2d017378..671cb862 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,7 @@ usb_request_status_t usb_vendor_request_reset( { if (stage == USB_TRANSFER_STAGE_SETUP) { pins_shutdown(); + sgpio_pin_shutdown(&sgpio_config); clock_gen_shutdown(); #ifdef IS_H1_R9 if (IS_H1_R9) { From e3ffbf68f0121bfa752577113c087691263a405b Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 13 May 2026 23:46:48 +0100 Subject: [PATCH 144/161] Move rf_path-related bits out of pins_setup. --- firmware/common/pins.c | 74 ------------------------ firmware/common/rf_path.c | 74 +++++++++++++++++++++++- firmware/hackrf_usb/hackrf_usb.c | 2 + firmware/hackrf_usb/usb_api_board_info.c | 2 + 4 files changed, 76 insertions(+), 76 deletions(-) diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 2bd135a3..97f3f413 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -33,7 +33,6 @@ #include "platform_gpio.h" #include "platform_scu.h" #include "power.h" -#include "rf_path.h" #ifdef IS_PRALINE #include "clock_io.h" #include "ice40_spi.h" @@ -204,7 +203,6 @@ void pins_shutdown(void) p2_ctrl_set(P2_SIGNAL_CLK3); p1_ctrl_set(P1_SIGNAL_CLKIN); - narrowband_filter_set(0); clkin_ctrl_set(CLKIN_SIGNAL_P1); gpio_output(gpio->p2_ctrl0); @@ -214,7 +212,6 @@ void pins_shutdown(void) gpio_output(gpio->p1_ctrl2); gpio_output(gpio->clkin_ctrl); gpio_output(gpio->pps_out); - gpio_output(gpio->aa_en); gpio_input(gpio->trigger_in); gpio_input(gpio->trigger_out); gpio_clear(gpio->fpga_cfg_spi_cs); @@ -223,8 +220,6 @@ void pins_shutdown(void) gpio_output(gpio->fpga_cfg_creset); gpio_input(gpio->fpga_cfg_cdone); gpio_input(gpio->max5864_select); - - rf_path_pin_shutdown(); } #endif @@ -288,75 +283,6 @@ void pins_setup(void) mixer_bus_setup(&mixer); - // initialize rf_path struct and assign gpio's -#ifdef IS_HACKRF_ONE - if (IS_HACKRF_ONE) { - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_hp = gpio->hp, - .gpio_lp = gpio->lp, - .gpio_tx_mix_bp = gpio->tx_mix_bp, - .gpio_no_mix_bypass = gpio->no_mix_bypass, - .gpio_rx_mix_bp = gpio->rx_mix_bp, - .gpio_tx_amp = gpio->tx_amp, - .gpio_tx = gpio->tx, - .gpio_mix_bypass = gpio->mix_bypass, - .gpio_rx = gpio->rx, - .gpio_no_tx_amp_pwr = gpio->no_tx_amp_pwr, - .gpio_amp_bypass = gpio->amp_bypass, - .gpio_rx_amp = gpio->rx_amp, - .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, - }; - #ifdef IS_H1_R9 - if (IS_H1_R9) { - rf_path.gpio_rx = gpio->h1r9_rx; - rf_path.gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; - } - #endif - } -#endif - -#ifdef IS_RAD1O - if (IS_RAD1O) { - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_tx_rx_n = gpio->tx_rx_n, - .gpio_tx_rx = gpio->tx_rx, - .gpio_by_mix = gpio->by_mix, - .gpio_by_mix_n = gpio->by_mix_n, - .gpio_by_amp = gpio->by_amp, - .gpio_by_amp_n = gpio->by_amp_n, - .gpio_mixer_en = gpio->mixer_en, - .gpio_low_high_filt = gpio->low_high_filt, - .gpio_low_high_filt_n = gpio->low_high_filt_n, - .gpio_tx_amp = gpio->tx_amp, - .gpio_rx_lna = gpio->rx_lna, - }; - } -#endif - -#ifdef IS_PRALINE - if (IS_PRALINE) { - rf_path = (rf_path_t){ - .switchctrl = 0, - .gpio_tx_en = gpio->tx_en, - .gpio_mix_en_n = gpio->mix_en_n, - .gpio_lpf_en = gpio->lpf_en, - .gpio_rf_amp_en = gpio->rf_amp_en, - .gpio_ant_bias_en_n = gpio->ant_bias_en_n, - }; - if ((detected_revision() == BOARD_REV_PRALINE_R1_0) || - (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { - rf_path.gpio_mix_en_n = gpio->mix_en_n_r1_0; - } - scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4); - scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); - } -#endif - - rf_path_pin_setup(&rf_path); - /* Configure external clock in */ scu_pinmux(scu->PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1); } diff --git a/firmware/common/rf_path.c b/firmware/common/rf_path.c index 27dd548f..51488c16 100644 --- a/firmware/common/rf_path.c +++ b/firmware/common/rf_path.c @@ -30,8 +30,6 @@ #ifdef IS_NOT_JAWBREAKER #include #include "platform_scu.h" -#endif -#ifdef IS_PRALINE #include "platform_gpio.h" #endif @@ -344,6 +342,10 @@ void rf_path_pin_shutdown(void) /* Configure RF power supply (VAA) switch */ scu_pinmux(scu->NO_VAA_ENABLE, SCU_GPIO_PDN | SCU_CONF_FUNCTION0); + + /* Disable narrowband filter. */ + narrowband_filter_set(0); + gpio_output(platform_gpio()->aa_en); } #endif } @@ -353,9 +355,77 @@ void rf_path_pin_setup(rf_path_t* const rf_path) #ifdef IS_JAWBREAKER (void) rf_path; #else + const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); #endif + // initialize rf_path struct and assign gpio's +#ifdef IS_HACKRF_ONE + if (IS_HACKRF_ONE) { + *rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_hp = gpio->hp, + .gpio_lp = gpio->lp, + .gpio_tx_mix_bp = gpio->tx_mix_bp, + .gpio_no_mix_bypass = gpio->no_mix_bypass, + .gpio_rx_mix_bp = gpio->rx_mix_bp, + .gpio_tx_amp = gpio->tx_amp, + .gpio_tx = gpio->tx, + .gpio_mix_bypass = gpio->mix_bypass, + .gpio_rx = gpio->rx, + .gpio_no_tx_amp_pwr = gpio->no_tx_amp_pwr, + .gpio_amp_bypass = gpio->amp_bypass, + .gpio_rx_amp = gpio->rx_amp, + .gpio_no_rx_amp_pwr = gpio->no_rx_amp_pwr, + }; + #ifdef IS_H1_R9 + if (IS_H1_R9) { + rf_path->gpio_rx = gpio->h1r9_rx; + rf_path->gpio_h1r9_no_ant_pwr = gpio->h1r9_no_ant_pwr; + } + #endif + } +#endif + +#ifdef IS_RAD1O + if (IS_RAD1O) { + *rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_tx_rx_n = gpio->tx_rx_n, + .gpio_tx_rx = gpio->tx_rx, + .gpio_by_mix = gpio->by_mix, + .gpio_by_mix_n = gpio->by_mix_n, + .gpio_by_amp = gpio->by_amp, + .gpio_by_amp_n = gpio->by_amp_n, + .gpio_mixer_en = gpio->mixer_en, + .gpio_low_high_filt = gpio->low_high_filt, + .gpio_low_high_filt_n = gpio->low_high_filt_n, + .gpio_tx_amp = gpio->tx_amp, + .gpio_rx_lna = gpio->rx_lna, + }; + } +#endif + +#ifdef IS_PRALINE + if (IS_PRALINE) { + *rf_path = (rf_path_t){ + .switchctrl = 0, + .gpio_tx_en = gpio->tx_en, + .gpio_mix_en_n = gpio->mix_en_n, + .gpio_lpf_en = gpio->lpf_en, + .gpio_rf_amp_en = gpio->rf_amp_en, + .gpio_ant_bias_en_n = gpio->ant_bias_en_n, + }; + if ((detected_revision() == BOARD_REV_PRALINE_R1_0) || + (detected_revision() == BOARD_REV_GSG_PRALINE_R1_0)) { + rf_path->gpio_mix_en_n = gpio->mix_en_n_r1_0; + } + scu_pinmux(scu->PINMUX_FPGA_CRESET, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + scu_pinmux(scu->PINMUX_FPGA_CDONE, SCU_GPIO_PUP | SCU_CONF_FUNCTION4); + scu_pinmux(scu->PINMUX_FPGA_SPI_CS, SCU_GPIO_NOPULL | SCU_CONF_FUNCTION0); + } +#endif + #ifdef IS_HACKRF_ONE if (IS_HACKRF_ONE) { /* Configure RF switch control signals */ diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 944b9556..c4d32fae 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -430,12 +430,14 @@ int main(void) pins_shutdown(); sgpio_pin_shutdown(&sgpio_config); + rf_path_pin_shutdown(); if (board_id != BOARD_ID_RAD1O) { clock_gen_shutdown(); } delay_us_at_mhz(10000, 96); pins_setup(); sgpio_configure_pin_functions(&sgpio_config); + rf_path_pin_setup(&rf_path); #ifdef IS_PRALINE if (IS_PRALINE) { enable_3v3aux_power(); diff --git a/firmware/hackrf_usb/usb_api_board_info.c b/firmware/hackrf_usb/usb_api_board_info.c index 671cb862..a5bad4d8 100644 --- a/firmware/hackrf_usb/usb_api_board_info.c +++ b/firmware/hackrf_usb/usb_api_board_info.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,7 @@ usb_request_status_t usb_vendor_request_reset( if (stage == USB_TRANSFER_STAGE_SETUP) { pins_shutdown(); sgpio_pin_shutdown(&sgpio_config); + rf_path_pin_shutdown(); clock_gen_shutdown(); #ifdef IS_H1_R9 if (IS_H1_R9) { From d534e1d019c6841d76a7d8a9c4f6f66c828ea719 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 18 May 2026 16:56:31 +0100 Subject: [PATCH 145/161] Move mixer-related bits out of pins_setup. --- firmware/common/pins.c | 3 --- firmware/hackrf_usb/hackrf_usb.c | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 97f3f413..b2d89b73 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -28,7 +28,6 @@ #include "cpld_jtag.h" #include "gpio.h" #include "leds.h" -#include "mixer.h" #include "platform_detect.h" #include "platform_gpio.h" #include "platform_scu.h" @@ -281,8 +280,6 @@ void pins_setup(void) } #endif - mixer_bus_setup(&mixer); - /* Configure external clock in */ scu_pinmux(scu->PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1); } diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index c4d32fae..b6161cc8 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,6 @@ #include #endif #ifdef IS_NOT_RAD1O - #include #include #endif #ifdef IS_PRALINE @@ -436,6 +436,7 @@ int main(void) } delay_us_at_mhz(10000, 96); pins_setup(); + mixer_bus_setup(&mixer); sgpio_configure_pin_functions(&sgpio_config); rf_path_pin_setup(&rf_path); #ifdef IS_PRALINE From 0585339269c9ae4a2ae760e68c35543d4ae2acb7 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 18 May 2026 18:30:59 +0100 Subject: [PATCH 146/161] Move cpld_jtag-related bits out of pins_setup. --- firmware/common/cpld_jtag.c | 23 +++++++++++++++++++++++ firmware/common/cpld_jtag.h | 1 + firmware/common/pins.c | 18 ------------------ firmware/hackrf_usb/hackrf_usb.c | 5 ++--- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/firmware/common/cpld_jtag.c b/firmware/common/cpld_jtag.c index e6413ba4..ab998808 100644 --- a/firmware/common/cpld_jtag.c +++ b/firmware/common/cpld_jtag.c @@ -22,6 +22,7 @@ #include "cpld_jtag.h" #include "platform_detect.h" +#include "platform_gpio.h" #ifdef IS_NOT_PRALINE #include #include @@ -42,6 +43,28 @@ jtag_t jtag_cpld = { .gpio = &jtag_gpio_cpld, }; +void cpld_jtag_pin_setup(void) +{ + const platform_gpio_t* gpio = platform_gpio(); + + jtag_gpio_cpld.gpio_tck = gpio->cpld_tck; + +#ifdef IS_NOT_PRALINE + if (IS_NOT_PRALINE) { + jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; + jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; + jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; + } +#endif + +#ifdef IS_EXPANSION_COMPATIBLE + if (IS_EXPANSION_COMPATIBLE) { + jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; + jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; + } +#endif +} + void cpld_jtag_take(jtag_t* const jtag) { const jtag_gpio_t* const gpio = jtag->gpio; diff --git a/firmware/common/cpld_jtag.h b/firmware/common/cpld_jtag.h index c1f707d4..39cfc1e7 100644 --- a/firmware/common/cpld_jtag.h +++ b/firmware/common/cpld_jtag.h @@ -45,6 +45,7 @@ typedef struct { typedef void (*refill_buffer_cb)(void); +void cpld_jtag_pin_setup(void); void cpld_jtag_take(jtag_t* const jtag); void cpld_jtag_release(jtag_t* const jtag); diff --git a/firmware/common/pins.c b/firmware/common/pins.c index b2d89b73..016b4520 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -25,7 +25,6 @@ #include -#include "cpld_jtag.h" #include "gpio.h" #include "leds.h" #include "platform_detect.h" @@ -263,23 +262,6 @@ void pins_setup(void) } #endif - jtag_gpio_cpld.gpio_tck = gpio->cpld_tck; - -#ifdef IS_NOT_PRALINE - if (IS_NOT_PRALINE) { - jtag_gpio_cpld.gpio_tms = gpio->cpld_tms; - jtag_gpio_cpld.gpio_tdi = gpio->cpld_tdi; - jtag_gpio_cpld.gpio_tdo = gpio->cpld_tdo; - } -#endif - -#ifdef IS_EXPANSION_COMPATIBLE - if (IS_EXPANSION_COMPATIBLE) { - jtag_gpio_cpld.gpio_pp_tms = gpio->cpld_pp_tms; - jtag_gpio_cpld.gpio_pp_tdo = gpio->cpld_pp_tdo; - } -#endif - /* Configure external clock in */ scu_pinmux(scu->PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1); } diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index b6161cc8..7135332e 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -67,9 +68,6 @@ #include #endif #endif -#ifdef IS_NOT_PRALINE - #include -#endif #include "usb_api_adc.h" #include "usb_api_board_info.h" @@ -436,6 +434,7 @@ int main(void) } delay_us_at_mhz(10000, 96); pins_setup(); + cpld_jtag_pin_setup(); mixer_bus_setup(&mixer); sgpio_configure_pin_functions(&sgpio_config); rf_path_pin_setup(&rf_path); From 977a9d24a625fb17709606c4772eaaa801e05c75 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 14 May 2026 16:56:23 +0100 Subject: [PATCH 147/161] Move ice40-related bits out of pins_setup. --- firmware/common/ice40_spi.c | 8 ++++++++ firmware/common/pins.c | 11 ----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/firmware/common/ice40_spi.c b/firmware/common/ice40_spi.c index ce646fd6..c2777745 100644 --- a/firmware/common/ice40_spi.c +++ b/firmware/common/ice40_spi.c @@ -26,6 +26,7 @@ #include #include "delay.h" +#include "platform_gpio.h" #include "platform_scu.h" /* Driver instance. */ @@ -42,8 +43,15 @@ ice40_spi_driver_t ice40 = { void ice40_spi_target_init(ice40_spi_driver_t* const drv) { + const platform_gpio_t* gpio = platform_gpio(); const platform_scu_t* scu = platform_scu(); + /* Configure drivers and driver pins */ + ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; + ice40.gpio_select = gpio->fpga_cfg_spi_cs; + ice40.gpio_creset = gpio->fpga_cfg_creset; + ice40.gpio_cdone = gpio->fpga_cfg_cdone; + /* Configure SSP1 Peripheral and relevant FPGA pins. */ scu_pinmux(scu->SSP1_CIPO, (SCU_SSP_IO | SCU_CONF_FUNCTION5)); scu_pinmux(scu->SSP1_COPI, (SCU_SSP_IO | SCU_CONF_FUNCTION5)); diff --git a/firmware/common/pins.c b/firmware/common/pins.c index 016b4520..51cfec0f 100644 --- a/firmware/common/pins.c +++ b/firmware/common/pins.c @@ -33,7 +33,6 @@ #include "power.h" #ifdef IS_PRALINE #include "clock_io.h" - #include "ice40_spi.h" #endif void pins_shutdown(void) @@ -252,16 +251,6 @@ void pins_setup(void) } #endif - /* Configure drivers and driver pins */ -#ifdef IS_PRALINE - if (IS_PRALINE) { - ssp_config_ice40_fpga.gpio_select = gpio->fpga_cfg_spi_cs; - ice40.gpio_select = gpio->fpga_cfg_spi_cs; - ice40.gpio_creset = gpio->fpga_cfg_creset; - ice40.gpio_cdone = gpio->fpga_cfg_cdone; - } -#endif - /* Configure external clock in */ scu_pinmux(scu->PINMUX_GP_CLKIN, SCU_CLK_IN | SCU_CONF_FUNCTION1); } From 9153f3d55e2530c8e6a7fbf9ec7386ecc7e9949f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 11 Jun 2026 20:23:35 +0100 Subject: [PATCH 148/161] Use PLL B for MCU_CLKIN on Praline. --- firmware/common/si5351c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 136f63ea..259c062a 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -604,6 +604,9 @@ void si5351c_init(si5351c_driver_t* const drv) drv->clk[2] = mcu_clkin; drv->mcu_clkin_id = 2; } + + // Use PLL B for MCU, since we reset PLL A during sample_rate_set. + drv->clk[drv->mcu_clkin_id].pll = SI5351C_PLL_B; } #endif } From 42b7f7703dba64b34f921ec58448a06652c22fec Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 12 Jun 2026 16:00:48 +0100 Subject: [PATCH 149/161] Fix management of CLKOUT/MCU_CLK synth sharing on H1R9. --- firmware/common/si5351c.c | 47 +++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/firmware/common/si5351c.c b/firmware/common/si5351c.c index 259c062a..a455a23e 100644 --- a/firmware/common/si5351c.c +++ b/firmware/common/si5351c.c @@ -274,17 +274,6 @@ void si5351c_enable_clock_outputs(si5351c_driver_t* const drv) SI5351C_OUTPUT_DISABLE); } si5351c_regs_commit(drv); - -#ifdef IS_H1_R9 - if (IS_H1_R9) { - const platform_gpio_t* gpio = platform_gpio(); - if (drv->clk[drv->clkout_id].output_enable) { - gpio_set(gpio->h1r9_clkout_en); - } else { - gpio_clear(gpio->h1r9_clkout_en); - } - } -#endif } void si5351c_set_int_mode( @@ -339,7 +328,7 @@ bool si5351c_clkin_signal_valid(si5351c_driver_t* const drv) } } -void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable) +static void si5351c_clkout_ms_enable(si5351c_driver_t* const drv, bool enable) { drv->clk[drv->clkout_id].output_enable = enable; drv->clk[drv->clkout_id].power_down = !enable; @@ -351,12 +340,42 @@ void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable) si5351c_enable_clock_outputs(drv); } +void si5351c_clkout_enable(si5351c_driver_t* const drv, bool enable) +{ +#ifdef IS_H1_R9 + if (IS_H1_R9) { + const platform_gpio_t* gpio = platform_gpio(); + + /* CLKOUT is shared with MCU_CLK, enable MS when either on. */ + bool mcu_clkin_enabled = gpio_read(gpio->h1r9_mcu_clk_en); + bool ms_needed = enable | mcu_clkin_enabled; + si5351c_clkout_ms_enable(drv, ms_needed); + + /* Set GPIO to gate CLKOUT output downstream of MS. */ + gpio_write(gpio->h1r9_clkout_en, enable); + } +#endif +#ifdef IS_NOT_H1_R9 + if (IS_NOT_H1_R9) { + /* We have a dedicated CLKOUT multisynth. */ + si5351c_clkout_ms_enable(drv, enable); + } +#endif +} + void si5351c_mcu_clkin_enable(si5351c_driver_t* const drv, bool enable) { #ifdef IS_H1_R9 if (IS_H1_R9) { - /* MCU clock is shared with CLKOUT. */ - si5351c_clkout_enable(drv, enable); + const platform_gpio_t* gpio = platform_gpio(); + + /* MCU_CLK is shared with CLKOUT, enable MS when either on. */ + bool clkout_enabled = gpio_read(gpio->h1r9_clkout_en); + bool ms_needed = enable | clkout_enabled; + si5351c_clkout_ms_enable(drv, ms_needed); + + /* Set GPIO to gate MCU_CLK output downstream of MS. */ + gpio_write(gpio->h1r9_mcu_clk_en, enable); } #endif #ifdef IS_NOT_H1_R9 From 092d034d805b030de7b885bcc4b8d7ad151e723f Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 21 May 2026 18:55:25 +0100 Subject: [PATCH 150/161] Simplify PortaPack detection and init API. --- firmware/common/clock_gen.c | 4 ++-- firmware/common/portapack.c | 16 ++++++++-------- firmware/common/portapack.h | 8 +++----- firmware/common/ui_portapack.c | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index 8a75074f..0cf21ab2 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -131,7 +131,7 @@ clock_source_t activate_best_clock_source(void) #ifdef IS_EXPANSION_COMPATIBLE if (IS_EXPANSION_COMPATIBLE) { /* Ensure PortaPack reference oscillator is off while checking for external clock input. */ - if (portapack_reference_oscillator && portapack()) { + if (portapack_present()) { portapack_reference_oscillator(false); } } @@ -146,7 +146,7 @@ clock_source_t activate_best_clock_source(void) #ifdef IS_EXPANSION_COMPATIBLE if (IS_EXPANSION_COMPATIBLE) { /* Enable PortaPack reference oscillator (if present), and check for valid clock. */ - if (portapack_reference_oscillator && portapack()) { + if (portapack_present()) { portapack_reference_oscillator(true); delay_ms(18); // for oscillator to enable. if (si5351c_clkin_signal_valid(&si5351c)) { diff --git a/firmware/common/portapack.c b/firmware/common/portapack.c index 0c3cbce2..b84bf03c 100644 --- a/firmware/common/portapack.c +++ b/firmware/common/portapack.c @@ -610,23 +610,23 @@ static bool portapack_detect(void) return idcode == 0x020A50DD || idcode == 0x00025610; } -static const portapack_t portapack_instance = {}; +static bool portapack_detected = false; -static const portapack_t* portapack_pointer = NULL; - -const portapack_t* portapack(void) +bool portapack_present(void) { - return portapack_pointer; + return portapack_detected; } -void portapack_init(void) +bool portapack_init(void) { if (portapack_detect()) { portapack_if_init(); portapack_lcd_reset(); portapack_lcd_init(); - portapack_pointer = &portapack_instance; + portapack_detected = true; } else { - portapack_pointer = NULL; + portapack_detected = false; } + + return portapack_detected; } diff --git a/firmware/common/portapack.h b/firmware/common/portapack.h index fdc369b8..3926bc3f 100644 --- a/firmware/common/portapack.h +++ b/firmware/common/portapack.h @@ -63,15 +63,13 @@ typedef struct { typedef struct { } portapack_t; -void portapack_init(void); +bool portapack_init(void); -/* If the "portapack" symbol is defined, PortaPack support is compiled in */ -/* If the portapack() call returns non-NULL, a PortaPack was detected and is initialized. */ -const portapack_t* portapack(void) __attribute__((weak)); +bool portapack_present(void); void portapack_backlight(const bool on); -void portapack_reference_oscillator(const bool on) __attribute__((weak)); +void portapack_reference_oscillator(const bool on); void portapack_fill_rectangle(const ui_rect_t rect, const ui_color_t color); diff --git a/firmware/common/ui_portapack.c b/firmware/common/ui_portapack.c index 85ffa705..3387b5eb 100644 --- a/firmware/common/ui_portapack.c +++ b/firmware/common/ui_portapack.c @@ -760,7 +760,7 @@ const hackrf_ui_t portapack_hackrf_ui = { const hackrf_ui_t* portapack_hackrf_ui_init(void) { - if (portapack()) { + if (portapack_present()) { return &portapack_hackrf_ui; } else { return NULL; From 3afd5d2acd75ba030389208b3a773c95fe9a9a48 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 15 Jun 2026 11:56:10 +0100 Subject: [PATCH 151/161] Move i2c_bus_start calls out of clock_gen_init and clock_gen_shutdown. --- firmware/common/clock_gen.c | 5 ----- firmware/hackrf_usb/hackrf_usb.c | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/firmware/common/clock_gen.c b/firmware/common/clock_gen.c index 8a75074f..b3d8fd4c 100644 --- a/firmware/common/clock_gen.c +++ b/firmware/common/clock_gen.c @@ -24,8 +24,6 @@ #include #include "hackrf_ui.h" -#include "i2c_bus.h" -#include "i2c_lpc.h" #include "platform_detect.h" #include "sgpio.h" #include "si5351c.h" @@ -36,8 +34,6 @@ void clock_gen_init(void) { - i2c_bus_start(si5351c.bus, &i2c_config_fast_clock); - si5351c_init(&si5351c); si5351c_disable_all_outputs(&si5351c); si5351c_disable_oeb_pin_control(&si5351c); @@ -120,7 +116,6 @@ void clock_gen_init(void) void clock_gen_shutdown(void) { - i2c_bus_start(si5351c.bus, &i2c_config_fast_clock); si5351c_disable_all_outputs(&si5351c); si5351c_disable_oeb_pin_control(&si5351c); si5351c_power_down_all_clocks(&si5351c); diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index ce24d879..6bc00417 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include #include #include @@ -426,6 +428,8 @@ int main(void) detect_hardware_platform(); board_id_t board_id = detected_platform(); + i2c_bus_start(&i2c0, &i2c_config_fast_clock); + pins_shutdown(); sgpio_pin_shutdown(&sgpio_config); rf_path_pin_shutdown(); From d6913807a658f361456f13aab10f366808f5ccd6 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 15 Jun 2026 11:56:40 +0100 Subject: [PATCH 152/161] Specify I2C clock in kHz, and adjust for CPU clock changes. --- firmware/common/i2c_lpc.c | 8 ++++++-- firmware/common/i2c_lpc.h | 2 +- firmware/hackrf_usb/hackrf_usb.c | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/firmware/common/i2c_lpc.c b/firmware/common/i2c_lpc.c index 9d6499fa..2c0fc5e2 100644 --- a/firmware/common/i2c_lpc.c +++ b/firmware/common/i2c_lpc.c @@ -25,6 +25,8 @@ #include #include +#include "cpu_clock.h" + /* Driver instances. */ i2c_bus_t i2c0 = { .obj = (void*) I2C0_BASE, @@ -41,7 +43,7 @@ i2c_bus_t i2c1 = { }; const i2c_lpc_config_t i2c_config_fast_clock = { - .duty_cycle_count = 255, + .clock_khz = 400, }; /* FIXME return i2c0 status from each function */ @@ -51,7 +53,9 @@ void i2c_lpc_start(i2c_bus_t* const bus, const void* const _config) const i2c_lpc_config_t* const config = _config; const uint32_t port = (uint32_t) bus->obj; - i2c_init(port, config->duty_cycle_count); + const uint16_t duty_cycle_count = + (cpu_clock_mhz * (2000 / config->clock_khz)) / 4; + i2c_init(port, duty_cycle_count); } void i2c_lpc_stop(i2c_bus_t* const bus) diff --git a/firmware/common/i2c_lpc.h b/firmware/common/i2c_lpc.h index e71cb362..2a2562ee 100644 --- a/firmware/common/i2c_lpc.h +++ b/firmware/common/i2c_lpc.h @@ -29,7 +29,7 @@ #include "i2c_bus.h" typedef struct { - const uint16_t duty_cycle_count; + const uint16_t clock_khz; } i2c_lpc_config_t; void i2c_lpc_start(i2c_bus_t* const bus, const void* const config); diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 6bc00417..813a9453 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -491,6 +491,9 @@ int main(void) #endif cpu_clock_init(); + /* Clock speed has changed, adjust I2C clock */ + i2c_bus_start(&i2c0, &i2c_config_fast_clock); + /* Wake the M0 */ ipc_halt_m0(); ipc_start_m0((uint32_t) &__ram_m0_start__); From aa76109b6ac96d7b2d2ad45cc5406eefbc435c35 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 15 Jun 2026 13:23:11 +0100 Subject: [PATCH 153/161] CI: Bump actions/checkout to v6. --- .github/workflows/build.yml | 4 ++-- .github/workflows/clang-format-check.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02239f82..6db34ce5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: shell: '${{ matrix.sys.shell }} {0}' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup cmake uses: lukka/get-cmake@latest @@ -179,7 +179,7 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index 0bf33429..d498d609 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -16,7 +16,7 @@ jobs: - check: 'firmware/hackrf_usb' exclude: '' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run clang-format-action uses: jidicula/clang-format-action@v4.6.2 with: From 73562ded31e91db216a99d2dc5383a9a6ee388a4 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 15 Jun 2026 13:59:17 +0100 Subject: [PATCH 154/161] CI: Simplify host build matrix and specify CMake generators. --- .github/workflows/build.yml | 95 ++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6db34ce5..e869824d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,44 +17,63 @@ jobs: host: strategy: matrix: - sys: - - {os: 'macos', shell: 'bash'} - - {os: 'ubuntu', shell: 'bash'} - - {os: 'windows', shell: 'pwsh'} - - {os: 'windows', shell: 'msys2'} - cmake: ['3.10.0', '3.16.0', '3.21.0', '4.0.0', 'latest'] + os: ['macos', 'ubuntu', 'windows'] + cmake: ['3.10.0', 'latest'] + compiler: ['gcc', 'msvc'] exclude: - # GitHub runners use Visual Studio 2022. Support added in CMake 3.21. - - sys: {os: 'windows', shell: 'pwsh'} + # MSVC only runs on Windows. + - os: 'macos' + compiler: 'msvc' + - os: 'ubuntu' + compiler: 'msvc' + # Only use the latest CMake on Windows. For MSVC, we can't use + # older CMake versions since the GitHub runners only have the + # latest Visual Studio release installed, and older CMake VS + # generators won't work. For GCC, we get curent CMake from the + # MSYS2 packages. + - os: 'windows' cmake: '3.10.0' - - sys: {os: 'windows', shell: 'pwsh'} - cmake: '3.16.0' - # MSYS2 always supplies the latest cmake. - - sys: {os: 'windows', shell: 'msys2'} - cmake: '3.10.0' - - sys: {os: 'windows', shell: 'msys2'} - cmake: '3.16.0' - - sys: {os: 'windows', shell: 'msys2'} - cmake: '3.21.0' - - sys: {os: 'windows', shell: 'msys2'} - cmake: '4.0.0' include: - - sys: {os: 'windows', shell: 'pwsh'} + # Shell selection + - os: 'macos' + shell: 'bash' + - os: 'ubuntu' + shell: 'bash' + - os: 'windows' + compiler: 'msvc' + shell: 'pwsh' + - os: 'windows' + compiler: 'gcc' + shell: 'msys2' + # CMake generator selection + - os: 'macos' + generator: 'Unix Makefiles' + - os: 'ubuntu' + generator: 'Unix Makefiles' + - os: 'windows' + compiler: 'msvc' + generator: 'Visual Studio 18 2026' + - os: 'windows' + compiler: 'gcc' + generator: 'MSYS Makefiles' + - os: 'windows' + compiler: 'msvc' cmake_args: >- -DPKG_CONFIG_EXECUTABLE=C:/vcpkg/installed/x64-windows/tools/pkgconf/pkgconf.exe -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake --install-prefix=$env:GITHUB_WORKSPACE/install - - sys: {os: 'windows', shell: 'msys2'} + - os: 'windows' + compiler: 'gcc' cmake_args: >- --install-prefix=/usr/local # Don't cancel all builds when one fails fail-fast: false - runs-on: ${{ matrix.sys.os }}-latest + runs-on: ${{ matrix.os }}-latest defaults: run: - shell: '${{ matrix.sys.shell }} {0}' + shell: '${{ matrix.shell }} {0}' steps: - uses: actions/checkout@v6 @@ -63,24 +82,24 @@ jobs: uses: lukka/get-cmake@latest with: cmakeVersion: ${{ matrix.cmake }} - if: matrix.sys.shell != 'msys2' + if: matrix.shell != 'msys2' - name: Install dependencies (macOS) run: brew install fftw - if: matrix.sys.os == 'macos' + if: matrix.os == 'macos' - name: Install dependencies (Ubuntu) run: | sudo apt update sudo apt install libfftw3-dev libusb-1.0-0-dev - if: matrix.sys.os == 'ubuntu' + if: matrix.os == 'ubuntu' - name: Install dependencies (Windows) run: vcpkg install --triplet=x64-windows libusb fftw3 pthreads pkgconf - if: matrix.sys.os == 'windows' && matrix.sys.shell != 'msys2' + if: matrix.os == 'windows' && matrix.shell != 'msys2' - name: Setup MSYS (Windows) - if: matrix.sys.os == 'windows' && matrix.sys.shell == 'msys2' + if: matrix.os == 'windows' && matrix.shell == 'msys2' uses: msys2/setup-msys2@v2 with: msystem: UCRT64 @@ -98,7 +117,7 @@ jobs: run: | cmake -E make_directory host/build cd host/build - cmake .. -DCMAKE_BUILD_TYPE=Release ${{matrix.cmake_args}} + cmake .. -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE=Release ${{matrix.cmake_args}} cmake --build . --config Release # Build libhackrf ONLY @@ -107,22 +126,22 @@ jobs: run: | cmake -E make_directory host/libhackrf/build cd host/libhackrf/build - cmake .. -DCMAKE_BUILD_TYPE=Release ${{ matrix.cmake_args }} + cmake .. -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE=Release ${{ matrix.cmake_args }} cmake --build . --config Release - name: Install (libhackrf) run: | sudo cmake --install host/libhackrf/build --config Release - if: matrix.sys.os != 'windows' && matrix.cmake != '3.10.0' + if: matrix.os != 'windows' && matrix.cmake != '3.10.0' - name: Install (libhackrf, CMake 3.10) run: | sudo cmake --build host/libhackrf/build --target install --config Release - if: matrix.sys.os != 'windows' && matrix.cmake == '3.10.0' + if: matrix.os != 'windows' && matrix.cmake == '3.10.0' - name: Install (libhackrf, Windows) run: cmake --install host/libhackrf/build --config Release - if: matrix.sys.os == 'windows' + if: matrix.os == 'windows' # Build hackrf-tools ONLY @@ -130,22 +149,22 @@ jobs: run: | cmake -E make_directory host/hackrf-tools/build cd host/hackrf-tools/build - cmake .. -DCMAKE_BUILD_TYPE=Release ${{ matrix.cmake_args }} + cmake .. -G "${{ matrix.generator }}" -DCMAKE_BUILD_TYPE=Release ${{ matrix.cmake_args }} cmake --build . --config Release - name: Install (hackrf-tools) run: | sudo cmake --install host/hackrf-tools/build --config Release - if: matrix.sys.os != 'windows' && matrix.cmake != '3.10.0' + if: matrix.os != 'windows' && matrix.cmake != '3.10.0' - name: Install (hackrf-tools, CMake 3.10) run: | sudo cmake --build host/hackrf-tools/build --target install --config Release - if: matrix.sys.os != 'windows' && matrix.cmake == '3.10.0' + if: matrix.os != 'windows' && matrix.cmake == '3.10.0' - name: Install (hackrf-tools, Windows) run: cmake --install host/hackrf-tools/build --config Release - if: matrix.sys.os == 'windows' + if: matrix.os == 'windows' # Publish the contents of install/bin (which should be the combination libhackrf and host-tools) for Windows - name: Publish Artifacts (Windows) @@ -153,7 +172,7 @@ jobs: with: name: hackrf-tools-windows path: ${{github.workspace}}/install/bin - if: matrix.sys.os == 'windows' && matrix.cmake == 'latest' && matrix.sys.shell == 'pwsh' + if: matrix.os == 'windows' && matrix.cmake == 'latest' && matrix.shell == 'pwsh' firmware: strategy: From 3e274a881d51504189bafebe0f2ef5bf38c79de1 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 15 Jun 2026 15:07:06 +0100 Subject: [PATCH 155/161] CI: Simplify install steps. --- .github/workflows/build.yml | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e869824d..64aa1bee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,6 +66,19 @@ jobs: compiler: 'gcc' cmake_args: >- --install-prefix=/usr/local + # CMake 3.10 uses --build with --target install + - cmake: '3.10.0' + install_cmd: --build + install_args: --target install + # Later versions just use --install + - cmake: 'latest' + install_cmd: --install + # We need sudo on MacOS and Ubuntu only + - os: macos + sudo: sudo + - os: ubuntu + sudo: sudo + # Don't cancel all builds when one fails fail-fast: false @@ -131,17 +144,7 @@ jobs: - name: Install (libhackrf) run: | - sudo cmake --install host/libhackrf/build --config Release - if: matrix.os != 'windows' && matrix.cmake != '3.10.0' - - - name: Install (libhackrf, CMake 3.10) - run: | - sudo cmake --build host/libhackrf/build --target install --config Release - if: matrix.os != 'windows' && matrix.cmake == '3.10.0' - - - name: Install (libhackrf, Windows) - run: cmake --install host/libhackrf/build --config Release - if: matrix.os == 'windows' + ${{ matrix.sudo }} cmake ${{ matrix.install_cmd }} host/libhackrf/build ${{ matrix.install_args }} --config Release # Build hackrf-tools ONLY @@ -154,17 +157,7 @@ jobs: - name: Install (hackrf-tools) run: | - sudo cmake --install host/hackrf-tools/build --config Release - if: matrix.os != 'windows' && matrix.cmake != '3.10.0' - - - name: Install (hackrf-tools, CMake 3.10) - run: | - sudo cmake --build host/hackrf-tools/build --target install --config Release - if: matrix.os != 'windows' && matrix.cmake == '3.10.0' - - - name: Install (hackrf-tools, Windows) - run: cmake --install host/hackrf-tools/build --config Release - if: matrix.os == 'windows' + ${{ matrix.sudo }} cmake ${{ matrix.install_cmd }} host/hackrf-tools/build ${{ matrix.install_args }} # Publish the contents of install/bin (which should be the combination libhackrf and host-tools) for Windows - name: Publish Artifacts (Windows) From eb8bc546a84b2774f1131bfb9b8d21b742b8d1e0 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 15 Jun 2026 15:32:28 +0100 Subject: [PATCH 156/161] CI: Build with more CMake and Visual Studio versions. --- .github/workflows/build.yml | 41 +++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64aa1bee..21593d8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: os: ['macos', 'ubuntu', 'windows'] - cmake: ['3.10.0', 'latest'] + cmake: ['3.10.0', '3.21.7', '4.2.6', 'latest'] compiler: ['gcc', 'msvc'] exclude: # MSVC only runs on Windows. @@ -26,13 +26,14 @@ jobs: compiler: 'msvc' - os: 'ubuntu' compiler: 'msvc' - # Only use the latest CMake on Windows. For MSVC, we can't use - # older CMake versions since the GitHub runners only have the - # latest Visual Studio release installed, and older CMake VS - # generators won't work. For GCC, we get curent CMake from the - # MSYS2 packages. + # We can't use the oldest CMake versions on Windows. - os: 'windows' cmake: '3.10.0' + # The in-between CMake versions aren't so intersting with GCC. + - compiler: 'gcc' + cmake: '3.21.7' + - compiler: 'gcc' + cmake: '4.2.6' include: # Shell selection - os: 'macos' @@ -45,17 +46,31 @@ jobs: - os: 'windows' compiler: 'gcc' shell: 'msys2' + # MSVC version selection + - cmake: '3.21.7' + msvc: '17.14' + - cmake: '4.2.6' + msvc: 'latest' + - cmake: 'latest' + msvc: 'latest' # CMake generator selection - os: 'macos' generator: 'Unix Makefiles' - os: 'ubuntu' generator: 'Unix Makefiles' - - os: 'windows' - compiler: 'msvc' + - compiler: 'msvc' + cmake: '3.21.7' + generator: 'Visual Studio 17 2022' + - compiler: 'msvc' + cmake: '4.2.6' + generator: 'Visual Studio 18 2026' + - compiler: 'msvc' + cmake: 'latest' generator: 'Visual Studio 18 2026' - os: 'windows' compiler: 'gcc' generator: 'MSYS Makefiles' + # Additional CMake arguments - os: 'windows' compiler: 'msvc' cmake_args: >- @@ -71,6 +86,10 @@ jobs: install_cmd: --build install_args: --target install # Later versions just use --install + - cmake: '3.21.7' + install_cmd: --install + - cmake: '4.2.6' + install_cmd: --install - cmake: 'latest' install_cmd: --install # We need sudo on MacOS and Ubuntu only @@ -97,6 +116,12 @@ jobs: cmakeVersion: ${{ matrix.cmake }} if: matrix.shell != 'msys2' + - name: Install MSVC (Windows) + uses: k3DW/setup-msvc@v1 + with: + vs-version: ${{ matrix.msvc }} + if: matrix.compiler == 'msvc' && matrix.msvc != 'latest' + - name: Install dependencies (macOS) run: brew install fftw if: matrix.os == 'macos' From 74c3b730fa95163bcd4a3dbff9f3b2d60e174d22 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Mon, 29 Jun 2026 19:28:50 +0100 Subject: [PATCH 157/161] Add 1.63 fixed point type. --- firmware/common/fixed_point.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/firmware/common/fixed_point.h b/firmware/common/fixed_point.h index b6908797..974e34d2 100644 --- a/firmware/common/fixed_point.h +++ b/firmware/common/fixed_point.h @@ -54,3 +54,20 @@ typedef uint64_t fp_28_36_t; #define SR_FP_MHZ(mhz) (mhz##ULL * SR_FP_ONE_MHZ) #define SR_FP_KHZ(khz) (khz##ULL * SR_FP_ONE_KHZ) #define SR_FP_HZ(hz) (hz##ULL * SR_FP_ONE_HZ) + +/* 1.63 fixed point */ +typedef uint64_t fp_1_63_t; + +#define FRAC_ONE (1ULL << 63) + +/* one hundredth in 1.63 fixed point */ +#define FRAC_ONE_PERCENT (FRAC_ONE / (100ULL)) + +/* one thousandth in 1.63 fixed point */ +#define FRAC_ONE_PERMILLE (FRAC_ONE / (1000ULL)) + +/* one millionth in 1.63 fixed point */ +#define FRAC_ONE_PPM (FRAC_ONE / (1000000ULL)) + +/* one billionth in 1.63 fixed point */ +#define FRAC_ONE_PPB (FRAC_ONE / (1000000000ULL)) From dda161cb1f588bc7bc6a1888958b2d6f014f4f49 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 30 Jun 2026 01:56:12 +0100 Subject: [PATCH 158/161] Add multiply & divide implementations for 128-bit unsigned integers. --- firmware/common/u128.c | 88 ++++++++++++++++++++++++++++++++++++ firmware/common/u128.h | 32 +++++++++++++ firmware/hackrf-common.cmake | 1 + 3 files changed, 121 insertions(+) create mode 100644 firmware/common/u128.c create mode 100644 firmware/common/u128.h diff --git a/firmware/common/u128.c b/firmware/common/u128.c new file mode 100644 index 00000000..3543f332 --- /dev/null +++ b/firmware/common/u128.c @@ -0,0 +1,88 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "u128.h" + +/* From https://stackoverflow.com/a/58381061 */ + +u128 u128_multiply(uint64_t lhs, uint64_t rhs) +{ + uint64_t lo_lo = (lhs & 0xFFFFFFFF) * (rhs & 0xFFFFFFFF); + uint64_t hi_lo = (lhs >> 32) * (rhs & 0xFFFFFFFF); + uint64_t lo_hi = (lhs & 0xFFFFFFFF) * (rhs >> 32); + uint64_t hi_hi = (lhs >> 32) * (rhs >> 32); + uint64_t cross = ((lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi); + return (u128){ + .hi = ((hi_lo >> 32) + (cross >> 32) + hi_hi), + .lo = ((cross << 32) | (lo_lo & 0xFFFFFFFF)), + }; +} + +/* From https://stackoverflow.com/a/70052545 */ + +/* clang-format off */ + +#define SUBCcc(a,b,cy,t0,t1,t2) \ + (t0=(b)+cy, t1=(a), cy=t0= divisor + rem.lo = tmp.lo; + rem.hi = tmp.hi; + quot.lo |= 1; + } + } + + return quot; +} diff --git a/firmware/common/u128.h b/firmware/common/u128.h new file mode 100644 index 00000000..c51dc5f5 --- /dev/null +++ b/firmware/common/u128.h @@ -0,0 +1,32 @@ +/* + * Copyright 2026 Great Scott Gadgets + * + * This file is part of HackRF. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +typedef struct { + uint64_t hi; + uint64_t lo; +} u128; + +u128 u128_multiply(uint64_t a, uint64_t b); +u128 u128_divide(u128 dvnd, u128 dvsr); diff --git a/firmware/hackrf-common.cmake b/firmware/hackrf-common.cmake index dfc67d9e..8f6698da 100644 --- a/firmware/hackrf-common.cmake +++ b/firmware/hackrf-common.cmake @@ -203,6 +203,7 @@ macro(DeclareTargets) ${PATH_HACKRF_FIRMWARE_COMMON}/adc.c ${PATH_HACKRF_FIRMWARE_COMMON}/da7219.c ${PATH_HACKRF_FIRMWARE_COMMON}/max283x.c + ${PATH_HACKRF_FIRMWARE_COMMON}/u128.c ) if(BOARD STREQUAL "RAD1O") From 8bfbd716da361e49bcaefb960b12ea837a2dffb8 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Tue, 30 Jun 2026 02:17:27 +0100 Subject: [PATCH 159/161] Add RADIO_CLOCK_CORRECTION register, bump USB API to 1.13. --- firmware/common/radio.c | 96 ++++++++++++++++++++++++++-- firmware/common/radio.h | 18 ++++-- firmware/hackrf_usb/usb_descriptor.c | 2 +- host/hackrf-tools/src/hackrf_debug.c | 2 +- 4 files changed, 103 insertions(+), 15 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index f8b2e514..6bf89784 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -34,6 +34,7 @@ #include "rf_path.h" #include "transceiver_mode.h" #include "tuning.h" +#include "u128.h" #ifdef IS_PRALINE #include "fpga.h" #include "tune_config.h" @@ -132,6 +133,75 @@ static uint32_t radio_update_direction(radio_t* const radio, uint64_t* bank) return (1 << RADIO_OPMODE); } +/* Apply correction to a true frequency to get an adjusted frequency. */ +static uint64_t adjust(uint64_t freq, uint64_t correction) +{ + /* skip if we can */ + if (correction == FRAC_ONE) { + return freq; + } + + u128 product = u128_multiply(freq, correction); + + /* product >> 63 */ + return (product.hi << 1) | (product.lo >> 63); +} + +/* Given an adjusted frequency and correction, return the true frequency. */ +static uint64_t unadjust(uint64_t freq, uint64_t correction) +{ + /* skip if we can */ + if (correction == FRAC_ONE) { + return freq; + } + + /* (1 << (63 + 63)) */ + u128 numerator = (u128){ + .hi = 1ULL << 62, + .lo = 0, + }; + u128 denominator = (u128){ + .hi = 0, + .lo = correction, + }; + u128 reciprocal = u128_divide(numerator, denominator); + return adjust(freq, reciprocal.lo); +} + +static uint64_t restrict_correction(uint64_t requested_correction) +{ + /* Correction should be in the range 0.99 to 1.01 */ + const uint64_t min_correction = FRAC_ONE - FRAC_ONE_PERCENT; + const uint64_t max_correction = FRAC_ONE + FRAC_ONE_PERCENT; + if (requested_correction >= min_correction && + requested_correction <= max_correction) { + return requested_correction; + } else { + return FRAC_ONE; + } +} + +static uint64_t set_lo(uint64_t freq_lo, uint64_t correction, bool apply) +{ + fp_40_24_t corrected_lo = adjust(freq_lo, correction); + fp_40_24_t achieved_lo = mixer_set_frequency(&mixer, corrected_lo, apply); + return unadjust(achieved_lo, correction); +} + +static uint64_t set_if(uint64_t freq_if, uint64_t correction, bool apply) +{ + fp_40_24_t corrected_if = adjust(freq_if, correction); + fp_40_24_t achieved_if = max283x_set_frequency(&max283x, corrected_if, apply); + return unadjust(achieved_if, correction); +} + +static uint64_t set_afe_rate(uint64_t afe_rate, uint64_t correction, bool apply) +{ + fp_28_36_t corrected_rate = adjust(afe_rate, correction); + fp_28_36_t achieved_rate = sample_rate_set(corrected_rate, apply); + return unadjust(achieved_rate, correction); +} + #define ABSOLUTE_MIN_AFE_RATE SR_FP_KHZ(200) #define ABSOLUTE_MAX_AFE_RATE SR_FP_KHZ(43600) #define MAX_SUPPORTED_AFE_RATE SR_FP_KHZ(40000) @@ -183,6 +253,9 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) bool new_n = false; const uint64_t requested_rate = bank[RADIO_SAMPLE_RATE]; + const uint64_t requested_correction = bank[RADIO_CLOCK_CORRECTION]; + + uint64_t correction = restrict_correction(requested_correction); if (requested_rate != RADIO_UNSET) { rate = MIN(requested_rate, MAX_MCU_RATE); @@ -236,7 +309,7 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) new_n = (n != previous_n); afe_rate = rate << n; - afe_rate = sample_rate_set(afe_rate, false); + afe_rate = set_afe_rate(afe_rate, correction, false); previous_rate = radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE]; if ((previous_n == RADIO_UNSET) || previous_rate == RADIO_UNSET) { previous_afe_rate = RADIO_UNSET; @@ -245,7 +318,7 @@ static uint32_t radio_update_sample_rate(radio_t* const radio, uint64_t* bank) } new_afe_rate = (afe_rate != previous_afe_rate); if (new_afe_rate) { - afe_rate = sample_rate_set(afe_rate, true); + afe_rate = set_afe_rate(afe_rate, correction, true); } rate = afe_rate >> n; @@ -373,6 +446,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) #ifdef IS_PRALINE const uint64_t requested_rotation = bank[RADIO_ROTATION]; #endif + const uint64_t requested_correction = bank[RADIO_CLOCK_CORRECTION]; const uint64_t applied_rf = radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_RF]; const uint64_t applied_if = radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_IF]; @@ -381,6 +455,8 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) radio->config[RADIO_BANK_APPLIED][RADIO_IMAGE_REJECT]; const uint64_t applied_rotation = radio->config[RADIO_BANK_APPLIED][RADIO_ROTATION]; + const uint64_t applied_correction = + radio->config[RADIO_BANK_APPLIED][RADIO_CLOCK_CORRECTION]; uint64_t freq_rf = applied_rf; uint64_t analog_rf = applied_rf; @@ -388,6 +464,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) uint64_t freq_lo = applied_lo; uint64_t img_reject = applied_img_reject; uint64_t rotation = applied_rotation; + uint64_t correction = applied_correction; uint64_t opmode = bank[RADIO_OPMODE]; if (opmode == RADIO_UNSET) { @@ -405,11 +482,12 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) img_reject = RF_PATH_FILTER_BYPASS; } } + correction = restrict_correction(requested_correction); if (requested_if != RADIO_UNSET) { - freq_if = max283x_set_frequency(&max283x, requested_if, false); + freq_if = set_if(requested_if, correction, false); } if (requested_lo != RADIO_UNSET) { - freq_lo = mixer_set_frequency(&mixer, freq_lo, false); + freq_lo = set_lo(requested_lo, correction, false); } #ifdef IS_PRALINE if (IS_PRALINE) { @@ -491,7 +569,7 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) freq_lo = RADIO_UNSET; } if (freq_lo != RADIO_UNSET) { - freq_lo = mixer_set_frequency(&mixer, freq_lo, false); + freq_lo = set_lo(freq_lo, correction, false); } } @@ -513,12 +591,12 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) /* Apply settings. */ if ((freq_if != applied_if) && (freq_if != RADIO_UNSET)) { - freq_if = max283x_set_frequency(&max283x, freq_if, true); + freq_if = set_if(freq_if, correction, true); radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_IF] = freq_if; changed |= (1 << RADIO_FREQUENCY_IF); } if ((freq_lo != applied_lo) && (freq_lo != RADIO_UNSET)) { - freq_lo = mixer_set_frequency(&mixer, freq_lo, true); + freq_lo = set_lo(freq_lo, correction, true); radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_LO] = freq_lo; changed |= (1 << RADIO_FREQUENCY_LO); } @@ -561,6 +639,10 @@ static uint32_t radio_update_frequency(radio_t* const radio, uint64_t* bank) radio->config[RADIO_BANK_APPLIED][RADIO_FREQUENCY_RF] = freq_rf; changed |= (1 << RADIO_FREQUENCY_RF); } + if ((correction != applied_correction) && (correction != RADIO_UNSET)) { + radio->config[RADIO_BANK_APPLIED][RADIO_CLOCK_CORRECTION] = correction; + changed |= (1 << RADIO_CLOCK_CORRECTION); + } return changed; } diff --git a/firmware/common/radio.h b/firmware/common/radio.h index 821c7fea..84b18c6e 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -165,17 +165,23 @@ typedef enum { * DC block enable of type bool. */ RADIO_DC_BLOCK = 22, + /** + * Correction factor for radio reference clock, of type fp_1_63_t. + */ + RADIO_CLOCK_CORRECTION = 23, } radio_register_t; -#define RADIO_NUM_REGS (23) +#define RADIO_NUM_REGS (24) #define RADIO_UNSET (0xffffffffffffffff) /* register groups for bitfield convenience */ -#define RADIO_REG_GROUP_RATE \ - ((1 << RADIO_SAMPLE_RATE) | (1 << RADIO_RESAMPLE_TX) | (1 << RADIO_RESAMPLE_RX)) -#define RADIO_REG_GROUP_FREQ \ - ((1 << RADIO_FREQUENCY_RF) | (1 << RADIO_FREQUENCY_IF) | \ - (1 << RADIO_FREQUENCY_LO) | (1 << RADIO_IMAGE_REJECT) | (1 << RADIO_ROTATION)) +#define RADIO_REG_GROUP_RATE \ + ((1 << RADIO_SAMPLE_RATE) | (1 << RADIO_RESAMPLE_TX) | \ + (1 << RADIO_RESAMPLE_RX) | (1 << RADIO_CLOCK_CORRECTION)) +#define RADIO_REG_GROUP_FREQ \ + ((1 << RADIO_FREQUENCY_RF) | (1 << RADIO_FREQUENCY_IF) | \ + (1 << RADIO_FREQUENCY_LO) | (1 << RADIO_IMAGE_REJECT) | (1 << RADIO_ROTATION) | \ + (1 << RADIO_CLOCK_CORRECTION)) #define RADIO_REG_GROUP_BW \ ((1 << RADIO_BB_BANDWIDTH_TX) | (1 << RADIO_BB_BANDWIDTH_RX) | \ (1 << RADIO_XCVR_TX_LPF) | (1 << RADIO_XCVR_RX_LPF) | \ diff --git a/firmware/hackrf_usb/usb_descriptor.c b/firmware/hackrf_usb/usb_descriptor.c index 005babb3..dde22e6e 100644 --- a/firmware/hackrf_usb/usb_descriptor.c +++ b/firmware/hackrf_usb/usb_descriptor.c @@ -28,7 +28,7 @@ #define USB_VENDOR_ID (0x1D50) -#define USB_API_VERSION (0x0112) +#define USB_API_VERSION (0x0113) #define USB_WORD(x) (x & 0xFF), ((x >> 8) & 0xFF) diff --git a/host/hackrf-tools/src/hackrf_debug.c b/host/hackrf-tools/src/hackrf_debug.c index 52ede9cb..5f8785b5 100644 --- a/host/hackrf-tools/src/hackrf_debug.c +++ b/host/hackrf-tools/src/hackrf_debug.c @@ -501,7 +501,7 @@ int radio_read_register( return result; } -#define RADIO_NUM_REGS (23) +#define RADIO_NUM_REGS (24) int radio_read_registers(hackrf_device* device, const uint8_t bank) { From 2b7ba8d7ab5d19b1df0eb31ab57cbc238875861c Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Thu, 16 Jul 2026 12:31:31 +0200 Subject: [PATCH 160/161] radio: rename RADIO_BANK_ACTIVE to RADIO_BANK_REQUESTED --- firmware/common/radio.c | 12 ++++---- firmware/common/radio.h | 8 +++--- firmware/hackrf_usb/usb_api_sweep.c | 4 +-- firmware/hackrf_usb/usb_api_transceiver.c | 34 +++++++++++------------ 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/firmware/common/radio.c b/firmware/common/radio.c index 6bf89784..36f65cad 100644 --- a/firmware/common/radio.c +++ b/firmware/common/radio.c @@ -54,7 +54,7 @@ void radio_init(radio_t* const radio) } } radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE] = TRANSCEIVER_MODE_OFF; - radio->config[RADIO_BANK_ACTIVE][RADIO_OPMODE] = TRANSCEIVER_MODE_OFF; + radio->config[RADIO_BANK_REQUESTED][RADIO_OPMODE] = TRANSCEIVER_MODE_OFF; radio->config[RADIO_BANK_IDLE][RADIO_OPMODE] = TRANSCEIVER_MODE_OFF; radio->config[RADIO_BANK_RX][RADIO_OPMODE] = TRANSCEIVER_MODE_RX; radio->config[RADIO_BANK_TX][RADIO_OPMODE] = TRANSCEIVER_MODE_TX; @@ -78,7 +78,7 @@ radio_error_t radio_reg_write( } switch (bank) { - case RADIO_BANK_ACTIVE: + case RADIO_BANK_REQUESTED: mark_dirty(radio, reg); /* fall through */ case RADIO_BANK_IDLE: @@ -937,7 +937,7 @@ bool radio_update(radio_t* const radio) return false; } radio->regs_dirty = 0; - memcpy(&tmp_bank[0], &(radio->config[RADIO_BANK_ACTIVE][0]), sizeof(tmp_bank)); + memcpy(&tmp_bank[0], &(radio->config[RADIO_BANK_REQUESTED][0]), sizeof(tmp_bank)); nvic_enable_irq(NVIC_USB0_IRQ); if ((dirty & RADIO_REG_GROUP_RATE) || @@ -998,14 +998,14 @@ void radio_switch_opmode(radio_t* const radio, const transceiver_mode_t mode) nvic_disable_irq(NVIC_USB0_IRQ); for (uint8_t reg = 0; reg < RADIO_NUM_REGS; reg++) { value = radio->config[source_bank][reg]; - previous = radio->config[RADIO_BANK_ACTIVE][reg]; + previous = radio->config[RADIO_BANK_REQUESTED][reg]; if ((value != RADIO_UNSET) && (value != previous)) { - radio->config[RADIO_BANK_ACTIVE][reg] = value; + radio->config[RADIO_BANK_REQUESTED][reg] = value; mark_dirty(radio, reg); } } - radio->config[RADIO_BANK_ACTIVE][RADIO_OPMODE] = mode; + radio->config[RADIO_BANK_REQUESTED][RADIO_OPMODE] = mode; mark_dirty(radio, RADIO_OPMODE); nvic_enable_irq(NVIC_USB0_IRQ); radio_update(radio); diff --git a/firmware/common/radio.h b/firmware/common/radio.h index 84b18c6e..8e12471a 100644 --- a/firmware/common/radio.h +++ b/firmware/common/radio.h @@ -191,7 +191,7 @@ typedef enum { (1 << RADIO_GAIN_RX_IF) | (1 << RADIO_GAIN_RX_BB)) /** - * Register bank RADIO_BANK_ACTIVE stores the active configuration. Active + * Register bank RADIO_BANK_REQUESTED stores the active configuration. Active * register settings are copied to the applied register when applied. * * The other three banks store settings that will be applied when switching to @@ -202,7 +202,7 @@ typedef enum { */ typedef enum { RADIO_BANK_APPLIED = 0, - RADIO_BANK_ACTIVE = 1, + RADIO_BANK_REQUESTED = 1, RADIO_BANK_IDLE = 2, RADIO_BANK_RX = 3, RADIO_BANK_TX = 4, @@ -237,7 +237,7 @@ typedef struct { void radio_init(radio_t* const radio); /** - * Write to one or more registers. Writes to RADIO_BANK_ACTIVE are applied at + * Write to one or more registers. Writes to RADIO_BANK_REQUESTED are applied at * the next radio_update(). Writes to RADIO_BANK_APPLIED are not supported. */ radio_error_t radio_reg_write( @@ -255,7 +255,7 @@ uint64_t radio_reg_read( const radio_register_t reg); /** - * Apply changes requested in RADIO_BANK_ACTIVE. + * Apply changes requested in RADIO_BANK_REQUESTED. * Return true if any changes were applied. */ bool radio_update(radio_t* const radio); diff --git a/firmware/hackrf_usb/usb_api_sweep.c b/firmware/hackrf_usb/usb_api_sweep.c index 0da2cf6e..1bea5dd2 100644 --- a/firmware/hackrf_usb/usb_api_sweep.c +++ b/firmware/hackrf_usb/usb_api_sweep.c @@ -132,7 +132,7 @@ usb_request_status_t usb_vendor_request_init_sweep( radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_FREQUENCY_RF, (sweep_freq + offset) * FP_ONE_HZ); usb_transfer_schedule_ack(endpoint->in); @@ -263,7 +263,7 @@ void sweep_mode(uint32_t seq) nvic_disable_irq(NVIC_USB0_IRQ); radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_FREQUENCY_RF, (sweep_freq + offset) * FP_ONE_HZ); nvic_enable_irq(NVIC_USB0_IRQ); diff --git a/firmware/hackrf_usb/usb_api_transceiver.c b/firmware/hackrf_usb/usb_api_transceiver.c index dbbbf661..e72a408b 100644 --- a/firmware/hackrf_usb/usb_api_transceiver.c +++ b/firmware/hackrf_usb/usb_api_transceiver.c @@ -94,12 +94,12 @@ usb_request_status_t usb_vendor_request_set_baseband_filter_bandwidth( (endpoint->setup.index << 16) | endpoint->setup.value; radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_BB_BANDWIDTH_TX, bandwidth); radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_BB_BANDWIDTH_RX, bandwidth); usb_transfer_schedule_ack(endpoint->in); @@ -123,22 +123,22 @@ usb_request_status_t usb_vendor_request_set_freq( set_freq_params.freq_mhz * 1000000ULL + set_freq_params.freq_hz; radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_FREQUENCY_RF, freq * FP_ONE_HZ); radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_FREQUENCY_IF, RADIO_UNSET); radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_FREQUENCY_LO, RADIO_UNSET); radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_IMAGE_REJECT, RADIO_UNSET); usb_transfer_schedule_ack(endpoint->in); @@ -160,17 +160,17 @@ usb_request_status_t usb_vendor_request_set_freq_explicit( } else if (stage == USB_TRANSFER_STAGE_DATA) { radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_FREQUENCY_IF, explicit_params.if_freq_hz * FP_ONE_HZ); radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_FREQUENCY_LO, explicit_params.lo_freq_hz * FP_ONE_HZ); radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_IMAGE_REJECT, explicit_params.path); usb_transfer_schedule_ack(endpoint->in); @@ -215,7 +215,7 @@ usb_request_status_t usb_vendor_request_set_sample_rate_frac( uint32_t numerator = set_sample_r_params.freq_hz; uint32_t denominator = set_sample_r_params.divider; uint64_t value = round_sample_rate(numerator, denominator); - radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_SAMPLE_RATE, value); + radio_reg_write(&radio, RADIO_BANK_REQUESTED, RADIO_SAMPLE_RATE, value); usb_transfer_schedule_ack(endpoint->in); } return USB_REQUEST_STATUS_OK; @@ -228,12 +228,12 @@ usb_request_status_t usb_vendor_request_set_amp_enable( if (stage == USB_TRANSFER_STAGE_SETUP) { radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_GAIN_TX_RF, endpoint->setup.value); radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_GAIN_RX_RF, endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -247,7 +247,7 @@ usb_request_status_t usb_vendor_request_set_lna_gain( { if (stage == USB_TRANSFER_STAGE_SETUP) { uint8_t gain = endpoint->setup.index; - radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_GAIN_RX_IF, gain); + radio_reg_write(&radio, RADIO_BANK_REQUESTED, RADIO_GAIN_RX_IF, gain); endpoint->buffer[0] = RADIO_OK; usb_transfer_schedule_block( endpoint->in, @@ -266,7 +266,7 @@ usb_request_status_t usb_vendor_request_set_vga_gain( { if (stage == USB_TRANSFER_STAGE_SETUP) { uint8_t gain = endpoint->setup.index; - radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_GAIN_RX_BB, gain); + radio_reg_write(&radio, RADIO_BANK_REQUESTED, RADIO_GAIN_RX_BB, gain); endpoint->buffer[0] = RADIO_OK; usb_transfer_schedule_block( endpoint->in, @@ -285,7 +285,7 @@ usb_request_status_t usb_vendor_request_set_txvga_gain( { if (stage == USB_TRANSFER_STAGE_SETUP) { uint8_t gain = endpoint->setup.index; - radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_GAIN_TX_IF, gain); + radio_reg_write(&radio, RADIO_BANK_REQUESTED, RADIO_GAIN_TX_IF, gain); endpoint->buffer[0] = RADIO_OK; usb_transfer_schedule_block( endpoint->in, @@ -315,7 +315,7 @@ usb_request_status_t usb_vendor_request_set_antenna_enable( if (stage == USB_TRANSFER_STAGE_SETUP) { radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_BIAS_TEE, endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); @@ -427,7 +427,7 @@ usb_request_status_t usb_vendor_request_set_hw_sync_mode( if (stage == USB_TRANSFER_STAGE_SETUP) { radio_reg_write( &radio, - RADIO_BANK_ACTIVE, + RADIO_BANK_REQUESTED, RADIO_TRIGGER, endpoint->setup.value); usb_transfer_schedule_ack(endpoint->in); From 6259086c9625ebef760f51305aae9a364a465a9a Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Tue, 21 Jul 2026 11:44:26 +0200 Subject: [PATCH 161/161] libhackrf: set a 500ms timeout for changing fpga bitstream --- host/libhackrf/src/hackrf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/host/libhackrf/src/hackrf.c b/host/libhackrf/src/hackrf.c index 7bafd5f3..4018ac63 100644 --- a/host/libhackrf/src/hackrf.c +++ b/host/libhackrf/src/hackrf.c @@ -57,6 +57,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSI #define DEFAULT_REQUEST_TIMEOUT 100 #define CPLD_WRITE_TIMEOUT 10000 #define SPIFLASH_WRITE_TIMEOUT 50000 // W25Q32JV max chip erase time +#define FPGA_BITSTREAM_TIMEOUT 500 // TODO: Factor this into a shared #include so that firmware can use // the same values. @@ -3496,7 +3497,7 @@ int ADDCALL hackrf_set_fpga_bitstream(hackrf_device* device, const uint8_t index 0, NULL, 0, - DEFAULT_REQUEST_TIMEOUT); + FPGA_BITSTREAM_TIMEOUT); if (result != 0) { last_libusb_error = result;

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/rf901EkpCLQNPWtrLnbDGTmUxmnue9PPPy36tabSu4XHUwMDFiW1tva1vWbc907L5vzrZeh9dvLH9iey40kejviTf1e9GdwyBcdTAwMThP3r55XHUwMDEz9zB63uihl+VYI8tcciZw3z/wd632X/R/aLH7YV98Izv9XHRvKlx1MDAxM1x1MDAwZqzGYa/eXHJmR1HX6KbHyfhWLzDdgWPFTbdwnVFqaMFcdTAwMThHUkmBqVq03kGrYNRQhFwiTTFnQiZaZ3Y/XHUwMDE4hndIbUiMKfTXRGNNXHUwMDE3d1xmLXswXGbgXHUwMDE2TLhBJEaUaso4hS+Le1x1MDAxZSb0toZcdTAwMTZXJoHvXVnbnuP54az/wFb4Xzznrtm7XHUwMDFh+N7U7cf3mLyvLi7iey5sx+lcdTAwMDR30ciwyLCgW0vjn81nT5auXHUwMDE39YJcdTAwMDdcdTAwMGWGrjWZpObqjc2eXHUwMDFkhOuEUXw1nN14v1x1MDAxZu3Xv/GcfHNk7YdcdTAwMWLmTlx1MDAxZGdx2Xb7VrhcclufblJPc/vzp6XunlhWOFx1MDAwMtNIcS3jZYyBRSlZvnrouVx1MDAxMchgd6RAXFzi+OGTXHUwMDFkXHUwMDAwV1x1MDAxMI15YTpcdTAwMTMrXuVwXHUwMDA2u1x04MUvMlx1MDAxZPfNhy5YSq1gX6XERC7aXHUwMDFk271anrfj9a7ip0RXv7zOw3Ljvd9cdTAwMTX9v+uHXHUwMDFlac66XHUwMDFmqX1w0GxmsVx1MDAxY1i3QSGMiaZcXHKZxjGShqJcdTAwMWNjJKjkXGLJXGaOMWFcdTAwMDZcZkIpXHUwMDAzKHNYXpZcdTAwMDUy4dVcdTAwMDI38E13MjZ9WOSfXHUwMDFivF3krI5ejFx1MDAxNZhcdFx1MDAwMluRg1+BWVx1MDAxMX45w5grTemPxm9cZsdcdTAwMTCG8Pon57WdznGtNzRtN7GRnlx1MDAxYnTs+4h6KHW1ZY5sJ4JlaqiGY1x1MDAwZsI12HKsi1x1MDAwNCBgXHUwMDE5XHUwMDAyXHUwMDFi3MKiOfDGcWtcdTAwMGbGg8dafnZ7PN9cdTAwMWXYrumclk7TnFx1MDAwNt6JNXmYaOBPreRKWHtcdTAwMGIjblx1MDAxMF5CXFx9dDZcdTAwMWIzb3zFJi25Z3kmXHUwMDFhje+yxDV935ulmUuVNlx1MDAxMGaKYqaFVIn9XHUwMDBmV0hcImEwXHUwMDAx5CSwR1IpnSWuklx1MDAwNngmRmFcYspJnlx1MDAwM0LwXHUwMDAwXHUwMDBlvkdppbgkQsbPeOFxmse3+Tx+3O14K8n8ypdlesOOMcKVTOJ3Tm7JMy7rkdywP4TB1rBcbtmtXHUwMDEwXHUwMDEzWnCRfOxcdTAwMWHsXHUwMDFle3Yy6lxuP/G3WrxcdTAwMDPRXHUwMDFmi+//vs69u1x1MDAxMKVR71xmPuPhXi1cclx1MDAwYlx1MDAxODH9oFx0W2a7g+U3sdx+QUvUq1x1MDAxMbJvaJn9XHUwMDA30NpL4SD0Tt6RXHUwMDFl2el6s5xFXHUwMDFheTfWe7t/XHUwMDFjrdWZXHUwMDFkXGbn27KCr1x1MDAxZnnmZNS4PT1jl3V60fXsk8O/XHUwMDBlVzJcdTAwMTlaXHRcdTAwMDNgRpWgXHUwMDE0QTxcdTAwMTlcdTAwMWKF2GQoXHUwMDAyK6oohKcx2Vx1MDAxNyZDXHUwMDEyXHUwMDAzXHUwMDFjXHLmhHKtOM1x9bAlXGJcdTAwMTFKhFawXHUwMDFmOlxmXHRwXHUwMDFj/L5YjbTVuPtqq1x1MDAwMelcdTAwMDXjXHUwMDEwn4m8oFx1MDAxNsK2wqhcdTAwMTbCXGItJUL6VzVcdTAwMWNFWFxyP/VcXJj+XHUwMDBlxuOUWT7vTNrTvf3xh1Pnw8zaudxdP+nFkEhRgF7KgEBcdTAwMWVrYIo1XHUwMDE3WHCw1XHC9JyS3ouLnu7pn9xy4MZcdTAwMWF5g9RCXHUwMDAxMXVeaMFIJptYWFxioSFhRlx1MDAwMv3wxKFcdTAwMDTPzYtcdTAwMDG/camsX/ddvq9cdTAwMGaDz2ft7sp4XHUwMDBl8VxiXGLjSlxiolx1MDAxMMdcIo1nIVxywCFcdTAwMThcdTAwMDdcYj5cdTAwMTBl2Vx1MDAxOJogXHUwMDFlXHUwMDFhkvijs4Bm2mA68anYXHUwMDFiXHUwMDAynsPPz47n1lx1MDAxYXhmXGKMO8nHMyaqMFx1MDAxMYZOglwihDdcdTAwMTJyXHUwMDE2ziF2zlx1MDAwYlx1MDAxMKrdk5133fessz3anur71vm+3IkzxVx1MDAxNFx1MDAxMKPUd9Hw5XXZsMO9k0DtXHUwMDFmd1x1MDAwZrZb3ufhO10/O3x3lj/sQ7C34riXx7rdXHUwMDFje+ez66Nue9Twj/X79t5q42ZcdTAwMWNjPrlVNeQuX9dcdTAwMTJVXHUwMDBiQlx1MDAxYkMqoVx00vBcdTAwMGaVS7xcdTAwMDbWXHUwMDEzVcZrXGbMXHUwMDE3oaiFXHSkXHUwMDE2RGGS5fUvIGrhb0TmP9cgs0BcdTAwMWNpcFG5ZMZcdTAwMWNcdTAwMTWRXHUwMDE5Ylx1MDAwNM1cdE7ky1x1MDAxNTgncJRgoelm4eujqrV7XHUwMDFiQFBXXHUwMDBivJqqd+2gtv+mXYW41YPJW36JvDWy+/2kg0srXFxPecpl0av0LarRvu7IyD09+njDr9Fw3G+cXHUwMDEwdnh0upZoXHL5jURcdTAwMTDCp+itNDUwo1pSXHUwMDAx/OZcIm6NNWtq0NBdU/D5kktcdTAwMTVcdTAwMTPhV6L3t/LVf61Bb1wimFZAK57E7aOshTKkX7BcdTAwMWKB6WWYVZmdViRan/5cdTAwMWOidf40qyFueWBSpkBcdEnLXHUwMDAybo2NUMnGYNshXHUwMDFmZ1lcdTAwMDWKSGFcYkRcdTAwMDBQXG5pqlTcP6FAUcJcdTAwMThkPDCO4Fx1MDAwMlVcdTAwMWZv/1xu+ePp6lx1MDAxY4aUXHUwMDA1XCIlyXBcdTAwMTKki2PTwmibKYS5XHUwMDE0skJcdTAwMDdcdTAwMWTpS5xrMFxymzC4Un2pXlxixag1g8J4vExcZr0kLiXUv3lcdMMqXHUwMDBlvFx1MDAxNspC/VxiTp5cdTAwMGZcdTAwMGU72XBh31pcdTAwMGZqUerll1/Z4JBcdTAwMWNl5lx1MDAxOUuOP5XQVZ7klNkoiDo55Fx1MDAwN0TyXHUwMDEw9VwiUS5cdTAwMTDbKMX4Q/JcdTAwMTD7tYWJXHUwMDEy3IB4XHUwMDE2XHUwMDAzLlx1MDAxOFi5vNQhpu+LSYpN0ofVTVx1MDAxMlx09VpBwKXn2CRCMlfjsIJJyFxyXHSvsJbjeVx1MDAxOaVcIvAtdX/KXHUwMDA2XHUwMDE1XHUwMDBi3N/ROuG0toZQ+m2eMlfP0Ci977mSbX/g7+5uXHUwMDBmXHUwMDA2nfp9bzjw0MpqJYSvhpI8VGSFojhdcVx1MDAwNjBcdTAwMDSjJSlcIlx1MDAxMD2Bw8lmPVxuXHUwMDFiXHUwMDAwXHTBMURf4LZ0TuxEw3NcdTAwMTGmOVxutX3KwcatYamUUJZcdTAwMTa/g1j596rHdjRcdTAwMGLKXHUwMDA3XHUwMDAzxjXDmlKVZ78kL5TkXHUwMDE5h1CMk81cdTAwMTT5xexyJMFb11He5Hra4uxcdTAwMTS3XHUwMDBlxp/a15PGVpJum0mNXHUwMDFme1x1MDAwN/fjY7IzpXrUu79u3e01XHUwMDBld1ZSRjNcdTAwMTYqe9CIXHUwMDA0IH1ccptbQszyeZZIXHUwMDExSoVcdTAwMDVyXHUwMDFhXHUwMDEyXHUwMDE2KqRglC6RMlRcdTAwMWG1lJIyJWW2XG6UaUOGOiNkRNBbMJVXPbdOuPD7kPBs9XBcdTAwMDFcdTAwMDDDXHSjXHUwMDA051x1MDAxZJJcdTAwMGJSqDFiTFxiwbCpelx1MDAxM75VXHTdXHUwMDE4iY/ynGt2k/4hoT8kkurvoSs+4dMyuuLSxKtRJE7Oj3a8XfdMclNMPu9eXHUwMDBlu62RXt2xMjavi4FMLqymSpFYgONkVDCiXHSkXHUwMDA2XCLvuIByI3nEl1x1MDAxMLOLj1x1MDAwMeOHvJxcdTAwMDPGrD5fh9WcSFxmVjNPmMBSXHUwMDE2ioswT1x1MDAwZTGy2qxmrszd/dncbrJcdTAwMDa5PJ/dXFw1d4/Odi/b2F/J3ZV60W/lncfKufx8fHCpj0xnXHUwMDFiXHUwMDA391x1MDAxZph/VcVB4LxcdTAwMGVIaVbRKX/5upa5Z1ximUuYrakhVFx1MDAxObPVy8HfOuz9uIaqKFC4MTj33C95aLNM3uhcXJclq4uei0/e2a51w1uew2nfXHUwMDEzXHUwMDBlcdkrZ6dejV+2W5/2fLTf9vbZSdup351cXOJcdTAwMWSxsl/milx1MDAwMD9cdTAwMTVcdTAwMTFz9qbrVZ/2yy/lOVVcdTAwMTH701x1MDAxYW5cdTAwMTlrzlx1MDAwNaIo1y1rpZYvL5itgYdMbFx1MDAxOG2X+bmLsXvsdO6mreOPnVtZ327ttVx1MDAwZiuoz/m27lNgjdepQyhhYfn7l9XRIGpwXG5cdTAwMDRkYVGvTpd7Pu09sVg6Z39xpmtwzlxcg3NcdTAwMWFBXHUwMDBlI7HK5Vx1MDAxYyS/hZwjklx1MDAxMsE5qzzDpWC0N1x1MDAxMsRcdTAwMTc/XHUwMDBlXHUwMDBin1x1MDAxNtafsOdURfOEQ8v8dKzkJarxsOW5QemvQTAxJGZR0Vx1MDAxM0VcIlFcdTAwMTZcdTAwMTn9XHUwMDFhRKsn9ORIK+ZcdTAwMDA7sNiSc1xuVj9HvVx1MDAxMtigXHUwMDFjKXDeXHUwMDFjvCtH66S9P0jM6puTofWdXHUwMDFkbHdcdTAwMWSyQ0BEwa7m1dRgJIpcdTAwMWSsXHUwMDEwSnJK5fORsyo9/SpcdTAwMDNk+KlnsFx1MDAxOI+YccgrXHUwMDFlyperX7WNXHUwMDBl5aODrYI3n38rOJQvmmV5NlDRLLPHdSXTzlx1MDAxY84tnzg+dXBXcDhcdTAwMTfNt2Ncclx1MDAxZWGbLVPYn3TGVs82nZyHXHUwMDE2tn3FmV95lFhmoFx1MDAxNVUl4kX4c70wRJSF4Vx1MDAxN4PsXHUwMDA3Qlx1MDAwMjzPbEROleNLJUKeMe6tU+DIXHSTXGbMTZ4x5sXVUVx1MDAxMlx1MDAxYyvs6iYqRmpcdTAwMWU5iVx1MDAwM8csXoNcdTAwMWZWilCIvqXum1reb2HTUlNcXKtcdTAwMWOqaJbl8eJGs6y6LOLrLO+D5Xs138AtczzuXHUwMDA0XHUwMDAwyMWSbN3Y1qxZXHUwMDFjuL2azy00XHUwMDE1VnSK/uXVl/9cdTAwMDEsMploIn0=RX DSP chainExtend to 8-bit I/QTX DSP chainEnableDC blockRound to 4-bit I/Q \ No newline at end of file diff --git a/docs/images/gateware-rx-only-basic-block-diagram.png b/docs/images/gateware-rx-only-basic-block-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..421ae2c76396799d808b7bbd725a406bbd760b2d GIT binary patch literal 138600 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 z zbF;OEdhRw_8_yDrGFh{Q&jPGlewRBgAYlJKUgPvG+IGo1-2%ONs?{FV1nf-~%^9nv zq6Lba2D=pXvRYyXM3z$i05e`(YzPc54XN4U3xO=Zio8FfA_@?mRnv zD+;2JEYE2d+r-Mnu$hxL)$1$Ma;a`@F@w*51;^CV;)4Y5w)qF~TWDjz_teVTEQy1S zZ+}My6GZZo#Pyv5^v*DFhKuP)eG0*AH`Km)=UpNHu9`NT|n;!n-S_GVdzwfX6DW;mnMK#7q&GlS#HP2j_PrhZ#z1lrrLMs|;lonSb_T=z$TTPU_dAsnxA|7mGxVtk|blZVwz(R`*ra0c0 zoz)Eb)j9sg;Xzb{!m}DRk$a`uIR8N4B=*j?*dZFY*T3URHzN={xnAmu)+~x3mVB5|4Pwa$F2`}o!8dgyc|=cQ6iCe zSIuJUTVSF`oybB#&+nII+SAH#T)e;jCfkkKd^128I zYAtRd51cdfAVhg&O)crR?xdK}HJf7Rh`f)9y_`A*hbZ;iZ zM%c)o?i09vo}0;z3HQ+oC8DL}(45U8<1?z(r?xk$QrHpNrpvt=aUj5(tItKH~Wra8xpVtXWg+^Vt>)V@7=Vhng2crot+mukrud_jmp#b0j_3T zC!6T`K$G4zCpWw9uJEeDX_N24hQ|ZUsexRn<%KXhRcmEJ4>@3ELb1`ox6o&CKp60& z?ge=;-a}s@T&UmCC@IfbW=IXI(3cxbo>M36ifD3yo)5e|OA$RjVg6?t?8pIVx7IyV zT}mTth=-#Q7m*S1wb$ir9C>&>hm6Zqvq;0zA0@Ssr&4%C_yW>@}r4W|3if-rS zyu1S4jzu#Qob9}x33U?MT@a_6Ij0sfA?JBmzcB;_X@l9<^G+0fCJ~xn6NDZNGAIUQ z=5nKk7HVGzWpCvKYcwp^Zng#A5(zs@VemADKSG{kG@U>2U)UZWic8HMr5av_Cb7Hz zJdA5jOz>=>Jw8h0641h$b2S=%Eo1aCXRmK{#ydB|`C6f!mX0!?wQ(qD6HB4R?$SWO z=-*s`K4**Et%+v+9g(Bs$NZ4!+TUGv`Dee{=ju(?)uNVCD^}$fJye$00y-uUTa%wO zuf2b(nD7?S|A43&v7`LH#^$7q-BN4S4#%gPvb^0l)zy0!`yi11nJz46RR3q9QV(vu zJn-R2>1ixKGG}N#j6dm?4`4&2!Z4 zgMAMBos~PE#N5=Z?U&saM7ZP=J+QMhr5)MHFIigxZVIS9q_Nk>H|G!NX!>TalLXUa za)O_54IMKhU+BppvC%NzgZKvL_#7M|Ti3203S{8kYH7a@vf?4&#}FWC@ogkw3!!$| zZ@?LOM@Szc3d*K$A^H2%E^px#hK65C zrx>4zof$O;9Q0IP4X2--D4)i`OEtKQcS>D`mU{ymHMT^FRQ)xI^$?Jn-kTK)?>`l4 z6c4Sbhjdu5wvBNvQPmHwk z94UVRNJTrSrs#+|FAM41b{u18ab2@$an7<(6IOfFYvU?^71FQUNC-vxFgY#fG<*C& zob-IW%~{?5gvG7}l?wU^9iLTBnUQs(z?8Z4gOM50iqH*4Nz}>eH&10 z{wCc+6-42Y+|Es(VMSMTT#KjhPk&OnYIGcINz${kv;9d9uFecDOCj3T{Ds0!-BEwL zs3~`B2z-Xi=)^rCUdiP%QlAZ}4}EYKKCqm>akcjGBhOh3m*Sz*XkF;^<*}IA4v|FY z+alq)gq+~jCFSs2=(xb$%HQIOl>#Ai^vVNd4sBHvqTd1)Ybb5E@v8Yh3ai<%->umT z<0@n4HqhPRZr_GAn_ASF9X?{Sy0U6biOy+T`P{SIp58~;kSQf0f}pBNieaY+ORgx7 zhE0~y;SqY>eAHhmx81z6?wOr6f`c}EX-lR{an&G^RSy3<_t?VD%>)xlRG9*3Vv zl`|DS=M{qs`M0?&Q>p<)(szGkGds-x;*DJSrwZbQ;}1sRnfAR$e@J&bgsacp3@S`5S$rss+nGKT2T@dM znMaWSO`*5OcgoTGC*EcfS#y#9bDBB;PnvGTOh)8L{)A`k%EMaST$!Z-MIRXFgCL5I z+yEkIss4V6u$#lpz`GhTlOO}T14h zYFLYqv;L&uZrsFYL|&uo1*XjG+c@F*NV(bMmCX9k=0=Y{ew4hw^)`zQN=$ch@C5N2 zk40_Vr3)*~v^g&oj83O?b1Qf^$F<7++?9VW7So9RD+d_hrkFW+U%l<3H_Yu}ugBsv z?fcMkWQLp5arTO%7 z;e(qE?}LL2RMek+I*kFj%WsZ>LA<95_MPWw*IO_bJj3svJOopaFP$#XMRpgJYFReW zz+p8MUg!HA_V`DZ$5)B8$%-S;_E!f(-|3cTE@mIO@}eUeUXvJ$SsK^3?0uMhdVMr? zw%-Ja4sS$Aw+6&a@qP}*%q-_~S2n{LrPll(Fh_aURQ&zav?Y^R)$8D6sB{Ewkh; zGzwG-5VZCJrv>E4R1+1`eDIVj9;smn*6Z0nrPSWj3Njcg%GaIEGA}2G%kExZpYy*- zA-{emwx2VdLGAr}e>V44E2@-0qbe+NZuZ^Z*PI_SBA*;ZLtH(iEm7zAb1<@JYd0G)^vzYd zC6`e9)p!le3x&0t4QJHEWjKm_C^Oo)cX6_l(r@hF*=+GGVABuJ$u@&io2Jmvqv7Zh z+1Ti^x;NlJ53iKQRDSP3rOo+fg0!QvG-8*WvLUB$Cxk1}akQm-gow?BwI)NUUH&&A zu($BtwqXp2fw(yius3C3Z&t$!@4|B=HO7Io1IpecQw2KUf9=f?Wp8-z8jica9O-ZK z7xY*@yMv9Sc;;z8$w`%3k!rB1V)2>RW)2S3j-mDFoX%vM8gr`Jp=?kYEE52AJ5EHY zK2?6QiV~80BW7ErgnyC&_o(RjdCg^`-^I`$D7AW(Q5V-hyP#3&#j@q`MZ~RXV%j|> zaQW6im#TH6pf5-w3Z zRz_l{(7v;pqPftjMZ{k=`=H^=R+92&H-na~)@MikU(@P0zy%F!mtm8_2fBOaap>n! zNKvYU5@p}6L}>7kX7*CWNM*!P?MKrHtI~3>O0xqW5EVESF@z47H8mRO)_A=fMotaj z&5p*_2|yoLdiT0ce0E4Yy2st`6aCgQPW{va;~_yxop0FDTYIy$@%?1z)IwzUh+X^B zz<_`(d!vRnLP~fE!dkgQdZ*gyM0LW^@L{fM-pZ)k8+x4a;&sF zASaNrC(tB;4+M3`rmmg2%P`*pdNMaawgD2++x-99xWF zfa0}I8{LIJuHlEjzJ)FW{@JSm8hjVNryVmv{8$G(rmiWXfF~I5BkM;{Wqr#C{zH=L zT2V+&Fs+WPj5rRo5{DQk1qv;dG0~{bBt*M;-g9}|dvTz5$5NhNNSs!9RT@ciV z)n0%>V0=itAbVGYQsEc@yUdLl&&y3%#xg`WL9`0fL8|^=q`+s@H|G0(!_}u^a-2WVCpp5+A1E&ADh5xvP|G0(!xP||?h5z4^2&O=m zLaU_2NG-dy%p{Of-Fo>9)5B@*(V4e*ZeB&hj$qyPm#N>>G2fI?iv>zJERWF>h)#VB z$K?dGCm%ju?TDa{w?*G1FH7KNn#C2`1|63l?uYF5@$ait;b}p;Y3QzcJXa-JaX=k= zzGxw?GX;?@WqeQ29VvXd_l=m69=7D^SWxV`7-f79M){CF2$iLa`fOx>vOXRj;0O23 zu!9@Ef)xKLcHu{fzd_~OxU&T|VCW4`AALjpk1SNh^3_C@0!Z&=d!>t*zusiD5Ek0$QWdv$&`vxEgP% zK6y-#E2M#GpdMj2(coDj6vB;%4j%RKE6`jwAFni_?eBK42cueh zV=yX}mpRn3`9AVS_k!}v{6yWh>eT;8w~8F(u;8ahztW;FpM;}mh|iC=(MT-)r# zGf0{Lh^EX9rsA__waW7T5U3iY2uk%2F*n94`jg4f$GyQ&?4gC`ufJvpxGnAXaHVqS ze+l=Issg>zmSnOJ@T^qvW`x4G>FMc#k2W?opMY9?G)u3mssWOBRA_uI(_4nMYx&U}RF)H1c*koM=B=juYt-0)nLzX|^=!*5<>D6ua6Ih=( zx+k!LB+MFVmHvlEpxg0C7;P-K`|W=pKX`i4Bd|s-GpmTZH^;HvD9@9FDh=z&3DBWH z-<ZxoV8Wkpl9hhY!~HADT#|Mt9m6y$m1kv*nxIVA)I2o6Rt zpl~o)(=7XeX@3GnoTX014N8hklf*hC!f0_(F(Ln2zyDfER=~6i4y3@YNId=5d}Dx| zUUERN`wXnC75Y}HV)ehd0H_irpDAN7o@_NkIinXkEd>=e!`~sjND>bSXf{-P$72y) z9A)~?OXRgsv$!q%KRx>Yeq1^iQQG-c+$YB?ESuRSXMtmD$JWIjW{sz)6=29g$D`tN zOo>P=RP>o}7x09M6~S_aRsT-NYrLPCH|duj&EHXLM&kPMf*?I`T032DFt?-Rl z6k>&{{!Skm!1}%xqpbKhsKOJoWT+7V&c)CVU5f*Z>6RupjWg_Qf6l0sM?^HP?u8z% zcwEtf4CcltLw<$^(Rki;tVVe@%aS`e41rvG^cHC33Y%aq-j|iYp^x83cU!rFW~*KE zEx~pN1YOrLSMwW{U}yl4tEu{*j0yPg=*jR1)W&&T8##~bWnkYdXx-6gxIbO8`)VY^ zvMB+vNuZ;krutV z85up-T79n5W;0P5VUny7O9c*vX_#`~y++kH)Si|yC=);-jW4xO=Kq}ySdQwmY+3-V z7RFR{ui(}O`cl{}C$?Nk1YEACGB%v7j~K?TPcw{~j1&Fc*Nop7e}4iF#akYS`k!w_YM!SIsfwx#dRpJ6MD3Cw2K2ny z8@P74k-j}|sQqwmbkwriYuKC$enUawM|Mto&!Dv+9DGNX+2|7+;@cRTyK5(|qFV@)=VnrKUO=tC%>gG~eAU)2WFy@L3RO zenuB5jatZOtdGr(so(K9$im+|!9GkLEz|poIx*0E{Bu%D$c~w z{GEoMB&T!iPCb2w4SD3lNE83MwZFB52v8&r10xAMEUJZiXMS&K%5w!9 zp57X4ncz|NK0x3R27%x_bI>m;l}0`L zcywms@BW~VK(A=zmF07oxUY%Hk;a?ihJ6%gW9$Jco~TO3i;LLI{YyJ0Gup#fjvb08 zi?OlZfgVp$^p1(E2yL4Gh1dLp-6Xo;NOId-1U!N#!#N|_XqX%_VGzah z)4Vu~yb{Q^MF4~ixs`X5YWrm6IaE$5LX+e^{7>7mYi826$L$poE!=ex8vzv%R~CCS zYs?()2J-=_Rof2UBZSpEX54Y^d@2*s0etFO`8yeo2NFnX&6Z7D3T)Vzj;|jx5(?p6 z9u$cAqO+`OcWBNi@X#+F>*I(?T06%XVRbucvvYI&Hy6Vu1?Lv!Rw*bOyG?h(8jZ_-I*SWknMS5 z0S1GL5b_viGKVY)faQ3`(i=}_GV%)roW&}1^;PvskMK{&E*{#UI*x4zirJ?ASgshu(I(g{lKDKCAX?*(-{9`ASFX+MybKea~8HO7QWB>NF(!wnS0Fb8s zUP29*c{>kUNzVjmU_@7JtCV1s1_rK~*dss+3jo2ms+;5`W;d_AT}4C`kM!3B;{dRs_HI9miFFxmS-zcMt^GH6*9OJ93$KrkJlfj3BdWER72ldPU1AOEXq9|# z4=V}1oiRNXfDMhTd0T&6$(|HtUxm-k(tzOF3_s|`0!P0B!QHD_V?wtJGtmjzXhab< z7A=xSp|&3#SRnUoVZC2YlI%9W7yS}SINJrK3F6ehGQEeSeksh{z%HK>Y@kC1*B!`t zi%bJ2xL=m~=**tYIvwlOZ}0I&j)|T9Szg&>t6~|_;4x%LcO|AnFN7NZq?RFO$w1pm zbX=GSc=nv=k~lWL=}aFCiMQ`k3dl^|Yo}U>AJL29vx@tEgCb5F_`fz3%-9KFgC~JT zSd|+Gh+sPHjuGuEaJyAiB@+^=k7uw7 z_JJ&FZ+V^Tq=WgwHjj;6c^sdM2>4f#EhyLP5&9AB8h=|sGynp2S6<2h$DkGR3E)p= z#-Pwx){l{Fk(;4ul5%o+mD@hbxtdSUq1NdyE)U7@2PPu!LclPJar$Ic=V!a~$~Z$$ z&0zp5qf(~f(~1-1&k$Ad<9So8{3J{37edq9<6H@Km}gQ*0wQMu)@Iw)i9kmzK8?Kc zG%9$#Tjn*PJH*7N*729Rw={jftL{l7I~#Oz)lDxiP5hrR~tW#*w3P)g?;DQ)bipe zrcy&4iq--M?}IT8Nc+ z%rf;>^U#!^EEiXAs?peBZ<+JuI2@2{G}K)LDHPmYP5xlAQ`U8<>qkg*MNdhn0t9*V zPZccTRi*D}|GJq4v;M{_rP)^1u98n(iO`C&2tFOWjAPTP7#--VTls81_x)js4rORV zHFO@qzbHV%CH~2uw5JKmV5xWm06@ZJdmR_24P~zfE8A_ffjc`6=Jrnr$zMv_eEOdj zRlemPTiO~G_^oYC(49mm>E=N8$m_!E&u%e3x_%a$S2l5ObavRZtxdT9qNZsN_P;q8 z`LSC-sbd)G!NgA~tk+5w4l99MOGYJ%9c=Z^>~q-3E?l}0*!(Oe0$3qBzpGny89OLsRGu~zn&#HI_k(bP`lHo&?Uco z&$aKHzXMShlxm7d6RwQbXmRCe>0O_+E3}^*nj&i(>PvfDt6ibUz>kUROSK4Ze_HJ# zyM|$S0=7?Z_CI8zA~oct27RWkp0j8Jd)0hZmAu+n541m5=`_ST*>IlXVog_F-GmpD zmCrzqK&DY^=ii^NCIK1pTiy(^$HuL^xY&zK;++3ILO)>6nBCxi&To~@pqRafBs^){33NVG z+s#RT7OGJxb7F^C8gSKsP_lD~q=;vne&|Sk3)&kfWD~;m*buTu?FX-IDeieJYV;_VQwM2zNpAZ1U~L^>KyB!OYPkRD6)P%S3k^{>$!~2MT9#y~KzZiplTh{> zK*C3368}ciTzT|*Pbiv3CL(udr|IVX3`R1oz^)OjYXX)b2n4yN;U-87(hc@*5A5O? z0P~@5168ipyfbO4ZK;!PFl5{UmUnsZd4uG5; z032`Y^CsM9MY3}>uV1S9>eUC^(>f(VRswX`;=*!`)B>u7MVEtd475KGB?4* z2`vB>H~X_$7Z9F)Dd~}@>N|HF^nh53v+()S*7u`9QqxrN5(~SA3ZE3s0T!vuk5Us*UXzeI zM8AnUZfQG@_1GdbmGyj6U0)gr2C`McKajG7l@}45u^BB?DP14dSiI*%o~g8aB~zhB zQ?}F5f|`!eLE!$M91HA?AwSpHF|0r|YUI>tFAhxy?~ z>nNJx_vw?v(r{@BV2$X_9q$zgfK3~>mHo!_=nTxyZEK;kbaMRAXp<{iXxXf-%=$-P zhnrHiNzt7*q#;u`Ss-mRgw6(XlL|ltQvzYeW%C@(A~~<412RCIBZ_V8-#9GEzU$l; zzilh#O(4|-duahc=aryFtO;sEJ8+ZXEC8KZgc-0^>yc8OZ(cmQPAlUkb>yl=;*R8x z67vChAu!OhTKrez^N+{6?VAI}cCT9CFxtgKL=Sxi+})Jla?e&_@||H1Z6VYq7@Yu2LwnD)I+ zA()+?Zv$X(Hjtqs#iPQmw^GGE)4-Rsw|v}15&i4i>~Dq%2rj^#%WMJ>&LG@*BxTx} za7$IZK?Pi~SrAL9I<0!8ZH3q#0%xg!aA2BvCkW{>Ctl7YZ%5Rdnn9J0`POl~It^Oi zmG3)H2m}$kLn9r5G?D5IhfbyaXA%B|a~^jqN9NPQ{QNuArKl7wSidVh+YMIGTL(u2 zQwD&2vVGu|79mERj^vJ~5xg>c!&GlI&5f5oOG&`o-Ix_{iq&BS&hibuE@rc*`PRQB zCezv?HQfb{WA_hC8rFBubMLNnT|)^}ZlvYuJBgJUrOGf`W2Uu-5#Z?RhS`+zz&~?c zfS-N}_T{=N>>NN(jGf<}g;dxeWaD^LuCwdt-p-Mif8pj<$z#(id9y#C5p_A29)%pd z>Nw!rTXr_-9G~}d7D-zgnX6P8Of`OA-Q{5poE}GW$yqAG`n#CxBjfuO_Pg87lWicI z36k}k7l#13d?3tub<$|6*KQ`QYfy!rPYYA=v~+#k+?T|TVTgd~iI5$8d5joF$xX_4 zYfdSgW3D?E$V{vIpOLit8PsPH(|Mr-RA4ipYsK3N*7l-W({#V*>(*oy3w}=RFPeP! z(Yd3mE1WcYuzmewWP7bAdWKTGx3^t#Rr9Sn!q}SmZGXVD)_BVmfM-zbtL&1g zjztb-mB7K;BGe-l{p+aQkJTMB5cy9A1Ko_CZ}yTFIyAG4)Csdr_7SVV*q0ZobLW)M zK{2+0N*ibiPqz-Kx~;9P9GI6?WbT-A6Hl3&5)VE79Wy{9i8}qt1jS;rEg>fB0 z96k$0s${qF`y4itUXtg;Vb`k=7dk%5K5NtdR2y6mW~(ZQeD+q8*AhbX(y17DQBT{A zEGpDFWI1Q8Q!3S1x~2;MdgsaR$K7VDAK-!*7+f`oFkABLiAq$3zP$*eB?I2Vy`p*j_3jaT+8YNu;&_tv2GQFS&Sw_hB;2nz-i zn5D1epAs#zlwRC>`b4Ln#M6WUne0t~ENrp1 zdOt|AgChA@#Knehe(fSD>5*Uw6(;WL!VLBD-Zl$s|LzYL=M!Cu{o^eL+Q;`gjg&JucAl0UqxR;D0u@WvWMw~sgSWv`bJ zoxcg4`L9oFQYRMmy^%DG1tKC<0C}x8$$~J3|A1sTHrl*qdMo_+TU@MG10g=;DAkgS zI_g6Ix@mU=HTZP9nEy^elfn$kiL9^N!BbkRH2XVbsZ;z!) zew984v>(U82tgzu874~HSVmDh#lr9B9F$N@v6)jn4sOriqqAWV2nR#Hr5o#q1>StZ zCo7YO%(;5Lu-jV~T=v!`=CUkpf&c+iKChIi_!N|1sLkE>4FXy^3xyPfjdryUyyjs6 zf$U4K0J>|{6nfdNxJ?+24loQ^kUdgREZPtewalZ*C-&gN6|_$@{()Rd@eUXP%IeaU+7 zj|5fJm=pp%)1D#bhVPF~?e!hTF-erZn0Jw74^+#oTR%qcgs8H|Jj|2OX)s&!_T366 z{p4V&bo^ng1v%7-m~VF;G*T_QkJ_FrQ@gC7HfdKm5(qa{4}|B$jF0vYe0FmZ6bHb^ zXNLCjBTS5mU`y1({&2{pms>7Ec0_aB6NfsB$Qjp?gxr>F_8FJztif&0K{r)VJ@A$L z{F81D`%e-gD|Ix{gA%O@lw%sSfyhD9nD$CXW062s!RG(!2Cl7LdE&Ig($Y7$k zzKi__ndXGoPq~@`0hfAayM2R;6o|CruOCrjHEy=;xz7%E*0r_>bAdlV`ewS;op9*M zMbE49?=o(m-zm{$vYZ5xcGKN2@g`x6*Vp43Tr#Z-HmGSk^69W?+FN%2O!4FM4jGaj z@yZ!oM+UFiT;E!~Ec{R~oOmz)MeagT&ja?nj-^o>icTFGrP|n^nt~R`M33ffv)=eV zycvbwU5~V>jb#QkN+cv4`Gyp3kh$_&_kRcb>rYh%$7IN~QtumAA2vm?WKIiHMz);6 zr_nF3f88R?sNRq}AHX_r^@zJ50qmZB&8AfV>}QKnfH%S5R39RzE`938)*g@6u_vNT zzV>AIe2ZoNY+X9(EW|dMK8ri=>G()j#U^D`wCkHc1tJ-lOYl_g$RcaT$Z?+n-%dTS zC9PuLrStHZWO-(w)xLLgUji@C9ks|3FHmgzB9A^wd2~Df`BNl5PG(EC&*5J9owow; zYK#1ZDYCJinTb*2Oo`kDP>{Dr9Rxh&MBk?yTrF09u#|DhYBjqmW9(kDCn0y)mm7@c z0O5v+BQ~1A$-XQuf3f}`C(vJ3T_;9oH9?>YDhJl!Z>_(tXpha zRuHOS?0yGln?{$dvBCh+4>hwohr{3x%6};V~jfT{r&ekJ*AXf^U1b_MhhHd zNM<7)y3#yP%TN1*u!Nkl!5%b1*1_bM32_c}DSZ1}J#)1_f~>q_9aKm zz!N^y60IW`DX1)8!at#0ci(E#V&b83wI^)&N!i)u^n`(YFGY?3_onN$$~lKS+Mi<& z$%V%Wm;mA__nKUlS+W+|K)3jMrD`23a^@wBXIJIu)kzH=Rp<^LS2XA*BcH`RnSm?* z=>%i~5_UZmWoXQOC9ym`aXq;^)+_YW6P?+H;T zv0QY3SqS>_$}Rb>y(v4GAXF~7uzJYlcgQXJ}ECI*)3vmo!U_s>EHh3 zzN2nBczN{fBp4|H*MNM3N!U`^{%F9;*+oM+9h;!?)+$BM|M?A zxbL;zXMLGI2#Y}3N5$JuFDQ75>Ev!wR(&0EGZyae2{{yWNpqaLgW6{do_d=_Rpup3 z_K1o*H6d&87-<={wVz&$5ajdB(!gr2unl)k_H)u&Pj9zv*$L~!@-d?d%|JppY`}Aw zJ9n(IVslUDm_1iDS6Xa&1O%$xDd%h|lzl|&jH^oEp;p1fAv%0<-Hgv;d?ivqSnoVl zAj2U*PIAh1h|8H8jcaPvfqd0oCn|^Q)XpB`Zq>*^H>Yf>0ZxhK7Pl2MVeta0*sK=Y zxptFb%csXmdTN320rP_L|d_Q0<*JFf# z#2jgY?93O34uuo$7R)`_ws-)L6>kLBK^h(69Yn62az(r`M#E4IsCDb;*hk_?@3KN$?=DiZR^qX$9>3q zHpjPjT^{G2LwC(T$%|;(ZiqrmMwcZR=?Oc@snj?no-n@tZqcc}vgnRwifAFC`M>mI z0ak(?r{ElEv21tZg$tk$gH{MzSNsHq>S?KOj8PBPH5Zz9TsI#AJg$cr-?H6~iGO}P zjtrT+&7oCM-jxyylqS?+2blk1HA16Pb`8|zL4`#!4^CIK{ZNX|jp8GgBu@iyl8=|1 z5aRu4zF>J6&3~g;A()9RS`!O|e8 z{&!jh`~9!bmqdY1pN7ubCPmWx(5BB#pe_bJ)rClXv1ooQsp!E8{-Tb37AovW%>CQH zLVx++)ZW4KrvRz5VMB;55MZ0<{vGT?bFnR2L))Qcx<{`;HTpjj?Nj?VL-7UfJq4hE7zttrsLZ*k#Z7fyg(c<8?` zoTi0<<*4*|<|p*8Rtw{a-lg1XA2WLDd!3+Env}J*UI`X4|Lk%L zI0=n48nEOKkkxU3^R|Di?%!(9zy>sdxSksfJY0<7wM=o}Tl-xC6N#+OIKyqjCW`ZsI8Ak?RygJT^_p0hi z`buRU8IF?A(7TsknBr%=`U?8FOxm`>6*=f1Yu>k{1H}Noyckk=2+}tBg%8D;{D{tx6?RTZ4n@ zp?a2Dc2*s}={uIh!$Q}N`>yUQ5`}PhgeR}B=G*=tgU9D&e7D|^s-OBT{M$;PTa^n% z5*wQ+X6QwMHHPT~un~V-Kt1G7hQ`4XSX6Uw2g<>}!5PJx0fr*R;hfqRJ^(YtlI+1C z<7Z>e>m#h|wU|r*A=!Bhz}=rI@G0HiELSC)5Bd?rcy@MnCWkqQxhnFa$7o~Y6q!1q znv~UG_TWEm{d69+m$woa|M*|oePE`z8i4i1RDvV%aKU6lTt)Dsm|bO^&6L-HT|3Xg zKqcOj2G!rS5U7vD?t|fA(5p(pmZ3h=R+#<5+uDeP1P;(65zY}mO7HvI@K2{n9pR?m z$q1sMc?%%DwvhrK+b8T837+jH-VCbRpx1ELL0CXQYra#DDKz~=e$juU z(9XDH9%{g1OlLB^2vz3pVFL?|ejbpfy}Od82`;HL$uBfk@pQo-Y)Hqrp2iEv@xWI> z8*N>J!aZz@GcEr_58!@HSk*c}?m|Qw6cKUfE%l&Cw=jBV_F8ieEwFC3bElTm2|9AY zOabZaPl;mYVjk3xLDjQ@KhTxpK6NemkecOtLi@% zp?w*Z`1gI;s;mP!gzHI3a7bh%8Ovvja2C6PjU>f9ZSgGvBU9Rpdb9whO48>4osoeNq?)eepqW$~s-SUY z`25^m0s8Eh5!OS)UmpBe@_vHt;!{|yc_&x2c@ z!y|z|uZQLmbND>G0lEjzvIRQNeITIn;if#5`6aNJ2lZSp)W9Gy-Z$9p>}x-a;WF4W>)ty=?=S@e1`ZoG!(_6Ak2t&i2|)t)$29u-cP$zN5;QjQ?(;eQN(8NI9T zy`tIK`Swp5_Qs<-8npgU?RqLbvY5Y+K@4k8Ah7*iBK0P?fMW*^27$0^309(=Y>aJQ zeN6))bUo+)WpC`aw~SFSj$4~=q@aKc8X>~zweClrN#BvvRp@*{IJ>aW;pZPa*Y$&X z6a$auqC;AcsS(|xBV|<;!GcZTNPgeYaY62xJgun6rPJ2MQ`r-NnYy4dZwz$xWSIl( zF6z^VtCEoFwyoC^u)P>7)nQS~Vk|U(QA#ObGJq@z2@a<9zr`EGq8fPG z>IRTU1ti&JMC%HW?2j3Btp4#qijdg`wc-7R1Km9^T{rkYNw$4rVf<7dfYZZ3&PcC^ zfCXwt{I92c{tZ2Wp?3d1Vu&0nte^URk!;|dbE`GagV`)tQv@QQ0M_&JRdXJ86Tt~xi)MSK7N7~D;8MkBA=F;`w8{#g%FL!y*`G+j%%BK4Fq|{E zN4J4rhfRD|{qv50K^vxX_8-E}r>i&a8NR7L+Uoe${}?n;O+!e*SHcqZqSwxNd-Zw`!aI)yt z0odfub#Q=b+x-bR#t4lB#>qbOxAf~_;P*%Uxf`0$xsubo{YCtIC@+3xw1m~+1(<;qk`PQaq)q{Jx{ai@eVns`zv3wYWV{JY54lBs)l;EE zGO|#cvlv;%zm`k`ZB>DEbQv{xDL1jF&?B-SHejIJesUCVw9JN6zFx~uG&H}-CeE=_?EiYlaY=s}&d}g~0(PLUEn~<5 z=ACsauYduO=fJz%O}r3xs^eJSQ3QZ%ROY8KFhtUpJqhwcb_Bs<&`AZxA^orUZQwni1fR_D zd})G$zzW{%hq&DZQ+c@ahVwyt7WsXLFUQ{=Xl>yt zOB!rPV2sw~re9=XgahD2-)^8E?g|oUqs{$fJ^H*fV|G7WQ8I^HI$rKl9~tyK$Jq1;+HG9 zM8FvDHknz18zV}wa$N*+O(f}Tnr{h21g*o5G)^??+Jr3yG1RC4}p+Y+pZ~Uk6u9Uo!XdG|II7iRbnx?R@Zc}_5e8S^L0J)#PwWFqFqHltIcUFQ1AT1z{I;zZ0w{$!Lesl<1b1ms(w*^I z*4AD%%4~YyYC$jtM}NglyL4{XVJg1n2h&SjGVk4B0qt>yZ+?e}dU0TE_kZ1r10BDj zBH-$2u-{RT0C+%lMgw^?XIz{~$Xic7BvyWDv#QRIQvfZvHj700OOH@?^aU1UhNk6H zO&JDrU>k(olR<`Xmqii|8TgqZ;I^U|+8*Gi02``jfBlLNd{AB%0GhG11XKe=*nofe zl8?kCT`B6ZF!d(TxUl)~R5H8J%lAw!b^`S#=siumpgwq0DXvRE<;%}s*+>COxEB!) zcF?k={p!1?;J3Rf@rmA^yBb}x^0ch9y4V;mQZEGkX5?0UbfUL{=;7&W0*BMUCiM-% zS$m)}4lmN%}Y`dg=Ht^+SHtmX)F8T-<&rt;6ytl95j%{ zu$hemcyDpMr9jBo^r5+SMYqY%gB6dT#diUk;O*WaE&tPFRZ&s#HU~#{O|$VFeiP&q z(H}ru7DDHkg4{a%O8#ls)<;0US@G`%zssOG#v+PN;EVMmZqLvJ$xZ+I$Qdy^Ym9NW z@vIKzS#lEuXH=)Tz|%Nb)nn$qS;!mgrRfy@S`ao_j}R=hY?K=*vW=wR4`MqYScvQ$ zXqQxMCRWVR4hgF(4^gfzR*b-tnB!d$@8m2g$d&i zz$>&%{~x~`*vhiM_4S*k{LlJ;W3HK8W-j>GxGx`_3~D~6JVgvDW~znJIo~h-rA5lu@ykxFU+$kh zzEzp^?CcU(ajf(GC{sqDxvZ>^#EW906U9`;s!xxFzw!i1uu{Ya;w@gX_YPJM??T69 zG{7+#k%!Rk663o(hIV)Ph79PbklXk8{`M+awwq5(@6WceRP}=tA|u(Go8g@vaJMF- zbSE}lTB*t!hy1WiU5-X|R#VPU+dcnHxL%2;Tf4pAg7IIQ@oB+PoC<{toj;`mRJK*y zhutL(hIBKXNL}}5PV&ZOR6jzB66BZ7sTyxDWKthNxm11Gzy57)vJvCKP~MlN(K{$%Shc@RUmr1OA%5N z>)i1k4#Ty|9=#a#=yszei5?ZFu;<-IjHjcRSOc-IPk`2(qNwj(>*w*$z+Lr>`52p&oF7ypNfu5+GJ>h5$ubTBhH!gt0;N?>F`^tGHT#c1 zzbi@%Oio=7K(*_FU~M%eMswhkS3}@+Tyj6Jv_R33LW$tI_bZ6i@!Nl~N^U;ele++S$Nhw zYKoVqn2oJ^QsY8rkJ}2}PNbtB*@^k_-*kFGM|s}q!XgaJlbmAFIvKsq>GX*DQN>PW z$85q6&rU?^ud5eWfIr*6zI_oUPkXaHxC>kx$i?S&dPMySbT;FMe5vUr(6CU>#R7Ri z>==y0uEp0P>fjx|8h*Zt69An4^ggk#wmus!>7BqB8sS<2_WHzuvsS6*fw8x>v`b|_ zI%^6X>u=8Dac$R_*D77fv*;74BwCF~3Y<(78$HucfegNcR5~`#f34&vrRYn1Vgt^M zV)mldCa3q>g}!Fq4fMj7Xp6ZI)WDY4NEx_WCg{Wngaifw=K1oC_a>BT`*GQ0!ND8o z;0p!AD9x4EAVT4PU-W);7HG|bFH8m;%;2&D`l?Hj7vVYEAGN1)D~esqb)v*Dyh?%i zl9Q2acppgdM|&i}4HPafht7}>y6M+vUKSKbMQHMAuOR{xz4JnR2ZmW~Hr!39e_jK3 zDL#-MELITpwMp*^^N%e%V3Y*mPP43B7u+LY)Rpq-X2dJNx<9fBJ`$7c5C78(u-ifa zM^o_IwU%7W59zSfp%}cugC!n;|Fwjae&(jX!UdKmkAK&fzJjoSC4kuK$~zGNC~==V zh}OS`bVxnyZsaTq)S+_|*XQ=mJ%7aQSbmLF?U8KKMhy8ymJE$axI^5Lkkxzm*Vx12 zi1`}pT{y@yt2GzR=fe5+YYGEjT1Y^$vK*xl@cM=EA<#M|fds0#p23oF*L~6uw5!gY zf6T%Ys3VPwqbP@sSL*opWZ!G-N^DBPj$BPPQ=B2sI`}?hi5@P0_9K&NyyTxM zo-A{l!(BVg$dmVo`(UmQ;bCQ6neWe2R~8=k&{jIaqe{fobH`_8q*&lFrjRT=@{W#8 z5UbVzq04Gh9ph|`#AMh-8O)}qo@e}tB3c$cP_e{PvAUFZVslBIFV^`dD`jhtN8 z)wUa`QC;@>t^Q&ITF1INYamJv`SYu_~aQ`imgVJ+r#he^(b2; z!lsLzK7Y;8yWZM62R{&)@25I`Il+rc-DTk2xhQUlg&yE+Ds)#zW}E^gV78SjqOqkS z8WJclQQ@mYc{ZK8-%}dsdX>*a*1sHdFS9UZNg#JGxIC`3UOwz~RCgvJeF_xIgKJt) zgU7rUu5)peH$={7Z3k{;B5yELcyVh}X&u+IPxwZdrdHKSd~xVi99Y_eLSc>C8%G6f zXmfM*+HlVNtoWO@`LjcEqWDhnf}Y}3ITL*t#oDh2bMN!dx5avjqrYC$&{?`TASB^H z6`@j=n14Cuq?$AF>WgP*YboiH*E~d>a)mMHtdTU$thd|xloAMU^u1v8Ccq`7tITwC zI@}w!jow)087bKIc)$cRvKe4;7z^5gA!7*DE;xDNp`^tk8#I`zv#5wcaKeKm8$H&{&$0Sg#hZgybT z%2-MH@X>(AO~=Z?DI4^t6<5~sMq>Ikvw#0NECNfeUDfi4T^|e;+jk@bP zoF_13amKP`o%6P}>uwrj<67*fe01RD+jFKUg0%Lc*Ct89&QS0DmS;T)fB zr8+*#igwv!zY_8&(PuRW9&IHhsq?&%lFc&Nl1U-Kbw5mbG7{xzqxig>>Te?A9;i#} z$d6(KS-lC)JFP^f#|lL>q^0dj`hn2E!BBEALt@u9$zJ7|F35&Jy{%hi4|EK(Tz`&4 zA8NPrlaHYI6&SCl)P&hQX1}KR*-(n#Yo~l=-z>KCz$4)LVd6sADsLE{8WvZTy z8}Ia!UKP(w^{1DFA zXte;rXCQw&K-xaBOuobh^@=_#_fJ0RiRW`xZ}#WO_yWNGf%Og_IaPS# zyjnU@Q*-mdLKz*n6=pBbZT!ix)-CDM7b9FNo*jCz#y|PCXFD_ddUt~_g{ad6=KB-e z%aBk@smWXWu5RsKY3X;bJAt2730wjOVU+2TOJ^(JJzSz&C1tq=uIP2H8|Hpq{sixG z8tBxle{eRPC^UtDfh&h01RFOdgjW+5Z#$0h;9HobD&0ooFMo5=Pu|&k;eV6Y7zseh z52k_OWK6p|!h@kDfO-!W$!9}Vk=>UY0z-#)2)$MsMYQS3#nFv9t!#0M;gOD(SaZyE zBwVf@DHHph_tQ85m{MN0`#TQ>sh11H1P`&adr?4hjqauD)8gNN6!~4xU^hU!@~udo zSAmCh$;&@*p}Zikx*}os!of#wk5OT(pS|ZvrPbK!$p-VU7MCKQog9ztwo$Eyc?-sN z3oi>=&6uTKiZq-qlCAKjzT$FwBWdA~gtI(#ss;xyVxJa}yx`{2OmCF?QTq_q_S#)zQILdyTVm z#L7s|+pHBWgOzR%MYR3?1ngVK&sln}H`<$2}BG_uF%2>tt=aC1YPBlx;+usXWTUGPGHq zBX&f6zns6+d$&IpX?mX26wak|=Mkm?jt3>1W^fl#_x{J#d2_5SKg%AGd}l185w&=X zih6f7{w>~)yG8F6W{tUO+b{CArjOe_ zpjAG(%>7GO&B@Fmefq9qe4ZCLbJ#tQQyaH{bNri;p+|)B>3O$FSy`N&t<8EqM-6bk zJZh36=UzLj;3!=~Y&RDoBv>i!;USmu+XMcekS8D+rL{_RvKgIo=XT%z;9vN&$B`Ds zCpiXgE}RDtMLSiHD{&6sc>T?3M!YCW<$c4A7A~8k7noh^#Yt_=y#b8h47Kplopqrj zKR5-c%MBqjW%WAda})b-R>n$)rgpm*+*?W6mk;VLT_P2>Dl>9lo4e@52zn0U0!QL2qSjZCh-`4!-_9Z8$lP| z-MfN-v~_X7jzRhjHHiG`T_I(;_;=#lA?mxMBH%R}^+KmaV~|6G9^R$%6TP<+={8=k z6#7WNRpmE8!fWtI=$0xKap9SkN!hkd)`qhKfIX+dorz291~iyAs?L z6+EFuA$b<%%)jRauf@6^mNRqB4ZJ_`BIB)AcdOf!SD*Q`iu#b#o1>I}|9G?m<&1#$ zajP@w59eG}Omu0GZGq_pM(mY^-*PYQ{LYBgo2BS=M5y6hD3?xXxLoiGeGU%)7r^?v zf3TBwH}1-Zvqka~M(mRX0eY_@MI3aD&RV!%Z_W`q_`!htcAZz@KN(al-$U^M~EQ~(1FTpLrIL>GPMZV$d2spNX9qNJ8 z#;REpJr6tSazGg#@_oDIT&p{ZS~o`e8#sY$hbujw)GQ^qp!ypyfDKVj;K<6)zjA6e61Sl|SzY*7Dj4IeJx?gj&}V=#1~I#bDqLh2T% zJSEGlj*sI9;@OQ~MJ0eQ>pXx_2y$>v-oHIvaODprlbB2f`qi)DI^Oi2T(0r5Y40Vk zJFF_{rPBMc0w;%Y;@@9mJUbcsG;|)VS6QoNZ@#^8&;oN|&d^UK5yb=ZCE-&@EBMY7 zd~Sx5u`Q{w?Ih2Y*uEoo)QDO-k}b*k!DuYgOXQAu^!^0V$QRdacz+zfon)WoEel3n z?dQK_iF_fp4rIl_V9D>Ebdh_4tT*{m4k%Iq?)wecyEFet-}>g<8(=&cV8keKqB8tM z`w*8G7r!Nb1!P31-VXy2GIIy5&>Yd;lKuH+PLn~21%#H~dW2uE>xPbj|F+oM zhBJY~^2f@U{tmz!L7M|c^eyvgzww7m6Mcyy%vtD=OaBed!M0RS)x-CPJ3^73IE-=$ zVSH0)+5SwjOtrjym!;8y+-t0w!+<8{{tYYkfym4E#33GZv;Z10RS(r=t5p?tXP`SL z0+{g#Me9M00q?O#sS{w5crojJ5v}OJfNj1VKu~r~CY#K~&7F9r!dJuwxiBSB2PY2E zJil1s+k|-;cP`gr)FUQ(ELy^a)tBLCRu8@Q%av=ZvoP*IF0q8=Yba118;Yb^UM@|0 zNp-BBs&tqr)-1y+RR=?~7oM|z5p+_=$7|6al&N%qMO~w#Z)V5rTg`k^c*UKujDh3MFtxS__FI0xbxkSu2} zpZ+kV#_#x%BEdD*X-ou2Lj61Lx4!Fc#39nvJ#tH14&VU_+Bfx4wIf?lcQ?OEUy+vl zuk+lD(KVT{9L@-nW9C~m89dJB$4x#O7ST`9SDOJO2ApsZdTqJwoIsfVCu4Nt@ z#?l|-&hkXDMw8UsamG>dlxKSN>MYpg2Z{JsIbJ7F$?@mOdRPszG>=o~t>b0>?xoQ5*>C~9ur5}< z!!cZe%&*AAubDH9b3a+#&ga-~T;`PisrXbZPp~)9J6x{8;$Z9Z&}~{9Yn>8@$3_4F z=7zYfH0BASXMK9^;L67F!9I^WS+X*#zL6k&QbzZ>D6>WAlji~nzbi5f;|mHQ7PP*W zshZ)^P8|NfdpSg=F%SmaQ~m#@@w;vy!W4=MR??yr)6YSC7SGDC`qs71TfHYB>(CH{ zr+VC9e9T{r))%RIJ#{fW>J`ea?{Qu48$T6?X(+6Zr} z8FHA9&z)<#ifA0-IL;(Ny04(^+=jU&tNWwWv5AsRH!h27Mp>MHclX2V@hoYq?zhUsb1pa3iqgkVx)^*f8#UzJr@`_euNU=u`{?H*R!o$xHmT_JgWvBMEJu#*j5b>2< zt3f*{yvF1B6W^iP6UV&o9PmJb9iEKEfUib?-gI6!d4zicev4Cv^CxV%31(~z*dy9G z5nTkDiP9z6?+&lQe-t*SylkTj;ilCMSb;@6cE8lNr6FU|lQuzq=<7x@l`HByam%?q zNcAk&%69KEN9aR#q%^1+pv5kr%T`16(%HfeNapv)CJ#I=S9~@RoEk!Qp%-i@Z=*|B zFW!*x?@QF7XI9Dm)U@y+auxF;6d`=5uzgIQsM94L*;->n-T5T|RH=nL56^1sca6WQ z;LLFj?ypKsy2hfR9>72y>Laixld7qb;!sjpTO*`c1`|P(Ed&NP;kXta`P8#8@)o## z@^gz5A6<^=9%^Iox0DjA9VbQv$w;d*htJ)Np9H+218Z1F$mbgJS_vUaVJXF8zHbBJ zL9CHH7B5q^z0A87Is8Vr@NZM|v#fg@rN;HjWhxA`B?wk&Yi^5~J6X{e|ACmaK(G{w z@9-l4Ta3d~iA}QAp$n0SRnI+rB_UisM1BevN~78-8;1 z)A5}cYyNrCkUh$XdlPz>_M1FXa{R3==_t=RTo|3m6~!xweoC^?#&~vWsH+!Mv8K2; ze|X|`E7B?mK|2L;8^aR;ASIY|-hVFOT+rGVa-*7u!0InX$f zWNhohD+VAKHUkrR$enFc$@f&~Zf+wAoja&TN(|ChM#^OZC?q31e77wTx7m{^opr_B zxtXQf^|Yl}bYoCOHUr!hk=^?$C%Wa*-4?=&-AKir05yd5u4lUjI=%e9UiB{XLJo&x zF}j$qZkyNA2}X!X_b5WPEYIUV;0H8g>(z>-wAkDH{2H3Y&uM!E_u}|t2k*82Y2?C!&;Rr*3PTa}&suKuh3OeKjHra>MQa3PtQ!f(A0rRwgF8ivTJX08Ne9d75l z=BxH59`l9>O6(@;m{uRxc<>$G!dSAql|@{yvAJZn|79#k?=bLF<%}!b-I1=Tk$UfP z72Ncq8t1;u2B@U5+B#K%NqAgT@OP^#r!p@G$@xce1*5J zx04mOjr5t#HjE0M)xA};V)ecw!PUk36ee%CB$dGni?ovk{pk8p9qGbtYK_7j4$GO7 z4TC&eWqe-`ZB&rBrUIF^1NS!8AMvP092fJ<3i)-rX%JlJye9Xaa?CH$+eoJI>u>D^ zd*=-Sx^*jXThu>PW*!}aG&|_5z;4HZMgI2z@{`Z$*ylV@D${8{!SKEecjoM(+&CiQbqV?}ZeZiFL*5p7T zmXBndyEU&P-2_2%c`s<%$xyXj{GPP6J7!}RRV3z1V23&0lc{oA{al$)9NH`YXG^-7^-pUJzOfSBhp3|AW1^ zj>>B9+D5kss3?em3MfcRNGb@3q$rZoA)!c@BAqJIA{~O1bf+}jh#)21tbKXDBIAj0gVBlh{U(USdHRqS_3?%K&<4Tfdx_)Gj>h-gVadz5P)79VS zepxk}xz}=_Lw$UPz{ior5j}T<9h0W%fXNghwLY@wKOWxB#X2D7<1Dppu{!uAPiLo9 znms+qqKmy96QO6B)nRZBZcqkg`Zp3G#CkoCkTWlo^CR&Qt8|0_b_obzak3PGcCSF& zt*0EfVxzb-hYxzK$kyJqwpq5KSG-35jxbQvfreuwnryli?F5lnvBJ;={H^yRIqxtH z+W2G#!?ZV8;;1!`O`gp%J5%{-{}uY=Y==#A;wukgb~Y!Y1-lG8lPTnqqhIAf`;o10_-mW5e7rrvk0HA@vn}FquR?_WPDwIE zSv%Fe<>;j93ofh0e{dbQa4gYi5n%{>W7(xke<#;N$N7Bu z*Oc9aXz*qU9;Yn1;PEB%C*>I=oloX(q}mig@qD6FyTd7#Dnn_UDOW4qP=G3&$#lmn zKi6BK`NCk`y`-~T=Cv$6lDyPf=#-=CoaPs0$$KT+16fN*X!R9Z`X)b?T6@JEe=nmU zWGGa0Ju8V{5Qk4Sf6po21LcxThntsry3`KC;*o|l119DK}o z%8BEz;fJG}fD?Od z+SCBbJPr$z!*2wvw2G94hYkZprQa2D~}_FdQd1%-7NP*?{e8Xn>wVz`n?&2$h3 z@1X)iW>d4{G<^d{Ce4+D+!?dx$i5Sz$Nuc7A!mt*>hOqS07GzV6h9d`tH;*Z7x3E} z@^ePE$lv(&e|!_Xs2j%WC=>f|ayCI7BSmp~Vw*Z^?a*M?SEYtA{}b#V+OoAzKxju? zz!_#We8bR+*9EU5Q8Mtfro(Mc<6gp3B5w@4#@xxB4yI+O_$(BS5^Ro#VqSP2ooqn# ztK(|da@Ap$oAm*)zP(5F($o71@SKB;- zIFsFO!H5l=1LzovZiZ&3IOGSzPC>fxZ=ON^;~rJMR>knOqI&CUE8QN0`4yL%z|(af z))P0S1dF~w2Grb`?OgB-4#+%uUTP-cHtBlw6+6?%4YMC}d5Sk$>l@k@ccZbuWWZwI zod<7m*dI+us5?IAtk_;C)#*-GIfgHnrz<%hG?*r? zk)WYc>l^u@P^Wa`EQyD{P=*}zKp3kfcYdB>Qn=uJ zc!LG5eB{H@cee}{I1%F4tic?qF303O7`OuW{FeJ*0X!Mlzt0<)BsS^$?3w(h6`>=f zt;;V{-X$m(f4;jyfbwjMc^W>wd!x1<+|muY@)T(!gW*|S43Mi-Ww|R{g#L*(t6fUh zcX2S4PxiYN+)k0D*#yah?-N`275W44zhu*P8#WajIL#n`)3kJ+)Aq9`odhxDq3NF^ zgrm117)(f`pSTAL-MhT32pF9HdBG7OnYX8`XumOj?+q2}&!O_7`^4=rQQ|u_jtP$u+E~p_O$Ziros4$YfVwC-F)1ld zolPPJc|T33&6=Q`s&nzyi^I#i>m%+1SJSNy9XixrX8Xu?JXop9kUSN_Qo;@vK&f;A z!AG9G6ZzCqYSw9OPKDcMD*GgppQic52l|)%bBxQIQ^XaIQ+-OnQ68&&`z`?7Cu-`k z0OP)7sSU6EJ=Si4N~9d}l}hLXMS!LuIez3)hyY|o=u*&2Dq7gNUZpjv0|5!naTWUx zpJBE2fS7JW=k=G6q}&|7^zxGVQ)l(l-K94d>`S9DMo;k)LzrH_O+UzRr2M+0J*c&M02FkOqdG373k&0!V#wkg**=pUvKZgt<~AaM=gAG&}E~C4;PwE z-nW3oCxx#+b6>O{;9YGpo9`K|m97KoFP$N`rrd{(oZOElo}a9XmR98~RlJnPCh`Ld z&=A5*JRFnHE_wcPSd0^2n(K*fvq4J8v&47l1-`oSQ1-Cjm>+3Ysi>i1mSO*^Z=i6P z^%`=OYKNYy9AXHItvj=KSasodtit|mga;Axba>!1gxw};Nb-%SBXNhOVVj?((0J>!a=766US0NBl-v!ZlNQYnN#_Z^sHQm46cMVx zA*bm)73+TgbM@Dxr(rxc;cuBsrYLQ9zSJf>9DRRR8oU8x1`rsfpjWqMZR=l z?aP%z)^go(CN~{FUo*KNSpLOvuJ<8#kb}tD+z=hF^+Pz z`4}A%^tK49tug(?A7!~w7{jk4m}E_<$g4C5j?x(=(Vh*M=e4Sr%HZU+oa&(Z=bMu6x$}70Fc(PXq%23HC&dX*o3onGE^=r%{i$tHme#wJa&=owj zy640kV=L1gZruS2lhF91@hQ&XTtnTN+1;(xuaN}DGQ{U|^jf0KCnG2K^Tk4*mM|lg z73m#I5yO@IK$VwUAd))!a~5iREJf}Z3k7sHkxi68RQx$toSH%@)1wv+OuE(Uw~CvO zY7E~!DTDL}sKTeOcq~<=^gmU^VaNiRmC_^BgI*kSAD%dE?CIGm29_`VqZi>>trd@5(>F*&23&Aco3on<91nJ$h1DT>=D}So!){G4)ftyFzVy ziTX-&Sl*Bm9D?b{(iblC_RRa;HNn+Y77>cVE*|Uem;D+p9{)K(Azr4w>KIF>zRV}@ zx8#jwU-I@(IGcagqPG+@z^or+5V=Fv!6Ql{FXL-jv{hMj8{Ek2Q$+Q)h*#?N{oC@*y7a=Cj-u zM;~pi5UZ>Vkh7b$nU!p-wh7J)mLIBcoHUg~lcr}L2q7DqclET%H^2r zmwGsD|{()xVZ2^?l9k{Mr4l|_H{lC99ao}ygpHP%cz z%$DzuOJrrzOF!a+7Db$)Kg60w7W3%dNYB=6q9Ce|-o6kJBLtOy<8}U2Yb?W~j55(! z!`XL*>(rt2V*|f(jc_UKaa2szv3ZLpN3UT-c?~{lcAV@a9iCU?3T+^2oF$XXm<;?9 z_@qiG4K)!beE0&dWM?gDp~BxnWgXDhJm~())k+@DV$kAMrHYT!ZO zV|c<{(*as1@mxq=3O*&@O14jHHQ4THrE}jiOw}QpKrbV+-q7IG{!wDDQ}om`U$8m! zT6)NYa~kr;lWT8VEX3dtd0jsxzk#le)>$2y*v0JE7Ie*Hw{FYIo$BG2__~>Oc2e4U z$@N5j-~}%O-=+&ni9vU41Zn6MDC;{n&-^H>o7cwB2t}q(X&EPrhWx-kPJ9Zq2Vx7arFIIR!f26Ay3U=#bz>HvTzw3ZG*AUH;wdk{O)zee; z0c8?3?L;X9~Qa8^(?=+L_uahlTm28KWZX#Y1e)$4Kfb4hRcNYfC{M; zpLW3ma5OdaETNce^k+<>R=7Zc`1Wn=HStvmKnui7ndc4R;C27un5U{acB2)7F1Y4* zw%?+*z3#Y35Wx-BYfHEcfN5c684k3UM~5+&g>Wy&gF3tw|nurYH34OYg4 zP}R}mP2n?yUAZj$JzIe|VKIn@Y|3710n^#5#()6D1XzF7I~V_WtN;QiJ>ZSionKO; zVabxHHS^seE?fteKRwArWr&1cIF9;oZDHZru|U?aMjvb*D@5nY{U`}VT!9t%O)KNX zD}a-{k~^%3xRBMree9}?+TV-Pfk86Gy>a+>;)ZXdcOm{E8>VWVrc6Imss;Qo;4vLf zcu=SlJcF{1C+#nuL-%1tk6@R_H2~28s#ECPcuI$1dreHJ|M}1Wl_6RY@Tq0 zxEbJye-lEsC4C=D#Rfg%P1TUxckn{b>QlKUZUKo_5|;%YrWcTwYSc} z7Y%|CEB-cs^aye8C+@jz-+VU&?PCyhn;c5II_qEVwFx=@3|SuhUzX>4(hp;-^v2-4 zx|`MEu*efcujP+KB;_z9`;}yX^lcD6%16zi*Bk_~E8>2W3WrS*ob#`36EJU|4HtW# zq^AVGI226Ssrhgnd#GF(E3(gBuB^ zD4BDfy$CZlw8qq~vwiBcC5l;@Jy+xBqbS7gs)MyQer zIaAU|RnN)a^o}F5X1!a54c_F7IwrH>vgIC4=j)wZ?Na2ADimaf1UEJ4MW&2Wrw{m92l{YYTrUU6c*w+}(yC`cYoN*FcMMcil6eLeTZ*_`SFh2r}1M zYtwFVMPxm2B3}D?(6bSm#QibXju4@rRXTp^hvPG+FdUO323YBX{DSHTD)QkP4`V{W8#1&u_R*>9y zq{|EZ1>4)7+{wqrLp4KC7&LGCMLJsAhMBIkcZNoaRF+sGD5u@rvzhm5yby8}@vjq2 zdB#SH`;h`DyGO51^Bf7uNaH+UywKKi;O3?`qiV&cJiX?IoH6I?u2e;YH^DTiEQj^~ zp>}CuYfMuWKSDX`--mS_6K)P$E5EmJR)P5TU7KS+@ag_NpsY;cG_u?Ksif>Y?EPF7B*XzkGL# z|6SK-P1Tj~vF3<@cE|Lva*1mWI~TdXEFLP(9=_f_nc+?Y8IzFa`i~1pz1a(%oL__) z298?Mx)h?({tlY37D730?6tPxUc+wNxtCRGZs<(96R~bcgWs=7)%WQB(SDux10#*r zai}k-?$_2v25s9D58RHHHbE9TesR1UtKWCWG95{6ubsJciT2~O7@-*g1Wy5u4JtYC zlvLE!mv^;inwljeIEe?cn}-h&ob~H}W285w0MR(&t2WtpoHoaKZd}UZMi6r4SK8mI zA3(x8I$eXove_aOkmR*Xv47;)Sjbb*!`T0dap&vRFI^n6iFVwLlR8+F5U_dRl-etO7MS>FQ;wA()*Ttv7h%GcN%S0q#tS>j{#2NC=6)*f{&r zR4B5SHGfs0am08Fij1Hd>g>o>rTla{>uV$Lh>O^A@#t`f+5?Ufvx`)0%`kF1Y-D2w zzJV>0M9bN^1|PVjtCx0jnE_FhLv*SP9euCkG5PFLtPkW89}@=L8YY5CcUguo%~Z1U zsWVy>(0`}s(4RS=K}6U|cH!M&{5|3a>y-IU9UHgZQu1Q7^HK8Pjb}DuA3`s8kJ0+-E$M^7=F^IzFIyNbC7tKCCc~R{S zl2D$erl+Sjzpt5_wF_r8p~#9s;>kT6N;Tzh4Z0U(p^>xJw;KBLWQ*jF(aV&@RM+5S zDn#1knayg@s}u*__ij98-V8B18tErx#B1Swl)>-bMjpujvVPNaUOtL)Mm`G5wyHn$ zr6A6#O&nz68$Jykpkcm2K!3Y99&Rdcx@EGcA$cj+LcmioR}nt+h21yK+t-?G-OWCD zyi%KduwZ9-TAElxZd9>k z4$Gi@6&E0HqP^boMiFeLUdF09f@{Y8k+CEALlNSm%{|*fhcP>Ah0iNyuWOP5S#{;2 zn^l%Uwft!io&5=3>w;t~h zdRn4#*MSJ0k!#Dpnv-+PkZxS+s{FtL=(!pjooaYyl zhhHhuQ8-+idKcreYLnpGS%PNA7|me2PMb;jr)bl;U*3FUeJNP+XhHC+;?Eo=sHKSF z<>?OcXP0$He53qmMwtEf02mmv6-V=W%QySC%rr|S-HgmSlQb+%QyHzxmryZ}j8!WH zui4CHJoXdS^-dX%Nwlz=AQ&+?>Y{dy_>!`Jm+s7)a#fzA#k?_nU#M!w;?RqAUVV+Z zGYj_Wt4gETrJVXty#3YPw>4$wNrt;c-doty>xT)s3INz~%*?EsI=!Y=iwYJ+*`nb2 zj~iWt7@79oz?Jkj>E^#%6ciq$A%$iX)?P2?T6RtN?dRjNj~`>%8i_mGNw+fMxZx8M zy)4bgcD~mDYi8w%Q#2p(at@Xz!KTQB(`H^~Q}T)9omA(LxMgX*f(Z%s^BlwBTg3K@ z7clD#U73_eod@~fU7e}Mv0N6)7N2MAjglYeZ<@Keeyn_KG-)^(5=2v~D8s=8PpS5z z##OYF`})~{wjqrpBz8AZBl&?TC&*OSO+xRYSGJYt*plVQh2|%RbB^8uB+JHoHpADX zRH8f(>Gv1Pll&otDyhJl8C1LZo3Pe@RkUwyb{rsT_kD2top-_vQ5SWJEd7Y~A%F@@ znz4`0FOHYCjbK|8mk4VYqZf41wZd~lWoq39zLFbj!DhKfjwe6i+(~hqK{akl?22!?d`JI1r;^hX)rv?~wCip(}g-5p6Du)Ek{i@vctjZ;L#tb$aC zyvOjZ7P;q?YxqNc5I5fl%7;^|#f8N_L{CNN4TN9JwR@{cJj42+ffdQ(#+U)7a7#69 zE0r%KLqp@1fxXncnVTn?mxI#|7SV?%PyFX1GVU#+*gs!HVc<3f&i{rO8P6)g1Cp3C zI9*fW16jdq(pCihPG1{*mfKB)ce7GX>;pGT!<=Sf*;%@rBC|i9zb$sV4)FU1kw-?O zc%0f#V<}%UaB}N`xc=O5zPMYy1A|tiv?SSUoJ-3fhCJhH$JeJ(4HX&o2~EZ z+Elp9rz+4)g{QN&IVaiW9RRM3?qKzwp0@@VkROK|ejh*hwe;?Ud}|9iQ*z9U?ZwTl z{Q(dO_S^6D-9p~UQp!+M2h0#7Vu$tVwN=|L*H^CZW{sfdBBAD56rJ@QKXjrM)%0$D zY|FGs_(h5okLX^k>EC``m>`kNu@%)OjJ`pZ?B{}JGXH4lZfwIX1vO~Gd#z~6QdH3z%2EBqo(Z8Z6{`c zt)1W3uW0U}%0;TNSx6UuFDFJVf(pK0=gT5iP}KVN*yYU;s9Q{1f4-CzH<`BHSR-kO zVE^T1D2@=luba!QqP{2LYY7c9bDrj&`+2TM_s+G}KR?$X92zv_HOR;@7K!^)Zg7;f z-pxiyLXyGOpv~xIRHa|PO{ua1&Ai$lCK2V=B)0cUl?Gd=Us zWOM!Q>Ggy9rwn9}660I9}sGSMyl6_i{$(78y9r%*{(`L)AcFJa{ z?^N*kAXJqH?d!Sr*(^!uJj0OyJDkG|WknymB&6kJ95Yz!%{kcGAt(9#WVp#s(oV9y z;Fr)pKfKY`k{{@#*_qcaRdVfi_>#)I2}V%s`jG!bDfoNTXxh@PtIfOy+KT z-65nbdmpfG+cS^90@EM`HPUsCq|rp$deu4d8kE3!BZVDT$`T`Oq{Mz`fY>~oWtR5V-BY+GhX z8AudNOu7hRx_EvSN~Xu>ecGdy{gHutOktYx?&6yjh$#oY7eExV%>4pj%K0{S8>C3r z-W!OO{-I)arWSN9)JfN90R-B%?`4Q@laAj!_3Sq%mLwa>+Nz1=MNhf(;fA?FO)w4 zo}NBhH?B#4=zwk$?`QmtYYhpz90Rvu;o_y=dn`NWg|BO$_jPf<*w%H zjEnXyP4e{R8CbVo9@^S4X^)@9C)Vg7mH%D}z`s!oo~v#X#c0vXx7BpjhuS5Xsn=i1T*lk0dd~A7xl^8l@;s{!~M;zS?nsxKw1f&DgS`S5O(*T1&hWsghUvF@00&H;>0ZScX0~sTW^I3SrMO-FggRC znws>bPJVEQ_wC0v3%WBa+bi@bHU>1ihi`UhPp{A{4^xd|y)VMdJIQ7YSMi$T{=foU zr>Ir%)whZ3VN`vfr654c2rlFannhkFX-=mrTEpK8_Ph=_n57p~C_GZyK=nZVg)G!z zP^_=4zwTN#^bOt3q8k(Qqu?)RD*igbIcB6IBbenG>tpl3pko36%QB9b!~u(^w&>(3 zT;2I?Wpi~J6P=G?Mn;hea)29PZJo~Yqn^Qj`dVlI$d0th3g~>D;f+4x354WKa?7se zHo{SX+3|I{CIHx1HZ8sp)HM~Txy-=WBY}~~sA8&kIRu`cD#p)ke!g!cahmVq*u()d zpI$|TDUIUJ31QMNGC+Jp#z3p~6Zvuvs1e{c1B&OL-eyp7mZ*$ir3E_*EumFhhXJQ~ z8j5NB;$%81?m9K=Z3>aZL)+xs!Fw2p0C3demWMZxSTF z*&g4B|5_^if^13htE*PB1O^pI%1DJAO-LACq-}F_#3n^49*U79Rz^~W=K$>+VX33^ z<3c}Hf>cCNVeUWITBHCvlU_pgkB&$vysi4xk(u)|9Ep|kmU;VFOKFj7yWzT0&xfS|JkIB{9JJ}Xh;+E^ z+a}Wk=JS1#$xr(}xnv8VQEl&{S9+w}l0PpVHvmB03BK(mb2Z?Z$Yi$v8PF(0$gRpR z|MShp)LJLi-=Dw#xHD~h*JS*C)WvsXQ|VTCl|enUo+LL~y5^!H#uB&tZ{AreiW%yx z$NNgRL&;yxFT7?lK$_o#qa+^)BqAZHN*Wh5kD#x7WFv%dop7eUE&9^F%asLPYGa#g zEnBszL+LLp$lDmT=8%|}l$SC@>Z9iCB3nk{nyeM-uDP$1t-12Z$57_YKCveEhXUj6 zh_ijsOyqoH8q%WelDbV0x|(~ zYKkTS`v=yId~fu!^yS|W13C@~`xU`M5G{*GZ5ypWM~FK%QBG4XwF$Q(Q}?|~3&e*Y zff#yo)$Dc>)!?eEbV}b;K#aHy^CMK)MQ_@s!^Py6MFV-mZ^cPTitsH{rph+{6cgvFqxSRI-`G3$+5hy|Q;zaGE(gu^mDQg9qYf~| zxq@g^s2#`7(y(wqdgS>BLiW#GO7KD%C|z%z@<029`-o4Z$|Txn3%6{O-=e!wHrpmP zM!g_wh_Byb>#DnzcAJESbh#VI~=!Z=-8s~JM7W;a!9EVtLE^nJ9m~k=-UZW zCkFC#3aP1SH?~CVUYZ=A<{2CYnB)Ls-PR8R(o^jmPr z^Pk-d&TADwLMZQg92c3O>y!$|3B;@wm;h(0NSH7iv2CwsAMq9lg0YGa4^~eD7lVgE zhm0m_$#rMvi!Zvbu_ygPk41%4YEKr0?cr$W&ZlCNX$ynY_kD;5{_chmViF#aQEe~Ri8qkk%Wv=~wJfxh3Wd?)?1iQwvV(&IrX{sEIm z-<{I%+*7!Mm4>jEiibI_{rN2f^!cIl`GPX=zRWfm@15pYnAZdA^I8BJ#_`r7KJ^V> z`XkWYkr+#`$j3y^zxg6vrRdf=9un9yb$lI2Jto#V6iRTg7is@NU^(#(R*GZ_J>+?ZM=B)>r<2O1y2=Qwr(rQ!qK{*3f;V(tjaH51Vb|5Nm zu2t*Aoj>V!w%<{JV1efXxA}x$nd?_Es9h7`!=u#n-+>?p=b}l}Ia?^yemW`s0r$tz z-zN>!Fb~YQ|Ec}j+t4xwFb9NVMv6838_@z1;O#}M3q>d4Kw$a!k!3gm8xiJFFLN*+ zN$>4G{yR2BkDAoHpE-$2q}ez)i~rQ&|CQhJ`NR)V_6Jr00(l4?X@w@bN4~TXBgx)M zgvpuq9{Wiw1LSz{pZ|!Em@Q;befOIPz2VdMa%SfCoAGIq@VD_C8_wt0Q0f`hlwP8xIr@ynPBg4!P*x(wUDl;1T0-u1k<=+k-p9 zt66Wu@_B|GaTP$M!{E^$3Z+X1WB-ly6Gk(Lbf+kP1APJhQQSw1kw1{? z@nb1?JI`DFZ}e1%G}1yymen@#CE*yX8V4>H5inJ2ox`{P6k81G&A^1r?E=N*Af=CC$UP-`JQSme%JJO`Y! z3409xw2%{IB@X)ZXR?$DF18kZR)P!Y#2|z}ACBrCaq^Sva@Z>e0gNLIn5ERD!zNI& zg5YLeX)-;7v?>`O^}jJ>N+IS!ykYhp3p654{&eFb(&}@mh1pS zH)lVCNRP++kf**^`xEYUQdfeqgoQL>M$z*jAZDt~SNnK>$eE9bE?G!ZneqCYZQT<^ z-wScYq1S;HJwk2%xE_{J0$1l-XYReAt2n$?xxr}qBOsi{b-t18@3rjKY6Dy}KxVAq zfubyghe(M-@wosKQn@icllP6)2@$ghk?5dOUMJDt*iw5QdA0~_3Qc+l0T8Cj?lm0< zgm;D=X?XDCeExwIg2?Znp7yuEp0Gz352AFBQM~dsJu+c|IYa_!&cjr*bkol~Q3h@j z!p1JW#UHPDjBwS$|D`bgO{9DeEUblKX2g{*BE1j(AD!;K>^qWmsOX2OYBnjeVdXFZ zcqk;TW%y4?iQF{JCRwGw2|nzVFjgLiZ41d(<$*RD;(IMeBoLkz_6zTRrZx^LBdS$a zLmZg#zik{y@+m-_#O(J@+?#L?q8uFG05g6?c+NfSdq^)FCZ*`|pW1l=VJxW#Psj}W zZ)NtaEes888`Ir47SQ=5WSJfM9E8DK-rkpS`{_{TG`#to(NVVB3_d45wp$ zoIh5CpQk?N$dBp*xTh(|#@Svx_v**SfjNRO(-JU+??u$B5$gQ)t|(+I@4b$BAgpdV zcO2J#-nJs7>BzQWNXfmwu`~7-Gz6)jdDhh~_~ZO(A%y0Eg1@vYsFX!EZdsKF$bY|W z9N~9He&tJh639b+??M75cfIR55fx!0tnj_!t-0@H%3xn0)W_>xOqMl&<8$qaSSH8| zXdOyRa5?^6x>h0`A81{E?N^Y1Y~1)83jle1+c*#$1n?A3w*DSX2w~~og#;|$zk=gW z!O?+f70LA+`w-mWNP4#hNPbRySE45|8b;qMdp*nZ#P$y5-aqUA_$KQ^S$RSp8`3L8 zD^uXh=bG!gh3-Gt0g!#ALxk5og(|fZ2%JZM4|J-9v>BZBdAZl#pmP7-o>fnGB#E~~ z@eLI|P(&~T`dD1UHgV5Mxd;ybIWFw8NDo4PokK>qztip_^6@0xBawYC>GzJ&Aku>{ z@^ZRjMmWH|t0KMix?sk6SjCSfLlVS8Z{53h?*mEp!$04thrqk69NE>*{m=K5Bv?=l z_ynAHS3{jM4>K1Dkl9VeCVyz6MutF*1$ouy@uZG>WBB%u2UKReCf|;@2Bm);V#MEI ztU_>Fk5#qpH3C0hfXMB_;T@d8@3C6MYW-$N|AEzvK%+jCJj-Knk~)r%-Xf;MYY;)e zQ2b#JC-u<|xpWsPocCFqees!1~3_Xaz-^dH%Cr$Jd$eR&dck_GvhnjV%Z{ZtyJE{(Gr;=t#k~6 zN&>vt*hKxYb0pY6S}S9>PtbLlAeQCh_&MH-bOMk6*oTGu9)5Vze3DRp>cd0&b_P`7 z$=kB-$ba=yoKJ2g(COKGp#3nkZLvP+xQ}JA*MR%yA0j_kB!aD^_d?uXaWgXBBLIv{ z!t<>J@GYwgrMSogK^g)wv1P(jhizgz$B6v$w# z4jyF47w)@^j4fsbI(3Yn6Vj|~@9$J{wAzvCJ!WjK*^BTj$_g^D`{;0X@8f3>FhbCk=u}#Jnz~2qXVBPe>@Q@1ARf%uZk$ zX4Sy^tm1K~^<4g|pMQw_z#|6(kh%-TzeoizS^gT8&b^(722lAHOu>bP|P<*k{$Z~eQ)|8DWWxA-62Vl(9!<-F)!rrotd)Hw@Yx1B{X%woFB zm7(;!W*(2YbGP<<3Q->c2T#jakb#d!ttFo?1QI&1d7eqG>vjqpB}iVaH7;|Dh) zf%VgmDxhpJOrcQjefy{o-MU51a$Wm&LpU3qKs%vk3(bV<(2)^IGZE_I>kF zLW(UFr(46`MEi28e`GY`YvTCnAz~7t8XPn!2Qr z=gzobzqqKk{acls|rpVo6C4+T2s-C50~CK&GX%NNvYC&P_0S87gc24(d1izBO|&cyoe2uJ+( zA__&rXRIO<+a8YEoj1E^5El6DV{h@8DmFg5@VSoOaH*7b-s;GIPh#}kjj@#a*J&Lj zoYHubQP7`i&)ZYO$NQ!FM2D>S#jYB@&dbQ8E_hdV%1K9lzWYuwIOG74Y8UGy;(`A4 z4Zpg6$bXecg9o?$Uf%cT;{E0afm*~*oNLuT^FO~326+v<(FIaDKFa^w*UvG6&zmD0 zclXb&`}?oQY=i4xVkWWb@qd}lK?~@vCR9t{Sr^WUD0e`V&s3h4LB<$qBDJs~)5Gd~)n zu@&P&(iubF*wnje(kX;!z$qliy>RA6`ET_mkNo+kc-E-lSf6c7#AS4APk$C~nY(}J6a3z~IZ1TpgbN;G;qr|63Ag)j4Tqgl)_Rf;>_kbx znToogl4S?{YZ8S+R%#`qFL>LNqMiDeu4?=8S%uqt_Usr7l#Zf}FwVWSKKLlf^_1!b z$xCe_`zx&C_K(=jW>_qiuP36=t69^cD(JoEy)sIU zx#GjSP_+M=^w}#88%q{I)|teZRhFCbbq=_D4H_>Ne!w~zDMPuF1Jq6H&lnSWve$!MHr3>L&6ag)6gcZtgmf1 z$P;Kn@38Mq@?)mC6&yHC%D%jJ;d?Wxb{>pDERm$&p1*^aj-TmxinmzyRZ7)3r^fE# z&IA`qEW)Iq@3Co+%`J40oD^|uPS*iR?(v}M7#D(DM!?baxY_bOv0nUCtyaUe?YTg! zVcW^KuU^yCz8^D5x4Nw!<+#>&3B55|IJOfvN}W@39b1-xX5n2U3(CRbQo_>Z#@>ml zNYW-E*yJ^)v-0YcC(U7NeJ4VS9U!k$boBR@W1NhZ#9@Y452_ALgTFFQLz2c&9dB&h z(rWDaK;Rj+ssiVbMF&$h>#)rz5syy#H9|F&SC5*x9lL$ci=_81O+=Wjsjxgf&A;8( zf~RgTuW(%cj6!Zxq>@Z5cb2YUts>LN?t--uK_8r1*>uy_&z<18 zl-~f4FVFMsOhR{>`xkyukPIs5(~Zb)=9!IusTakKe~#x;X*baS zMri;FU~cQ-T=v~}hW&)nyW{``kmSu@hU~VYA;Hd_r+E7d8O_JwCEA8TWo|U5G_9NRGC2%?;Dk!{g ziDn|N2w@#K`#`{MIa7_=~`M!gfOQJC3>M}aQ*`1CNR<$)u>@p{@TyE0fwanxcKun28vv&f1RoTe4s(_aBYN=NinOcrzZN#vEzCP<+m= zkjN|~M(APMZ5270yYe$U)^xBs7Ryumr2p~ppC?kr?g<-BayGmoali~!u2&|-JD;E5 zZS9`&2ON$B$R;WUF5IOQRT@2|1m4}TPdh?sEH z2#|dlbgSsi*KIGqJIXHs5runOJ6~HmK5(?WN{lcqxdck1%ftw3BdJ86*JYCSMC*nd z@qq0IF-2AGrz!{O+*B>T88%4bW+O} zyTw#%mE-1eBTL6&mNLHyxZjtWuZ(t{U4MIkJRY+djS*W!VVuJ4W>WIjq(Q^rx9mdL zARgGw_>AP9s|+6H_`oVmVn3I2xG<^Y-Yb@cCmSpac|}fT7?Po$Rp#T98z5$9Dr(be z85TY*HLkNb3D zm{}B>vF4dmf=FcYfY|sUO7RumC4)r3OW;L&S*fWaQki?~+vS`);NjqFWo$Ahi3of%9}F zjy%EUXv^iEJA5qHd#Q#=PixrR&Dvs1K@C|A=yYGl!aY_}wAOFb0bkcxANI0hE*=Xo zm`(_liZH9NB{3`eLh?f8%zXAcf{cmX zcvtk%i!n3I>5I2!{Pol77Om?a!fdbvUD+OXuD<+L}#05!(6rto+%gCOD@$XJc z_c4&I-f~h1f+1wkY8|VsUhP&hD~|#hr{%&lc1*2r%!Pe5^Wj`hZTDHtqA%Ju*I}`D za9aVc{P#Lts}s+{9?-l6n)m&$8xm4IBW!F@3Jp7$9`*RSXKNTClPt2vvl+z`;nVeW z(R0NX<@KM!n3|8SID@LIf6!p=$(zhv#%M-nU@%Cb(i=d9;8x6eNM;Qx7gf>Qi#Ay0Dl zY-VXxr zHRpP6Pu)mJ&u_ogXN7$m_qhDV(4tc)VvzJ$ute$mCah}LJOg7ielp6Q*}e)5W^_i% z=`{UkU-CPo2D~R%do)`@zO;lYt2)ENwQOB_i+v&`M-HWE5T&3i zC?`IX;_;C3OrlBkd!977S1s6>jWXUSO8!ABrP!Hd{z;UPq52elj*bZW5N4{e-zX~* zNB>^1kMcu`sHGO^{Mu7E5Hz;VLa%}rdxZITfWPkj+?yVa#)79)jPb`EhYZs)+OK~7 z5SY%eIv7yhaJnrC#OIZ#FJ8p{zyj11oP4-D6C>B@>sNEv{T%1VZ9R#rGBS51 z&RROs{+Hac8}apSrKU3&{Y z7LC4`EjE^T4bs2ka+3!U`8{u!Tb@bj`mm3HEv^Olu)Uq!J-lx zwQ^%Q3YB{r0GfiN`!qq_x7~C?eia^L=gQ*12-@n@*)Wj0_N&`qW!7gZso$gV4jXF< z?d3dQh2KR}#gcPb_!;@~I5}qHM#)%!csnA?*G8N%m-_Ap>L_d-X6PJvef~ZA>0#z% z`xZ`)u(l`%!Wfn4B8|A1DzGWL7%1(MkuVh z;AEE$V)xeaqaO~L&{4*mJh2*Va)qI`t5lP!lJHZEgQs5&Shz2BqaD|nR&Mtt#t&Z^ zTYTAz8_&b4z^%(F=z0)cy~eP)&0E0lsc;q4WkpnC&7^Sp8IBSciKfB4xYEp+swuL4 zJ%0hK{c0s~rc24tAHJ9`<8^38lqTxEZ_+ZO4kUG2qMj{ep!TTa_yLtFUWmoqlj#lCv*9sb57eGMIpS%rDBhHhvw9QZ;K!5A;^og=E4shcUWg zs`v-6L+h${V^Bt&_I!%09`zeD>@v}Ig04HM*g59}(?eQEI<_~ruNrH!m)k9uYsgS2 za(b8$%mnL2+0tf|%%omsxt^>CMyct|(*u-)Fa^G>+J*zi=FUjeNA#!>(KPe7##9k0 z-D#W6OD!qS5&8i3q)LaYa*lSz<^-uk;rnE59HRHhpSo3kC#qwHu!dm854C_SM7eX& z;*A+W+_8aAm%Yo$UsBultw2zx|q{0EK#W#>JSz?v@Yw!>uNK@ z8`V#K(||U4328;f$Sof#+PFSBVE*yRfy6NVtRTcm&{gnJ=KBP8_@b>N$bSh?T#EuF z^47ENz1Jugif4|WSj!0za^CmywNLW8#jb!O9IfRv`|4X*U24!`a1u^)ut}k;Iwdwc z$o|zGdUARFe0IU{$9A#Lm38jc(}Wt=1v|IYTH%49cDy}YvPyZCW576XbsK#aIB5!Q z5rbaYHr9S7ms*%xJeS*OS1yg)RnH8}SFw!;l-K`WjR91%jj4;{}l>JD$1hcXsHYkyOe8M~``O1|3%Efk`F# zqfG!7rsBkZGJz?>HW-cZ>HRv(rRC! zpoRt<7bye%6KdQaAjNq(3xwjXfg7=|zRd@4GhQ`A*dwZk%H#Nxpo#|QL=$>%)vZUv z{^E6kDa7f1IDd>vG`{89Hq-TyRHa9rkGc*Y$EQvvTo=Wb++s}2C{QKgG**^=gT^A= zjWm<6R|i!>`f!)#x2lAp)kn}3)M^qa)y(&7v`t^7uW&uR_UYLPLk%RA+Q9-wuG=Ww zMlrr&WaA7iwot$bsY}jbkl-?Msk7km(MQc? zF(Vxl5J4>WwGd7FPz|^JqezZn4TNTfzRqYBi0MNY<7f{}P^2B;Gsq{GhH5(5_QM|A z&rB4io%ulRP_gg2qP((YmqG`aq=SpOXjh(fjFMr3w0}%hLa0W+k+{3)e3QHR^4*9X zFTCBM(KrU^c&enSb2s6Msm}4;)AITgndf#bafn1QXY}{|s;qv!MXfbhL#O1Ext@%8 zeeyQJ2bVmuB*{=m=5Cx1=PjVM)oij{_&@D^XIK7%l^`Nfa^VcfG3$kPE`>TmHr8Za$v3c4$iVV8nz226ijQuLNvFk4}$oY213i4kX$B zgHe27TXfjOw{*CM34IB40Rw44GYB7+|0r@zW0cOn69)f5QP6MSC2m}95Ub7gkoL%UKP?WE|ZeQM-om2&a@izH@|JYuIfx3M&^YUEh6Z`Hm z6&Ypr{+`_ar*}NBSXNx0O!ECAxL~jh`CVD3>0ufaf_K>ww{hc;;RS$elq>IrG{1~@ z@9osE2W&I@{_S3;H{9k7nNrHc@f;XQPpX0$c^`B3V%G;$ zg!-==MtqdM`8{!S&0No^p4mFPw&v?%#bUW(KrvJ1H*nC>VD5>4a`hw*SAv!2@iax9 zk!2PZ{>=W(;^$IV87sM^?uh#C%Ei7X!D`n%_JDgn`dfGui&>mwtzpIr z$P{&(PE~{85YJD%{hE`D0b*;6dHJ|mIVf5PajH} zwr=)qZny{6Z@_2Y%7)@*RuVVnG6?mjBM9DkW50b_0}#hS0W*Zcgok2W;PYw=alUYK za`}9cDROJ_uV9RdS?1{A**mtUY54D2);bk-W-QZi(ZjWLpn!?~FkrZCwx1TRZ9CAV zJmvpWlP2bLvl8f`;H~(q<0{?ZkmU8-(N^#Whz9WeKfu?YG;px9D9@UL%^Aa0`K@aA zdw6i2oDR(+9_<~};?v$`J^+cB=?DE6{PSSQ9vg;Wm24Hx5{hO~CTDglLNObCr2*&62h6Zwh9;0Q|vl -TuOxSK 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 literal 0 HcmV?d00001 diff --git a/docs/images/gateware-rx-only-basic-block-diagram.svg b/docs/images/gateware-rx-only-basic-block-diagram.svg new file mode 100644 index 00000000..bf470a17 --- /dev/null +++ b/docs/images/gateware-rx-only-basic-block-diagram.svg @@ -0,0 +1,4 @@ + + +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1d2XLiyFx1MDAxMn3vr3D4vjaaytpr3mjjfaPbu+/ccFxiXHUwMDEwoDabXHUwMDAxL3hi/v1mYVx1MDAxYklIXGKE5W6Pw3SMx9ZSVKky85zMyiz9/WVlZXU46nmrf66selx1MDAwZlW35df67v3qV3v8zutcdTAwMGb8blx1MDAwN0/R8d+D7m2/Or6yOVx1MDAxY/ZcdTAwMDZ//vFHcIdT7baf7vJaXtvrXGZcdTAwMDd43X/x75WVv8c/8Yxfs/deNM/o7ujuql4oVbav6i2v2aSn41vHXHUwMDE3vXSm71WHbqfR8oJTXHUwMDBmeFxcMO1wKTVcdTAwMDFuqKJCTs6O8GyBa4dcYmXwnz3HXHUwMDE5nZy+92vDJl7CXHJzJDFcdTAwMDZcZlx1MDAwM8KUXHUwMDExkyuant9oXHUwMDBl7Wg1OFx1MDAwMlxiMDBaaS745JKnXHUwMDBl/blCJkdcdTAwMDbDfvfaW+u2un3b6/+AZ/9cdTAwMDV9rrjV60a/e9upXHUwMDA119THn+Caut9qXHUwMDFkXHJH45bxIeNcdTAwMDNdnWr/7LnzdOr4rLvwXHUwMDBiXHUwMDFizY43XHUwMDE4RPra7blVf2ifXHUwMDEzkOCo7V1vuzaer/9cdTAwMDV96rttb9tOWOe21ZpcdTAwMWP2OzXPTsPq5TDybZ3a87dFrlx1MDAxZXiebYFcdTAwMGIhqVx1MDAwNFx1MDAxNjzHQLKA89jhg25nLGaUaU6lYkFf/UFcdMVrOG617rZcdTAwMDZe8JxtXHUwMDFm1kOiXHUwMDE3XGbltldzn25cdTAwMDGlXHSRnEtNZfCtLb9zPd3zVrd6XHUwMDFkfMv46D9fk6S53+tcbulcdTAwMTd/XGbd9qj/sOOLm59bN1x1MDAwYkszSrCD4zNcdTAwMDL7pFhMmlx1MDAwNXEkaFBCK0GlXHUwMDExIGLiTIE4nFx1MDAxYk3wZsVcdFxinSTP3CGKMKqe2/mU57g83y8uzyBcdFx1MDAxYSCGZihBoLVms+RcdTAwMTm4UspIQmneXHUwMDAyrVCNIFx1MDAxZoG+OamU5F79bHR0f7R+8qM8gDatLyzQhmhHXHUwMDEzNL7mSaBFVKCBoSAqYTjHh0BQ+nVMoIFSR4OQhlx1MDAxMTS9ilx1MDAwM8RcdTAwMDVaS8dcdTAwMDDFdsBcdTAwMWFyLfM20K6o6X+9QD9sLi7RRlx1MDAxYsJcciiTINCGxsR8XCLQRCptOFx1MDAxNUtZ6OdcdTAwMTOBXHUwMDA0hqSwtv1QqT6sXHUwMDE1Ovvt01x1MDAxMpR33fJN20zGXHUwMDEzkUS33+/er07O/PM1rV3eO14vnVx1MDAxZmx8q+5tsmH52uyvlcvJ7Vx1MDAwZb2H4aLNbq3vXHUwMDE2XHUwMDFh3X3wXG7175detaaborBgd59/S9Fuxlx1MDAwNf6Wj3anjz869qhiM+IoTSlcdTAwMDcpXGZBVI4otnBcdTAwMThii0aAQq3EXHUwMDBi4rRLoV2Q+NGKXHUwMDAwKNBcdDBcdTAwMTWSolxc1HjYdzuDnttHefu3q/JpXHUwMDA2cEJWq/E7eVxmhqzhXHUwMDE0M3XZIKRpXCJcYiyjysnSq4yRaFe01lnIViCMVlxicfjF0tpfXHUwMDFkvzP0+nW3XHUwMDFhmvJ6tzM88lx1MDAxZsdMkUSObrhtv2WfvYw0Vmz5XHL7XHUwMDE0VquebW01/CiGPnoyk1x1MDAwYtp+rVx1MDAxNoa2Kjbq+lx1MDAxZK+/vVxiRnb7fsPvuK3jYFx1MDAwMCtcdP13b4fdXHUwMDFm3uBpXHUwMDA0w/6tXHUwMDE3fkje1otagENFikZcdTAwMWa6ZLChL3Yv5NlGcXR1eDHY+Xa/MF5cdTAwMDPRyuGKXHRBXHUwMDAx/5M6QIAoXHUwMDAzVZKhY0V48ERjXGaUSVwimFx1MDAwMpLoUb0w0Jd2PiloXFzNR4truVx1MDAxNJSjtuokXHUwMDA2XG7hrkxpOUP6r1xmzlbuXGaUh1x1MDAxYn1cdTAwMTVGeeXSjfDvKqVBa3/jiJOu2iolXHUwMDA0XGKegDRcIs3SWDFjXHUwMDE0u8KQzfAo+0Rwclx1MDAxMJ7wySkplSBExWR5XHUwMDFjXHUwMDFi4MBcYt4umTJcdNxcdTAwMTM9NmSvTFx1MDAxOM1cclx1MDAwMzRwXHUwMDE5JFlL7Vx1MDAxOflbQKvmXHUwMDBlmt6vXHUwMDE158fFxVx1MDAxOW2I0FpRkVx1MDAxOFwioFxuZsmzvVFcbkVV/jFcdTAwMDJcYmtXXHUwMDA22Op1/WmCXHUwMDFi/LZcdTAwMTI87PFcdTAwMWaT3//3NfHqmVx1MDAwMvn6u1x1MDAwYjFJXHUwMDBlmouR0sHQ7Vx1MDAwZr/h3PqdXHUwMDA2XHUwMDBlLaDAL+G+J2AkN2c/XHUwMDA2XHUwMDE3Pc/b2W9cdTAwMWPv3m6sb1xmT9ZDPHi13a2NxbHbr/jD8Im6/+DVyvbRRZ7cXG7CXHUwMDFm9l5cdTAwMWJglKEzSIiOPFx1MDAwMVx1MDAwN1VcdTAwMWRpJ/7QNlgnaWxcZlx1MDAxM+K+6nVq80dwdXn0uKtkoXRdO9+/XHUwMDE5kn3dPi2+blx1MDAwNLaThjGBPjrOXHUwMDA2XHUwMDAzqdTX6Fx1MDAwMCV2XHUwMDFmXSnkX2izXHUwMDE4T1x1MDAxOcN4XHUwMDFlitbyNT03pns4wvA5NCb+XHUwMDE05nutSvd+LLRcdTAwMTHCMe77kdd40Yyojtrv3Fx1MDAxZVx1MDAxY/W8qu+2XHUwMDEyvnP6XFyKbb91XHUwMDA37v366UW/w/3e2fre4SWqzsJsXHUwMDA1XHUwMDE5hsOkXHUwMDE4442hYFjUXHUwMDBiseFcdTAwMDWjJUVHXHUwMDAzT6E7YuLxsiC8XHUwMDAw3KpFiPBcdTAwMDThXHUwMDA15Whcbuh1PTVcdTAwMTNYgXy4SoXWaaXy7+Yq7uHixl0xdFx1MDAxOI2BXHUwMDE4KbGPWurpo5PgXHUwMDAy+lwiaNp1KGycU3Dhe/Ho8q7VumxteuKAbO6VOlx1MDAwN98uX1x1MDAxZlx1MDAwNPjWk7DfuNhsr3Xq/Ye19vHPs6uLXHUwMDFjYlx1MDAxNm9cdTAwMWJcXGBcIrxcdTAwMDbyKuKW/lxcU4JcdTAwMGKoh1xiVJJynHItXHUwMDE4TPE29FAo53hcdTAwMTGeXHUwMDAyXHUwMDEydlx1MDAxZV602tpcdTAwMDX7oUSCQm6XXHUwMDE0XHUwMDA0zznq/WGiXHUwMDBibnlxVcbZXHUwMDExmiGVSOJpis6kacCUpnhnyH3Mh6ZlXHUwMDE230BcdTAwMWGfnfOjzfJ2yJj9vqDCXHUwMDFjaJxcdTAwMGUqTPU7n2BCOvuZXHUwMDA3z1xuqCOU9e9cdTAwMDE00s6pWFx1MDAwMqIzVZSg30WQbUpcdTAwMWVXYzT5XHUwMDBlJ1pySdFJMyoplIDgjDZcdTAwMDCRQVwi/lx1MDAxYqlcdTAwMDNF+oz9T1T6e1x1MDAwNnRWklx1MDAwMeGhNalAo5mauZZFiUZcdTAwMDfbXHUwMDE4vtRaVlx1MDAxYdzRb3fNXHUwMDFk3z/c5o/HJ/euvzV42D57PTq/1YpCaX9fN0/uz9tUXHUwMDBlvvPvpHk56vdyaDc9XG6S2u5cdTAwMDKoL7nSKmxcIpZH/fT5SkF9dPdcdTAwMWQhUZfRYlxi9OFiqK+QqCNcdTAwMTEnSqF7XHUwMDFhXHUwMDBm1lx1MDAwMOVcdTAwMGW3qM+ZIFx1MDAwMtl+YFA+UX++iciwpoAsXVCi4lx1MDAwYoHWRsRcdTAwMTdcdTAwMWEmqK9xXlxm1yE37b2sKfw4XylcdTAwMWSVV6pNXHUwMDA04PdcdTAwMDD+c4B3XHUwMDFh/JO7n9OCQrV9bVxu+nrjorB3RnrNi3p95C6zoGCYYCTkvb2QXHUwMDAwzVHruVx1MDAwNqltJkBCXHUwMDA2wFwiLOAzXHUwMDAzYL6KV1x1MDAxNtdwY2zQXFyoJFx1MDAxZl2JlPArJ0ZIXHUwMDAzS2l4XHUwMDFh/NXP21e8tLtdVK39+ulVr/ZjIDtcdTAwMGKxgFT0W858pGhLej9T0Fx1MDAwZoCDXHUwMDAzNiVLakBIXHUwMDA2XHUwMDE2UVx1MDAxNIQ/6+sqVCVNNGNx+OPMQdk3RnPFXHUwMDAxUfJcdTAwMTP+suhGNYPTy1x1MDAwNTNM0UT4k7PXJlx1MDAwMKdNKibpu1xcUn9cdTAwMGaoN1x1MDAwN2pcdTAwMTLW0fNcdTAwMDa75maxt/F4XHRcdTAwMGZcdTAwMTfkYueuoTvFarO0iPpcdTAwMDaZm0xLXGJcdMFcdTAwMTjlrJMqhFx1MDAwNoriYVxipfE4NFx1MDAxN1x1MDAwZbXaayhTRFx1MDAxMpG0aP6pvDOU18+gvIJIVEEqk/zbcNJkLGJlXHUwMDE0slx1MDAwZmreRHlZWFizKu/+2klcdTAwMWXK2/Lqw1x1MDAxNNVcdTAwMWR2e7P0NtLZaSWN9C5cdTAwMWYlLVx1MDAxNvp7m+6t7vXgdHercvyjPLh8yL5ohD84ejFcdTAwMDHhXHUwMDFjSyNlXHUwMDBlaEI1Z6isQqmEqFx1MDAxNFx1MDAwNUdcbirQU+VEUJ1UMiCMg+4sXHUwMDEwyiUg7YXcc6w/xKLRz8X1lkslhVaJi0ZcdTAwMTCOXHUwMDA2TOuttCutXG5nNG9GurW5dbKzUXvY6V+Qy+bjkfx5R4dcdTAwMGIx0q9pzfJz2lx1MDAxMKdra71cdTAwMGLvhFx1MDAxY69cdTAwMWaOXHUwMDFlR416crPZ4zzcXG5sTkw3ffwpUMmJdKxiXGJCXHUwMDE5OmuhiOFYrDhxUC81eoxCccZcdTAwMTmLKSB7Xt1haD5cdTAwMDHCfOpcdTAwMTMr5+rc9eI6J1x1MDAwMFx1MDAxNFMwQ+dYXGZBX3ROWD+f8eWywNOlXHUwMDE3zfNroPKovP1cdTAwMWV47lx1MDAxY1x1MDAwMIst7YR7nVx1MDAwZoSmJ+bML7pTjpRcXFx1MDAxMiaBszBGPlx0qbb5XHUwMDEyhiPqKVwiQMazv9/Dws6HQNBOXHUwMDA25iupYFooXHUwMDE2y7CwXHUwMDBmW8zUZkAuZG9drkopXHLqTs9cdTAwMWJFfjeCjXqxcHDrN+RVkd6+XHUwMDFlQV+xUvJWyJzarto+6P9sjjr1q7K7Y6rtbqmyX32PiJ8+XymILyRzOFx1MDAwMrp1blGbp1xit3JYOFx1MDAwYjeO9+azVmR5XHUwMDBi0c2QRE5cZoI250musYbZrrGg6Fx1MDAxYmtcdLknkWeV3SS8X7HY2++2/ur0vYY/QNxcdTAwMWW8XHUwMDA3XG4wXHUwMDA3gFx1MDAxMyjAy0BWXHUwMDEyxpFcdTAwMGYpOLi4Wz9Qt5U1MyhcdTAwMWU23IJcdTAwMTi11k1cdTAwMDZSoFx1MDAxZGSFKECCaCFIfKFnnO1cdTAwMDFUXHUwMDEyKVx1MDAxOUmIgS2YizlmXHUwMDA18rmZvFx1MDAxN3o+XHUwMDAyK6hcdTAwMTCeYaVHaGRYWiWxgrTKXHUwMDExQFx1MDAwN04ztN65p2M+XHUwMDE2y63SNavf3Fx1MDAxY1xct+/IIS9ublx1MDAxZbzfdMy3Svh4U1rAcMJlTlx0XHUwMDFm6fOVRlx1MDAwYjh3XGbTXHUwMDA0XGZcdTAwMDFpwlx0KE81pNTme4CWXHUwMDA2oZ+bOC9cdTAwMDBiXHUwMDFjsMSAo1soXHUwMDE4elx1MDAxYZ/EIIORXHUwMDEwWZa8gKKNICwpXHUwMDE0YMzMXHUwMDFkO2z0zVYwmKWMRGqeJ4HQ4vSyeZ4rbqf2V6fe6t6/oOt74Fx1MDAwNnNwODHz045kJXkg+ZCDdPOZVoXHtbEhdVszXG5GXHUwMDAy0OiuJtQ4WoNcdTAwMDRcIlx0UYxcdTAwMTBcdTAwMWVcdTAwMGZcdTAwMTdwinZcdTAwMDKRxtbFXHUwMDE4JFx1MDAwNlx0XHUwMDAxP+IwobhcdTAwMDFcIpTgzKA3kTMz+ECKLzMovlHomymZWIjH9MxiXHJcdTAwMGVgluVcdTAwMDYpai9cdTAwMThcbsFSap9rXHUwMDE13kx5XHUwMDFj31x1MDAxZJfEoL1cdTAwMTgyL1hIl55cZr6yZCFcdTAwMWTXWjBpQFJjgp1cdTAwMDCeXHUwMDA2wfFcdTAwMTDCKqVcdTAwMWNcdTAwMDfKiVx1MDAxNLFBZKykS7dqS1xyoUCmxlx1MDAxMNquIPsoYrV0SfVy0Yq6aOXbpJYuKnr5XHUwMDE2003Otbt33r7/9FxcXHUwMDA2Z/6w+axDXHUwMDBiMLb0SFa6LddcdTAwMGWx5f9EXHUwMDE5rpicWjxcdTAwMTWoXHUwMDE4SuH/KCBGk3guXHUwMDFmZ47ktvBfI1x1MDAxNKB7XHUwMDFmUL6QJVx1MDAxN9Q6iXpM/YCGss0+XHJ51JBcdTAwMTczXHUwMDE4cvSmldaUJ+bswsycXck1RVx1MDAwNVx1MDAxM3lb8uXXcvK15LPk0X5cbnFRfL0lT1/7WcpcZlx1MDAxMsdMfaJGUFx1MDAxMFx1MDAxMu94Ruv9XHUwMDA2ldwkUy+XsM6p9c5RoXqFMd0+Lz9enFx1MDAxNlx1MDAwZb3G962T81x1MDAxZF070qejhYwpoGCNo1lEXHUwMDFiSamK+r/Ii1FJOPJcZlx1MDAwNDe7RWHcmlxu4qBcdTAwMTckuEL+zG0tXlwiL5bIRuzea5pcdTAwMTOtaPAln9Y0ak03Z1RAhFTieTJpXFw6n60sui/4XHUwMDAxSIymJWSvTPiysvlEMlQ78buzy3I1s4WZkjo+XHUwMDFiXHUwMDE30sXt7DRfmnEmv41cdTAwMTJyM1x1MDAxY+lcdTAwMTVoaYbDXHUwMDEwXHUwMDFh2VUxXHUwMDFharfpMYorhC6NpJdcdTAwMThcdTAwMWRPYWPM0YhrVFx1MDAwMkVh5Twh0I7UWoFiXHUwMDAy/XVBXHQ2l3eo/Vx1MDAwM1mOrVxma2xUMatcdTAwMDNJa2ygU1x1MDAwYiuIYeEo6McyXHUwMDEwMyVyfDZBXHUwMDE4X8/E0ndt+1x1MDAwNZTmd21AXHUwMDAzmXr5XHUwMDBlLWf6kkua5VREhkvSY4aT2q1JkHFJ677Ga2yYcezaiUC7i+4v6IRcdTAwMDVcdTAwMDewcmyUVFx1MDAxNFxiXHUwMDE4vNArfFKuWYZzd3HDSYkhQsjE7CUmZvqvjKLhVOGZzilcdTAwMTJpXHUwMDE3s4NH+fvs5iyJXHUwMDFjn01cdTAwMTLG11x1MDAxYs632Fx1MDAxMyt/w/lcdTAwMDbxxn+94TwtnVxuenj6rXZ17N81qvdnP1x1MDAwZk9cdTAwMWbjhnN2LS9cdTAwMGVaXHUwMDExxjklgk5vT09cdTAwMDV6rFx1MDAwNvk7JYjjUsb9VWp3XHUwMDBmXHUwMDFkr9xcdG3TN1x1MDAxMvxVUI6x8Vx1MDAxOGycXHUwMDE5YFmCf4l28aNcdTAwMTbzVsjF4raTXHUwMDFiidaTmcRE7jDOTS/fXHUwMDAyMjJcdTAwMDTD3HdTXHUwMDE0oJXJKSuxLLfafKu8R1x1MDAxZUeHpdvd0vd+736hOlx1MDAwNFx1MDAwMMJcdTAwMWRFXHUwMDE5XHUwMDEzxu5eYqbjL1x1MDAwMqlcdTAwMDJnUlBcclx1MDAxY59dQiGQYVx1MDAwZdhwXHUwMDFiml5Nw1umfKZcdTAwMWbMl99GluC11DZ4nSC+JlRZXHUwMDE1Sz7gyjAlVN55iXYjukzvXHUwMDBiXHSEMbRcdTAwMDN01V6zUuu2Xb/zV2fbplxmdNxW5HBcdTAwMWXJXGJvVNaXeVx1MDAwNPlkIVx1MDAxOOJvb19ccn72z+q33aPznUdf3NGFco2YduyylFx1MDAxMVJzYqZerPJSn4u20Fx1MDAxOEpcdTAwMTNeXHUwMDEzJOhTXHUwMDBlMrrLKFZcXH7WXHUwMDFjZdH1ZpaMXHUwMDAzZpBcdTAwMWSwxOp6XHUwMDFlh7BcdTAwMDCrqFwiaMjfYYHuRnmz+I5VOdq9fPR0r9pyq4Otys559fL+plHQ1cPL5kKgXHUwMDFjrImAofZNUcmaSqXdbEyx+P7zQKhD7d6PZlx1MDAxYyA1SbD8qaozVTVLLb3lPUaHX/dcdTAwMTLaXGKDzlZVXHUwMDAxdjNtSoOw83tR1WM7kVXPv1x1MDAwYufvvTuNTexlPoqbvrlsamqILYu3XHUwMDFiJCCtNlx1MDAxY1x1MDAwNI1uYKOkXHUwMDAzTElcIkBcdTAwMTD0XHUwMDFlTVxccVx1MDAwNddcdTAwMGV6loix6K4gXHUwMDFkT4JY5lx1MDAxMI183Vx1MDAxOPt2XG5mZJaiwFx1MDAwNXbbf4v3Rvz6nfYrkME3tPVcdTAwMWFKm+Sd9iFcdTAwMWVumyixwilAzyn3XHUwMDFjP3RcdTAwMGXDW4X+rsha5Oq43M27faYwv0FzzJFG4VxcXGKhXHUwMDA0sagog+a+TDX7XHUwMDFiXHUwMDEzXHUwMDBlx9l4oIVSoIlEXHUwMDE3Klx1MDAxMFx1MDAxZPtcdTAwMDGHUEnBVpIxlEdcdTAwMTNcdTAwMGZUZlxmXHUwMDAwvsX6TnhcYnanYTE1XHUwMDAyxCCG7lx1MDAwMVx1MDAwM1x1MDAwNlxcMpYyhF+0c/+sSptnW0HD/X/q09SQpzQhVXgj4vs0ilhrqZI8r/XJ7/FcdTAwMTKXWFwiZdQ2TGVSzq1p0Y+lbu+0ecf3dsnQVzdH9e+Lv8YzXGKUMkVQlqdee2jffiRtXHUwMDFlhNKCo0+aUFxu905cIqVcdTAwMWaiXHUwMDFhXHUwMDBl3MXRXHUwMDEwXHUwMDFkXHUwMDA2qoxiSS8+RI2OXHUwMDFknqRJMmmE3W4+ZzBcdTAwMTTAc1x1MDAwYpSmXHUwMDE3lGV5j9LUflx1MDAwZtPvUUqIlDLpSFx1MDAwMdq+koVwXHUwMDFiUI3L8+T1M8+t5E3t3kCWf1x1MDAwN7XLsIkngJGCiMQlUzq7slOg1Fx1MDAxMSXpMrJcdTAwMWPpRmJBN1sqcJpz+cYrLi9cYkQowe1cdTAwMGXUXHUwMDA00JOBudytgNqTLPxv0l5Mj15PXHUwMDA2f8FrnFR0Tqbe44Qg+Fo2+Fx1MDAwNsvB04Q2OobQe5xcdTAwMTghdufPd89cdTAwMDZZuP8z2GBUXHUwMDFiUuV3XHUwMDExOpguzPOaf1x1MDAwYj745bmVVbfXO1x1MDAxYaJcdTAwMDGbXGLV6p3v3X+bXHUwMDFkNfjyPKNcdTAwMTZavHHm8D9f/vk/eadZeyJ9ADCinterfaceSGPIORX DSP chainADCMCUSPISPI controlregistersSGPIO andflow controlADC clock domainInternal clock domainFPGATransceiver \ No newline at end of file diff --git a/docs/images/gateware-standard-dsp-chain.png b/docs/images/gateware-standard-dsp-chain.png new file mode 100644 index 0000000000000000000000000000000000000000..69973a94db572f97d8fcc83fb1b9001dd682c6e5 GIT binary patch literal 130043 zcmeFZXHb;e)-{R>A|N@b2qK|T5G14IoTKDy10u1JoHL>%B?%2pkRTvAHBCm5C?GjV zRD>pjlmpFj!aolrk*;f`R5?BOHnv=nf`u=bCZScDUrH`6!0JS z8Be;j7Vqb|^)u6{TZqF(wo@Y;MK?&X}7vtU-VKM*n^rXpsAVTkC(8NE~?4 zKTGs~(~HD|v2#=qmX;}wZC90kI$nID=IH}CHRy$!PWL9z#dYi4aS)YoyWQ(N-A@Hw zmo2FDEl4Z+BngsUQk#rDXgrvW=@r=vbD+hsx@qcl=NK zQ+&2M(LZ0^;a7UtQ2BTv#kreI>4Ao>zSrVPNfZD6lzY18dd)&)NI)s?pfXPq_{5;g z>R5j_Pp`1$OG+J1<60N4BTurWu7^#$QlZzmHtM(EBymE6JWqE=d*tr;TKJtG&mf*9 z=(nvDJbGBST5bt`@m70du|KP)FGH$l=%XehBG0cQt-RpuV{y$wn$T8_rNtxgm~MO2 z6nax^DLbxvtLx!Rq-9I7=Em7;QpWCHv2(jx_YD~T*wf&#-dNI$qQ+}AEGUC$LC(U- z#LStciT4_^cA{*(b|v}OTe0cF>e=v&XD7QQ{(KINhn8I%z&P%T`2@oeqXi8)+WD}1 ze&ef<=oTVQ&;URR55!-{FI6{Jm*>Y@^- zHSbbe=n=E3r+lq1J=a&Yg5uize(#u3Cb!e${SvD**vah|pDuaYbFW=+e8i!DjCV2h z*?hS)Qe-G&i~LDTHSyyMu~=bk49CwOuNmK14IexHYD?-7n5my#$k#=DZ91OXV6g6- z8VaR>>BXoC;D!>?SPHrw8*f91G3x>G$X9IOtUWvqKST z87DsGQB~;SPO0jAufU8C<@}=7ncwl6?j}!7Fuu~BeZqTTV&UK797YgI75KICFYKtv z*{RaI5*Ou&PBL-0e8O{^4>)+_z!&S2+^0RIR!+dNiVO+5bmb~sXuUd1TeQXi--(ea zQ6@Ll%|OPlhCpz$g}h)-&5^n4yCn^y;}_L%LX93@t=m?`oQ}iVx}HISh!*SFmN0a~ z{xr`<<8iSkI_Y_LoSNuOz1F1XY1Vs_&b@uh!*{0Q)r|;*6s%%OGem3F7f8u66szZKqk4K~Y{)ep6lgYcpmtLPQ=9xi^JVf1u&Wwe)^pXrg+U>!Jpl)b}@ z$_F@kM6FZRbFoUSU$u_iCLP~4PzIBRt3w5s zO5k_hPNKkuPDEJucZYEHD<2IJ{ym5Z^dT3Acc6R?3=%^)?~clX`0FS0*3|ECUT6LF z$#p04%H0t|-8obDDc2~N>R1_jj&joMJ2nAXzk$%EAgj9KH_NS4d7K$u{)h9_6<>&i zDxG?mHb1ZBf}k|0sU|W!U{Rwwu&t0I`w|a1k48A~i+JzZS&u9il+Z2KvUR%2y?ehJyjeLXe_1mEnKDJ0B#FvpYQ<*G}s$Ua=`bV)6P!sV9)NAju**pDPS5ibw1 z0ZR}py6j_8*Bg&v6$6TF%(4P_P{}CI-C^AxON6~nKS%2`^*$>B*QJ3El6E&Ay?C$h z(6CFk5{IuS30J>OIN=%xtBSlMCfe_oU#cd0*b*_3aX=YfkwGH>y{`!GQz6Eo8jZT$ zx}~m9R*`9Bp}i6hYxOzVY&WT?9p>JjboNGhqv7Un#vV|#81;rj^^QOst`}cWVn=Ef z7;<8A;V27IYBtsE(}U7amn{?YEyJd+a!X$D?nPj4?!A5)M*^X^?p-sVP_S6WKZoSH zlfWc=?^_Um^4c8kw?=fbZMXjORPD{WI90*U2#pvEMMbzeLiO=Hh4Zf{0Ucg7j-tsZ zx7HsX+dTq?TlatLf{2xDfg&S?1i`B0#q);e<0r~Cn$D*?8l7A}Je}B!x-mbDKE=do zqQ`S2YI&uaaL&7qQkQ#OM~z4Gg+-b=AqbC5UB0sJ5;-pZwl}o4OvU$qpo}bWm|ID{ zg9KV%rK6;4pc|z@oot*{dU@ygg~j`_LArX;p6_xe1mtczhOjh7Ux3op z-N5m|@w^gC0_-HX$1lIcCXmXYFRrSW$)SiL;uaoHocr* zu7|~{Jb_=EeF3)In~Q7JWeobtlV;ivpWx|~yj)y07uUbC5{g%<>ZXX73u6yAnUGcv z|LiX&`}%ahqb17`{pY*oxOn6&=|1HwB-NMY)DQ7*nW)I$5cVk5xE>uw&eCR`mM9k# zH0g%Zdelewrr5Zys!}EMOSR)KgNF7*Q@@)1>0^b@?XH?qlCoM)lH4a9U+F_%Qf8KK zd0zmrDy=au%jEzfg=*p*j~{_1lel8q1b%B~j#D@|P)$!_ksWnyYO&o})hBScpliMc zy{!yQSzgYj+QLDJ=C=^$=XiVZG#Po=ytHXEKOVG6;UQY~A{e>yhKSt76`8!R1S z5T~~Wz|OR}#OFR+c88%n4avPNj5MB-l)+U8`-@Olh|77=^ zXRtj#-QPc}OXj?bQp3IdB0%3V>6!8?BHC{6jfUrQvI+tes}vUGr=lIqNi8=T7+DR? z0=MM#q7hsZw25iN_y;B>oKTc*;Lr23{U+3FNci?$!)gKPOjz*Hvqy9nfs8;<<5INf zNi>JdO1Uf#ebgp)5t3X_qzKSOl#?+VogY{EpHyF?5H>`#ZWV0gn7?_PGkn!^(&^Wn zk^y`SAf#^RUXjr!$jPhLU&d50sO&rR6)gkeo33V1@(%I5vjk5(Jn7bPFFn$3fZ9bumdgrIz`1V^MXRgUgq|blra3`RGD0l3R zhOwksfp}OIzU)dMiDFOlTK<@jzH;>&I8k@>HbCOAm5sddvn(MZ2S9J0(k8Nd;83K7 z@0+ftQ~^*HeiDUG9AsW%% zY7nC)8HG(FG)&VWEDw)9et?rt6u$Xhu>F7V#1Zp#fcbn}cia6th-`||m#RWn^7Bcd z40*zJpA(JUl9mQ@)w{^h|GTVzCZCztZkJ<)w_+>iqa(z9-_G!lou<*$VTsIz<%yR(c_ys?nAN+dTz8?5HRej2YUk}G6Vd-Q~ zP_2JUD>PF7yO{tDUOn8W`%Tl+=cETX@mPkm zEuz5@HPett*ER8+$44mvSev_0{4pkbLbz%r=luj1Fx~4dI2%>@J(`hZt?8w4u!inO zzapbtBA(y<+qM21e+~pweiVPE!u;fmO|7n5ti$!{u z{TctJn69*J&DiFUpujo-St1Z>ok3n18MbTHjjqdmp61zBr$UO(VR@|r9;hn@Re(kB z9Jf*#b`#VV`Q6iV6)}r5d`06fRmSZ0{aIoyyt=ss)U!}J-<|%Baw8!&zt&r}g``I* zF#YKsF+5`p zS1OQ~xA=D(>3`Rq;-CK@To)5-ipzTI&V z$KqGWn|^193p6z^%C8*T4EikgWmp17E&2E+uUi41T(GF|XDE%1mgi?2>K1$_+F{jG zzkzH8+4=8W4|cZS#~tlnZF)2K#$ek8;HTzlr)M%coFY=O%`Gu|rA$(G)Hdry2e z=jMk0`9zAxY#61s7$|-$`(>!3CQ|ecE3HL5e*Z|Qmng&8dZ4^0`=%w{>q&BR3y5F! zh}%wt+G>*UyjQn+|K2)r`p3YhI?I%i*TrkmDt1Dt&fgvvEk_lWDu!l<;5iQ26CcN)o^1Cz$iAhUlHb0Z{tiIH z2~|v_XIVVBuhtyWRUHGrYmHA1dvLvM(jrbpm=tz6pD2(j=*mqi5~2&vzGmcB2~y8cBqjS66U-2y# zgOS~ypxo8yDzJ0Chbqjj9IEH!&_S=1Nz#d}TQzZ>cC;BPYYblwRRE zdG@vJTg+(pgp=zCzDr5$s0^#GRS9~(8WEegMS73ZUl zQCKjs=-71{{z8Tl)dAZqQC3>NgP+qy_(H9|RR-UY_~cFx`K$++wt;&h01QEynU9yq zWrkr#xUG#N^N;>jyA>YPT;^cw+s7mt%3xUD&id4Z@Znq><>ArJ(%^ENY#KNnI%Z$N z6LkC6E4~SKVIX>-uOEOF-Kp-=OH<+B!JhVjmcZ(~k`f%wZ*RcnsJz~$g+lBQbwnl6 zh+#t+mP_ClRfP1$;2bV(sTA&jl9F!@SStYHf~O23^!iP+TGy44tqK~4`ZDe7VNK_! z8|VS0hf%}Ssp>O!6+PW8VdQLEVmp0O01PICY0IiZbU~>E`e_C++K3S{MofP!>wswZ z38=$+m=lf6#!(J;;A>~c8v?IxJIVjpI6G(;yVd$wμqJBWb83Z3Q+%c7x5_t^?V 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