Skip to content
Draft
8 changes: 8 additions & 0 deletions firmware/common/fpga.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@

#include "ice40_spi.h"

/* Supported Bitstreams. */
typedef enum {
FPGA_BITSTREAM_STANDARD = 0,
FPGA_BITSTREAM_HALFPREC = 1,
FPGA_BITSTREAM_EXTPREC_RX = 2,
FPGA_BITSTREAM_EXTPREC_TX = 3,
} fpga_bitstream_index_t;

/* Up to 7 registers, each containing up to 8 bits of data */
#define FPGA_NUM_REGS 7
#define FPGA_DATA_REGS_MAX_VALUE 255
Expand Down
89 changes: 89 additions & 0 deletions firmware/common/radio.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,19 @@ void radio_init(radio_t* const radio)
radio->config[RADIO_BANK_TX][RADIO_OPMODE] = TRANSCEIVER_MODE_TX;
radio->config[RADIO_BANK_IDLE][RADIO_BIAS_TEE] = false;
radio->regs_dirty = 0;
radio->regs_locked = 0;
}

static inline void mark_dirty(radio_t* const radio, radio_register_t reg)
{
radio->regs_dirty |= (1 << reg);
}

static inline bool check_locked(radio_t* const radio, radio_register_t reg)
{
return radio->regs_locked & (1 << reg);
}

