Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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