radio_error_t radio_reg_write(
radio_t* const radio,
const radio_register_bank_t bank,
Expand All @@ -76,6 +82,10 @@ radio_error_t radio_reg_write(
return RADIO_ERR_INVALID_REGISTER;
}

if (check_locked(radio, reg)) {
return RADIO_ERR_LOCKED_REGISTER;
}

switch (bank) {
case RADIO_BANK_ACTIVE:
mark_dirty(radio, reg);
Expand Down Expand Up @@ -106,6 +116,20 @@ uint64_t radio_reg_read(
return radio->config[bank][reg];
}

radio_error_t radio_reg_lock(
radio_t* const radio,
const radio_register_t reg,
const bool locked)
{
if (reg > RADIO_NUM_REGS) {
return RADIO_ERR_INVALID_REGISTER;
}

radio->regs_locked = (radio->regs_locked & ~(1 << reg)) | (locked << reg);

return RADIO_OK;
}

static uint32_t radio_update_direction(radio_t* const radio, uint64_t* bank)
{
const uint64_t requested = bank[RADIO_OPMODE];
Expand Down Expand Up @@ -928,3 +952,68 @@ void radio_switch_opmode(radio_t* const radio, const transceiver_mode_t mode)
nvic_enable_irq(NVIC_USB0_IRQ);
radio_update(radio);
}

bool radio_set_config_mode(radio_t* const radio, const radio_config_mode_t mode)
{
// Check if the requested mode is supported.
switch (mode) {
case RADIO_CONFIG_STANDARD:
// supported on all boards
break;
#ifdef IS_PRALINE
case RADIO_CONFIG_HALF_PRECISION:
case RADIO_CONFIG_EXT_PRECISION_RX:
case RADIO_CONFIG_EXT_PRECISION_TX:
// only supported on praline
if (!IS_PRALINE) {
return false;
}
break;
#endif
default:
return false;
}

// Don't do anything if we're already in the requested mode.
if (mode == radio->config_mode) {
return true;
}

#if defined(IS_PRALINE) && !(defined(DFU_MODE) || defined(RAM_MODE))
if (IS_PRALINE) {
fpga_bitstream_index_t bitstream_index;
switch (mode) {
case RADIO_CONFIG_STANDARD:
bitstream_index = FPGA_BITSTREAM_STANDARD;
break;
case RADIO_CONFIG_HALF_PRECISION:
bitstream_index = FPGA_BITSTREAM_HALFPREC;
break;
case RADIO_CONFIG_EXT_PRECISION_RX:
bitstream_index = FPGA_BITSTREAM_EXTPREC_RX;
break;
case RADIO_CONFIG_EXT_PRECISION_TX:
bitstream_index = FPGA_BITSTREAM_EXTPREC_TX;
break;
default:
return false;
}

// Reset radio state.
for (uint8_t reg = 0; reg < RADIO_NUM_REGS; reg++) {
radio_reg_write(radio, RADIO_BANK_ALL, reg, RADIO_UNSET);
}

// Load bitstream.
extern struct fpga_loader_t fpga_loader;
if (!fpga_image_load(&fpga_loader, bitstream_index)) {
return false;
}
}
#endif

// Update radio config mode.
radio->config_mode = mode;

return true;
}
28 changes: 23 additions & 5 deletions firmware/common/radio.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,20 @@ typedef enum {
RADIO_ERR_INVALID_PARAM = -2,
RADIO_ERR_INVALID_BANK = -3,
RADIO_ERR_INVALID_REGISTER = -4,
RADIO_ERR_LOCKED_REGISTER = -5,
RADIO_ERR_UNSUPPORTED_OPERATION = -10,
RADIO_ERR_UNIMPLEMENTED = -19,
RADIO_ERR_OTHER = -9999,
} radio_error_t;

/* radio configuration modes */
typedef enum {
RADIO_CONFIG_LEGACY = 0,
RADIO_CONFIG_STANDARD = 1,
RADIO_CONFIG_EXT_PRECISION_RX = 2,
RADIO_CONFIG_EXT_PRECISION_TX = 3,
RADIO_CONFIG_HALF_PRECISION = 4,
RADIO_CONFIG_STANDARD = 0,
#ifdef IS_PRALINE
RADIO_CONFIG_EXT_PRECISION_RX = 1,
RADIO_CONFIG_EXT_PRECISION_TX = 2,
RADIO_CONFIG_HALF_PRECISION = 3,
#endif
} radio_config_mode_t;

typedef struct {
Expand Down Expand Up @@ -225,6 +227,7 @@ typedef struct {
radio_config_mode_t config_mode;
uint64_t config[RADIO_NUM_BANKS][RADIO_NUM_REGS];
volatile uint32_t regs_dirty;
uint32_t regs_locked;
update_fn update_cb;
} radio_t;

Expand All @@ -248,6 +251,15 @@ uint64_t radio_reg_read(
const radio_register_bank_t bank,
const radio_register_t reg);

/**
* Lock a register. Prevents any future calls to `radio_reg_write`
* from overwriting the current stored value of the register.
*/
radio_error_t radio_reg_lock(
radio_t* const radio,
const radio_register_t reg,
const bool locked);

/**
* Apply changes requested in RADIO_BANK_ACTIVE.
* Return true if any changes were applied.
Expand All @@ -260,6 +272,12 @@ bool radio_update(radio_t* const radio);
*/
void radio_switch_opmode(radio_t* const radio, const transceiver_mode_t mode);

/**
* Switch to a new configuration mode.
* Return true if the mode was successfully switched.
*/
bool radio_set_config_mode(radio_t* const radio, const radio_config_mode_t mode);

/**
* Driver instance.
*/
Expand Down
1 change: 1 addition & 0 deletions firmware/hackrf_usb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ set(SRC_M4
usb_api_spiflash.c
usb_api_transceiver.c
usb_api_operacake.c
usb_api_radio.c
usb_api_sweep.c
usb_api_selftest.c
usb_api_ui.c
Expand Down
8 changes: 8 additions & 0 deletions firmware/hackrf_usb/hackrf_usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
#include "usb_api_board_info.h"
#include "usb_api_m0_state.h"
#include "usb_api_operacake.h"
#include "usb_api_radio.h"
#include "usb_api_register.h"
#include "usb_api_selftest.h"
#include "usb_api_spiflash.h"
Expand Down Expand Up @@ -182,6 +183,13 @@ static usb_request_handler_fn vendor_request_handler[] = {
usb_vendor_request_write_radio_reg,
usb_vendor_request_read_radio_reg,
usb_vendor_request_get_buffer_size,
usb_vendor_request_lock_radio_reg,
usb_vendor_request_open,
usb_vendor_request_close,
usb_vendor_request_set_radio_mode,
usb_vendor_request_set_radio_frequency,
usb_vendor_request_set_radio_frequency_explicit,
usb_vendor_request_set_radio_sample_rate,
};

static const uint32_t vendor_request_handler_count =
Expand Down
141 changes: 141 additions & 0 deletions firmware/hackrf_usb/usb_api_radio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright 2026 Great Scott Gadgets <info@greatscottgadgets.com>
*
* 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 <stddef.h>
#include <stdint.h>

#include <fixed_point.h>
#include <radio.h>
#include <usb_request.h>
#include <usb_type.h>

#include "usb_queue.h"

usb_request_status_t usb_vendor_request_set_radio_mode(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
if (stage == USB_TRANSFER_STAGE_SETUP) {
radio_config_mode_t mode = endpoint->setup.value;
if (!radio_set_config_mode(&radio, mode)) {
return USB_REQUEST_STATUS_STALL;
}
usb_transfer_schedule_ack(endpoint->in);
}

return USB_REQUEST_STATUS_OK;
}

usb_request_status_t usb_vendor_request_set_radio_frequency(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
static fp_40_24_t hz_param;

if (stage == USB_TRANSFER_STAGE_SETUP) {
usb_transfer_schedule_block(
endpoint->out,
&hz_param,
sizeof(fp_40_24_t),
NULL,
NULL);
} else if (stage == USB_TRANSFER_STAGE_DATA) {
radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_FREQUENCY_RF, hz_param);
radio_reg_write(
&radio,
RADIO_BANK_ACTIVE,
RADIO_FREQUENCY_IF,
RADIO_UNSET);
radio_reg_write(
&radio,
RADIO_BANK_ACTIVE,
RADIO_FREQUENCY_LO,
RADIO_UNSET);
radio_reg_write(
&radio,
RADIO_BANK_ACTIVE,
RADIO_IMAGE_REJECT,
RADIO_UNSET);
usb_transfer_schedule_ack(endpoint->in);
}

return USB_REQUEST_STATUS_OK;
}

usb_request_status_t usb_vendor_request_set_radio_frequency_explicit(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
static struct {
fp_40_24_t if_freq_hz;
fp_40_24_t lo_freq_hz;
uint8_t path;
} params;

if (stage == USB_TRANSFER_STAGE_SETUP) {
usb_transfer_schedule_block(
endpoint->out,
&params,
sizeof(params),
NULL,
NULL);
} else if (stage == USB_TRANSFER_STAGE_DATA) {
radio_reg_write(
&radio,
RADIO_BANK_ACTIVE,
RADIO_FREQUENCY_IF,
params.if_freq_hz);
radio_reg_write(
&radio,
RADIO_BANK_ACTIVE,
RADIO_FREQUENCY_LO,
params.lo_freq_hz);
radio_reg_write(
&radio,
RADIO_BANK_ACTIVE,
RADIO_IMAGE_REJECT,
params.path);
usb_transfer_schedule_ack(endpoint->in);
}

return USB_REQUEST_STATUS_OK;
}

usb_request_status_t usb_vendor_request_set_radio_sample_rate(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
{
static fp_28_36_t sps_param;

if (stage == USB_TRANSFER_STAGE_SETUP) {
usb_transfer_schedule_block(
endpoint->out,
&sps_param,
sizeof(fp_28_36_t),
NULL,
NULL);
} else if (stage == USB_TRANSFER_STAGE_DATA) {
radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_SAMPLE_RATE, sps_param);
usb_transfer_schedule_ack(endpoint->in);
}

return USB_REQUEST_STATUS_OK;
}
Loading
Loading