mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
host: Complete X4xx GPIO SPI support
Update the X4xx SPI core, radio-control integration, examples, and tests for transactions up to 64 bits and the revised register map.
This commit is contained in:
parent
b1e4016c2b
commit
3ed25c39c7
@ -29,7 +29,7 @@ SPI_DEFAULT_SDO_PIN = 2
|
||||
SPI_DEFAULT_CS_PIN = 3
|
||||
SPI_DEFAULT_PAYLOAD_LENGTH = 32
|
||||
SPI_DEFAULT_PAYLOAD = "0xfefe"
|
||||
SPI_DEFAULT_CLK_DIVIDER = 4
|
||||
SPI_DEFAULT_CLK_DIVIDER = None
|
||||
SPI_DEFAULT_MOSI_EDGE = uhd.types.SPIEdge.EDGE_RISE
|
||||
SPI_DEFAULT_MISO_EDGE = uhd.types.SPIEdge.EDGE_FALL
|
||||
|
||||
@ -61,17 +61,18 @@ def parse_args():
|
||||
"--clk-div", default=SPI_DEFAULT_CLK_DIVIDER, type=int, help="Clock divider for SPI"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--miso-edge", default=SPI_DEFAULT_MISO_EDGE, type=spi_edge_type, help="SPI MISO edge"
|
||||
"--miso-edge",
|
||||
default=SPI_DEFAULT_MISO_EDGE,
|
||||
type=spi_edge_type,
|
||||
help="SPI Sample MISO at clk edge type",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mosi-edge", default=SPI_DEFAULT_MOSI_EDGE, type=spi_edge_type, help="SPI MOSI edge"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use-custom-divider",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Use custom clock divider for SPI",
|
||||
"--mosi-edge",
|
||||
default=SPI_DEFAULT_MOSI_EDGE,
|
||||
type=spi_edge_type,
|
||||
help="SPI Output MOSI at clk edge type",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@ -138,24 +139,34 @@ print(f" CS = {cfg.periph_cs}")
|
||||
print()
|
||||
|
||||
payload = int(args.payload, 16)
|
||||
if not 1 <= args.length <= 32:
|
||||
print("ERROR: --length must be between 1 and 32 bits.")
|
||||
max_payload_bits = 64
|
||||
if not 1 <= args.length <= max_payload_bits:
|
||||
print(f"ERROR: --length must be between 1 and {max_payload_bits} bits.")
|
||||
sys.exit(1)
|
||||
if not 0 <= payload <= 0xFFFFFFFF:
|
||||
print("ERROR: --payload must fit in 32 bits (0 .. 0xFFFFFFFF).")
|
||||
if not 0 <= payload < (1 << max_payload_bits):
|
||||
print(f"ERROR: --payload must fit in {max_payload_bits} bits.")
|
||||
sys.exit(1)
|
||||
mask = (1 << args.length) - 1 if args.length < 32 else 0xFFFFFFFF
|
||||
mask = (1 << args.length) - 1
|
||||
if payload & ~mask:
|
||||
print(
|
||||
f"WARNING: Payload {payload:#x} has bits set outside of the specified "
|
||||
f"length {args.length} bits. Remaining payload {(payload & mask):#x}."
|
||||
)
|
||||
payload &= mask
|
||||
print(f"Writing payload {payload:#x} with length {args.length} bits.")
|
||||
|
||||
# The spi_config_t holds items like the clock divider and the SDI and SDO edges
|
||||
spi_cfg = uhd.types.SPIConfig()
|
||||
spi_cfg.divider = args.clk_div
|
||||
spi_cfg.use_custom_divider = args.use_custom_divider
|
||||
if args.clk_div is not None:
|
||||
spi_cfg.divider = args.clk_div
|
||||
spi_cfg.use_custom_divider = True
|
||||
spi_cfg.miso_edge = args.miso_edge
|
||||
spi_cfg.mosi_edge = args.mosi_edge
|
||||
|
||||
# Do the SPI transaction. There are write() and read() methods available, too.
|
||||
print("Performing SPI transaction...")
|
||||
read_data = spi.transact_spi(0, spi_cfg, payload, args.length, True)
|
||||
if args.length > 32:
|
||||
read_data = spi.transact_spi64(0, spi_cfg, payload, args.length, True)
|
||||
else:
|
||||
read_data = spi.transact_spi32(0, spi_cfg, payload, args.length, True)
|
||||
print(f"Data read: {read_data:#x}")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright 2022 Ettus Research, a National Instruments Brand
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
@ -14,6 +14,7 @@
|
||||
#include <uhd/utils/safe_main.hpp>
|
||||
#include <stdlib.h>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
static const size_t SPI_DEFAULT_CLK_PIN = 0;
|
||||
@ -22,7 +23,6 @@ static const size_t SPI_DEFAULT_SDO_PIN = 2;
|
||||
static const size_t SPI_DEFAULT_CS_PIN = 3;
|
||||
static const size_t SPI_DEFAULT_PAYLOAD_LENGTH = 32;
|
||||
static const std::string SPI_DEFAULT_PAYLOAD = "0xfefe";
|
||||
static const size_t SPI_DEFAULT_CLK_DIVIDER = 4;
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
@ -37,7 +37,7 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
|
||||
size_t payload_length;
|
||||
size_t clk_divider;
|
||||
std::string payload_str;
|
||||
uint32_t payload;
|
||||
uint64_t payload;
|
||||
|
||||
// setup the program options
|
||||
po::options_description desc("Allowed options");
|
||||
@ -52,7 +52,7 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
|
||||
("cs", po::value<size_t>(&cs)->default_value(SPI_DEFAULT_CS_PIN), "number of pin for chip select")
|
||||
("payload", po::value<std::string>(&payload_str)->default_value(SPI_DEFAULT_PAYLOAD), "payload as integer value")
|
||||
("length", po::value<size_t>(&payload_length)->default_value(SPI_DEFAULT_PAYLOAD_LENGTH), "payload length in bits")
|
||||
("clk-div", po::value<size_t>(&clk_divider)->default_value(SPI_DEFAULT_CLK_DIVIDER), "clock divider for SPI")
|
||||
("clk-div", po::value<size_t>(&clk_divider), "clock divider for SPI")
|
||||
;
|
||||
// clang-format on
|
||||
po::variables_map vm;
|
||||
@ -118,19 +118,40 @@ int UHD_SAFE_MAIN(int argc, char* argv[])
|
||||
<< " CS = " << (int)(periph_cfg.periph_cs) << std::endl
|
||||
<< std::endl;
|
||||
|
||||
payload = strtoul(payload_str.c_str(), NULL, 0);
|
||||
payload = strtoull(payload_str.c_str(), NULL, 0);
|
||||
const size_t max_payload_bits = 64;
|
||||
if (payload_length == 0 || payload_length > max_payload_bits) {
|
||||
std::cerr << "Error: --length must be between 1 and " << max_payload_bits
|
||||
<< " bits." << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const uint64_t payload_mask =
|
||||
payload_length == 64 ? UINT64_MAX : (UINT64_C(1) << payload_length) - UINT64_C(1);
|
||||
if (payload & ~payload_mask) {
|
||||
std::cout << "WARNING: Payload 0x" << std::hex << payload
|
||||
<< " has bits set outside of the specified length " << std::dec
|
||||
<< payload_length << " bits. Remaining payload 0x" << std::hex
|
||||
<< (payload & payload_mask) << std::endl;
|
||||
payload &= payload_mask;
|
||||
}
|
||||
std::cout << "Writing payload: 0x" << std::hex << payload << " with length "
|
||||
<< std::dec << payload_length << " bits" << std::endl;
|
||||
// The spi_config_t holds items like the clock divider and the SDI and SDO edges
|
||||
uhd::spi_config_t config;
|
||||
config.divider = clk_divider;
|
||||
config.use_custom_divider = true;
|
||||
config.mosi_edge = config.EDGE_RISE;
|
||||
config.miso_edge = config.EDGE_FALL;
|
||||
if (vm.count("clk-div")) {
|
||||
config.divider = clk_divider;
|
||||
config.use_custom_divider = true;
|
||||
}
|
||||
config.mosi_edge = config.EDGE_RISE;
|
||||
config.miso_edge = config.EDGE_FALL;
|
||||
|
||||
// Do the SPI transaction. There are write() and read() methods available, too.
|
||||
std::cout << "Performing SPI transaction..." << std::endl;
|
||||
uint32_t read_data = spi_ref->transact_spi(0, config, payload, payload_length, true);
|
||||
const uint64_t read_data =
|
||||
payload_length > 32
|
||||
? spi_ref->transact_spi64(0, config, payload, payload_length, true)
|
||||
: spi_ref->transact_spi32(
|
||||
0, config, static_cast<uint32_t>(payload), payload_length, true);
|
||||
std::cout << "Data read: 0x" << std::hex << read_data << std::endl;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright 2021 Ettus Research, a National Instruments Brand
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
@ -26,7 +26,10 @@ class spi_core_4000 : uhd::noncopyable, public uhd::spi_iface
|
||||
public:
|
||||
using sptr = std::shared_ptr<spi_core_4000>;
|
||||
using poke32_fn_t = std::function<void(uint32_t, uint32_t)>;
|
||||
using poke64_fn_t = std::function<void(uint32_t, uint64_t)>;
|
||||
using peek32_fn_t = std::function<uint32_t(uint32_t)>;
|
||||
using poll32_fn_t =
|
||||
std::function<uint32_t(uint32_t, uint32_t, uint32_t, double, bool)>;
|
||||
|
||||
virtual ~spi_core_4000(void) = default;
|
||||
|
||||
@ -35,11 +38,15 @@ public:
|
||||
|
||||
//! makes a new spi core from register iface and peripheral
|
||||
static sptr make(poke32_fn_t&& poke32_fn,
|
||||
peek32_fn_t&& peek_fn,
|
||||
poke64_fn_t&& poke64_fn,
|
||||
peek32_fn_t&& peek32_fn,
|
||||
poll32_fn_t&& poll32_fn,
|
||||
const size_t spi_periph_cfg,
|
||||
const size_t spi_transaction_cfg,
|
||||
const size_t spi_transaction_go,
|
||||
const size_t spi_transaction_lsb,
|
||||
const size_t spi_status,
|
||||
const size_t spi_full_response_msb,
|
||||
const size_t spi_controller_info);
|
||||
|
||||
//! Configures the SPI transaction. The vector index refers to the peripheral number.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright 2021 Ettus Research, a National Instruments Brand
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
@ -7,30 +7,59 @@
|
||||
#include <uhd/exception.hpp>
|
||||
#include <uhd/features/spi_getter_iface.hpp>
|
||||
#include <uhdlib/usrp/cores/spi_core_4000.hpp>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t SPI_PERIPH_MOSI_EDGE_MASK = 1u << 28;
|
||||
constexpr uint32_t SPI_PERIPH_MISO_EDGE_MASK = 1u << 27;
|
||||
constexpr uint32_t SPI_PERIPH_NUM_BITS_MASK = 0x7F;
|
||||
constexpr uint32_t SPI_PERIPH_NUM_BITS_SHIFT = 20;
|
||||
constexpr uint32_t SPI_PERIPH_PIN_MASK = 0x1F;
|
||||
constexpr uint32_t SPI_PERIPH_CS_SHIFT = 15;
|
||||
constexpr uint32_t SPI_PERIPH_SDI_SHIFT = 10;
|
||||
constexpr uint32_t SPI_PERIPH_SDO_SHIFT = 5;
|
||||
constexpr uint32_t SPI_PERIPH_CLK_SHIFT = 0;
|
||||
|
||||
constexpr uint32_t SPI_TRANSACTION_PERIPH_MASK = 0x3;
|
||||
constexpr uint32_t SPI_TRANSACTION_PERIPH_SHIFT = 16;
|
||||
constexpr uint32_t SPI_TRANSACTION_DIVIDER_MASK = 0xFFFF;
|
||||
constexpr uint32_t SPI_TRANSACTION_DIVIDER_SHIFT = 0;
|
||||
|
||||
constexpr uint32_t SPI_STATUS_READY_MASK = 1u << 31;
|
||||
constexpr uint32_t SPI_STATUS_RESPONSE_MASK = 0x7FFFFFFF;
|
||||
} // namespace
|
||||
|
||||
namespace uhd { namespace cores {
|
||||
|
||||
class spi_core_4000_impl : public spi_core_4000
|
||||
{
|
||||
public:
|
||||
spi_core_4000_impl(poke32_fn_t&& poke32_fn,
|
||||
poke64_fn_t&& poke64_fn,
|
||||
peek32_fn_t&& peek32_fn,
|
||||
poll32_fn_t&& poll32_fn,
|
||||
const size_t spi_periph_cfg,
|
||||
const size_t spi_transaction_cfg,
|
||||
const size_t spi_transaction_go,
|
||||
const size_t spi_transaction_lsb,
|
||||
const size_t spi_status,
|
||||
const size_t spi_full_response_msb,
|
||||
const size_t spi_controller_info)
|
||||
: _poke32(std::move(poke32_fn))
|
||||
, _poke64(std::move(poke64_fn))
|
||||
, _peek32(std::move(peek32_fn))
|
||||
, _poll32(std::move(poll32_fn))
|
||||
, _spi_periph_cfg(spi_periph_cfg)
|
||||
, _spi_transaction_cfg(spi_transaction_cfg)
|
||||
, _spi_transaction_go(spi_transaction_go)
|
||||
, _spi_transaction_lsb(spi_transaction_lsb)
|
||||
, _spi_status(spi_status)
|
||||
, _spi_full_response_msb(spi_full_response_msb)
|
||||
, _spi_ctrl_info(spi_controller_info)
|
||||
{
|
||||
}
|
||||
@ -56,23 +85,40 @@ public:
|
||||
const uint32_t data,
|
||||
const size_t num_bits,
|
||||
const bool readback) override
|
||||
{
|
||||
return transact_spi64(which_periph, config, data, num_bits, readback)
|
||||
& 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
uint64_t transact_spi64(const int which_periph,
|
||||
const spi_config_t& config,
|
||||
const uint64_t data,
|
||||
const size_t num_bits,
|
||||
const bool readback) override
|
||||
{
|
||||
if (static_cast<uint32_t>(which_periph) >= _spi_periph_config.size()) {
|
||||
throw uhd::value_error(
|
||||
"No configuration given for requested SPI peripheral.");
|
||||
}
|
||||
if (config.divider > 0xFFFF) {
|
||||
size_t divider = 4;
|
||||
if (config.use_custom_divider) {
|
||||
divider = config.divider;
|
||||
}
|
||||
if (divider > 0xFFFF) {
|
||||
throw uhd::value_error("Clock divider exceeds maximum value (65535).");
|
||||
}
|
||||
if (num_bits > 64 || num_bits == 0) {
|
||||
throw uhd::value_error("Number of bits must be between 1 and 64.");
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
uint32_t periph_ctrl = 0;
|
||||
if (config.mosi_edge == spi_config_t::EDGE_FALL) {
|
||||
periph_ctrl |= (1 << 27);
|
||||
periph_ctrl |= SPI_PERIPH_MOSI_EDGE_MASK;
|
||||
}
|
||||
if (config.miso_edge == spi_config_t::EDGE_RISE) {
|
||||
periph_ctrl |= (1 << 26);
|
||||
periph_ctrl |= SPI_PERIPH_MISO_EDGE_MASK;
|
||||
}
|
||||
periph_ctrl |= ((num_bits & 0x3F) << 20);
|
||||
periph_ctrl |= (num_bits & SPI_PERIPH_NUM_BITS_MASK) << SPI_PERIPH_NUM_BITS_SHIFT;
|
||||
|
||||
/* In the FPGA, the offset between Port A and B is 16, but we only have 12 pins
|
||||
* per port. To align this with the rest of the GPIO API, we need to adjust the
|
||||
@ -83,16 +129,20 @@ public:
|
||||
+ ((pin >= NUM_PINS_PER_PORT) ? static_cast<uint32_t>(
|
||||
PORT_NUMBER_OFFSET - NUM_PINS_PER_PORT)
|
||||
: 0u);
|
||||
return (adj & 0x1F) << offset;
|
||||
return (adj & SPI_PERIPH_PIN_MASK) << offset;
|
||||
};
|
||||
// periph_cs (which GPIO line for CS signal)
|
||||
periph_ctrl |= get_bitfield(_spi_periph_config[which_periph].periph_cs, 15);
|
||||
periph_ctrl |=
|
||||
get_bitfield(_spi_periph_config[which_periph].periph_cs, SPI_PERIPH_CS_SHIFT);
|
||||
// periph_sdi (which GPIO line for serial data in from peripheral)
|
||||
periph_ctrl |= get_bitfield(_spi_periph_config[which_periph].periph_sdi, 10);
|
||||
periph_ctrl |= get_bitfield(
|
||||
_spi_periph_config[which_periph].periph_sdi, SPI_PERIPH_SDI_SHIFT);
|
||||
// periph_sdo (which GPIO line for serial data out to peripheral)
|
||||
periph_ctrl |= get_bitfield(_spi_periph_config[which_periph].periph_sdo, 5);
|
||||
periph_ctrl |= get_bitfield(
|
||||
_spi_periph_config[which_periph].periph_sdo, SPI_PERIPH_SDO_SHIFT);
|
||||
// periph_clk (which GPIO line for clk signal)
|
||||
periph_ctrl |= get_bitfield(_spi_periph_config[which_periph].periph_clk, 0);
|
||||
periph_ctrl |= get_bitfield(
|
||||
_spi_periph_config[which_periph].periph_clk, SPI_PERIPH_CLK_SHIFT);
|
||||
|
||||
// conditionally send peripheral control
|
||||
if (_periph_ctrl_cache[which_periph] != periph_ctrl) {
|
||||
@ -102,9 +152,11 @@ public:
|
||||
|
||||
uint32_t transaction_config = 0;
|
||||
// SPI chip select
|
||||
transaction_config |= ((which_periph & 0x3) << 16);
|
||||
transaction_config |= (which_periph & SPI_TRANSACTION_PERIPH_MASK)
|
||||
<< SPI_TRANSACTION_PERIPH_SHIFT;
|
||||
// SPI clock divider
|
||||
transaction_config |= ((config.divider & 0xFFFF) << 0);
|
||||
transaction_config |= (divider & SPI_TRANSACTION_DIVIDER_MASK)
|
||||
<< SPI_TRANSACTION_DIVIDER_SHIFT;
|
||||
|
||||
// conditionally send transaction config
|
||||
if (_transaction_cfg_cache != transaction_config) {
|
||||
@ -112,28 +164,41 @@ public:
|
||||
_transaction_cfg_cache = transaction_config;
|
||||
}
|
||||
|
||||
// load data word (in upper bits)
|
||||
const uint32_t data_out = data << (32 - num_bits);
|
||||
|
||||
// send data word
|
||||
_poke32(_spi_transaction_go, data_out);
|
||||
if (num_bits > 32) {
|
||||
_poke64(_spi_transaction_lsb, data << (64 - num_bits));
|
||||
} else {
|
||||
// load data word (in upper bits)
|
||||
const uint32_t data_out =
|
||||
static_cast<uint32_t>((data << (32 - num_bits)) & 0xFFFFFFFF);
|
||||
_poke32(_spi_transaction_go, data_out);
|
||||
}
|
||||
|
||||
// conditional readback
|
||||
if (readback) {
|
||||
uint32_t spi_response = 0;
|
||||
bool spi_ready = false;
|
||||
// Poll the SPI status until we get a SPI Ready flag
|
||||
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
|
||||
while (!spi_ready) {
|
||||
spi_response = _peek32(_spi_status);
|
||||
spi_ready = spi_ready_bit(spi_response);
|
||||
if (spi_timeout(t1, 5)) {
|
||||
throw uhd::io_error(
|
||||
"SPI Read did not receive a SPI Ready within 5 seconds");
|
||||
return 0;
|
||||
}
|
||||
uint64_t response = 0;
|
||||
try {
|
||||
response = _poll32(_spi_status,
|
||||
SPI_STATUS_READY_MASK,
|
||||
SPI_STATUS_READY_MASK,
|
||||
5.0,
|
||||
true)
|
||||
& SPI_STATUS_RESPONSE_MASK;
|
||||
} catch (uhd::op_failed&) {
|
||||
throw uhd::io_error(
|
||||
"SPI Read did not receive a SPI Ready within 5 seconds");
|
||||
return 0;
|
||||
}
|
||||
return (0xFFFFFF & spi_response);
|
||||
|
||||
if (num_bits > 31) {
|
||||
// Read and filter bits 32 to 63 of the response if applicable
|
||||
const size_t msb_bits = std::min(num_bits - 31, size_t(32));
|
||||
const uint64_t msb_mask = (UINT64_C(1) << msb_bits) - 1;
|
||||
response |= (_peek32(_spi_full_response_msb) & msb_mask) << 31;
|
||||
} else {
|
||||
// Filter the response to only include the valid number of bits
|
||||
response &= (UINT64_C(1) << num_bits) - 1;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -141,23 +206,22 @@ public:
|
||||
|
||||
private:
|
||||
poke32_fn_t _poke32;
|
||||
poke64_fn_t _poke64;
|
||||
peek32_fn_t _peek32;
|
||||
poll32_fn_t _poll32;
|
||||
const size_t _spi_periph_cfg;
|
||||
const size_t _spi_transaction_cfg;
|
||||
const size_t _spi_transaction_go;
|
||||
const size_t _spi_transaction_lsb;
|
||||
const size_t _spi_status;
|
||||
const size_t _spi_full_response_msb;
|
||||
const size_t _spi_ctrl_info;
|
||||
std::vector<uint32_t> _periph_ctrl_cache;
|
||||
uint32_t _transaction_cfg_cache = 0;
|
||||
// Initialize with invalid value.
|
||||
uint32_t _transaction_cfg_cache = std::numeric_limits<uint32_t>::max();
|
||||
std::mutex _mutex;
|
||||
std::vector<uhd::features::spi_periph_config_t> _spi_periph_config;
|
||||
|
||||
/*! Gets the SPI_READY flag */
|
||||
bool spi_ready_bit(uint32_t spi_response)
|
||||
{
|
||||
return (spi_response >> 24) & 0x1;
|
||||
}
|
||||
|
||||
/*! Find out if we timed out */
|
||||
bool spi_timeout(std::chrono::steady_clock::time_point start, uint32_t timeout_s)
|
||||
{
|
||||
@ -167,19 +231,27 @@ private:
|
||||
};
|
||||
|
||||
spi_core_4000::sptr spi_core_4000::make(spi_core_4000::poke32_fn_t&& poke32_fn,
|
||||
spi_core_4000::poke64_fn_t&& poke64_fn,
|
||||
spi_core_4000::peek32_fn_t&& peek32_fn,
|
||||
spi_core_4000::poll32_fn_t&& poll32_fn,
|
||||
const size_t spi_periph_cfg,
|
||||
const size_t spi_transaction_cfg,
|
||||
const size_t spi_transaction_go,
|
||||
const size_t spi_transaction_lsb,
|
||||
const size_t spi_status,
|
||||
const size_t spi_full_response_msb,
|
||||
const size_t spi_controller_info)
|
||||
{
|
||||
return std::make_shared<spi_core_4000_impl>(std::move(poke32_fn),
|
||||
std::move(poke64_fn),
|
||||
std::move(peek32_fn),
|
||||
std::move(poll32_fn),
|
||||
spi_periph_cfg,
|
||||
spi_transaction_cfg,
|
||||
spi_transaction_go,
|
||||
spi_transaction_lsb,
|
||||
spi_status,
|
||||
spi_full_response_msb,
|
||||
spi_controller_info);
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ namespace {
|
||||
//! Most pessimistic time for a CHDR query to go to device and back
|
||||
const double MPMD_CHDR_MAX_RTT = 0.02;
|
||||
//! MPM Compatibility number {MAJOR, MINOR}
|
||||
const std::vector<size_t> MPM_COMPAT_NUM = {7, 0};
|
||||
const std::vector<size_t> MPM_COMPAT_NUM = {8, 0};
|
||||
/*! Default number of motherboards to initialize in parallel at once.
|
||||
*
|
||||
* This caps the number of threads spawned during parallel initialization. It
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright 2020 Ettus Research, a National Instruments Brand
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
@ -225,13 +225,32 @@ x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args)
|
||||
[this](const uint32_t addr, const uint32_t data) {
|
||||
regs().poke32(addr, data, get_command_time(0));
|
||||
},
|
||||
[this](const uint32_t addr, const uint64_t data) {
|
||||
regs().poke64(addr, data, get_command_time(0));
|
||||
},
|
||||
[this](const uint32_t addr) {
|
||||
return regs().peek32(addr, get_command_time(0));
|
||||
},
|
||||
[this](const uint32_t addr,
|
||||
const uint32_t data,
|
||||
const uint32_t mask,
|
||||
const double timeout,
|
||||
const bool ack) {
|
||||
return regs()
|
||||
.poll32(addr,
|
||||
data,
|
||||
mask,
|
||||
uhd::time_spec_t(timeout),
|
||||
get_command_time(0),
|
||||
ack)
|
||||
.value();
|
||||
},
|
||||
x400_regs::SPI_PERIPH_CFG,
|
||||
x400_regs::SPI_TRANSACTION_CFG_REG,
|
||||
x400_regs::SPI_TRANSACTION_GO_REG,
|
||||
x400_regs::SPI_TRANSACTION_LSB_REG,
|
||||
x400_regs::SPI_STATUS_REG,
|
||||
x400_regs::SPI_FULL_RESPONSE_MSB_REG,
|
||||
x400_regs::SPI_CONTROLLER_INFO_REG);
|
||||
|
||||
_spi_getter_iface = std::make_shared<x400_spi_getter>(spicore);
|
||||
@ -239,7 +258,7 @@ x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args)
|
||||
} else {
|
||||
UHD_LOG_INFO("x400_radio_control",
|
||||
"SPI functionality not available in this FPGA image. Please update to at "
|
||||
"least version 7.7 to use SPI.");
|
||||
"least version 12.0 to use SPI.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright 2020 Ettus Research, a National Instruments Brand
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
@ -48,14 +48,20 @@ constexpr uint32_t SPI_PERIPH_CFG =
|
||||
//! Base address for SPI_TRANSACTION_CONFIG Register
|
||||
constexpr uint32_t SPI_TRANSACTION_CFG_REG = SPI_PERIPH_CFG + 0x0010;
|
||||
|
||||
//! Base address for SPI_TRANSACTION_LSB Register
|
||||
constexpr uint32_t SPI_TRANSACTION_LSB_REG = SPI_PERIPH_CFG + 0x0014;
|
||||
|
||||
//! Base address for SPI_TRANSACTION_GO Register
|
||||
constexpr uint32_t SPI_TRANSACTION_GO_REG = SPI_PERIPH_CFG + 0x0014;
|
||||
constexpr uint32_t SPI_TRANSACTION_GO_REG = SPI_PERIPH_CFG + 0x0018;
|
||||
|
||||
//! Base address for SPI_STATUS Register
|
||||
constexpr uint32_t SPI_STATUS_REG = SPI_PERIPH_CFG + 0x0018;
|
||||
constexpr uint32_t SPI_STATUS_REG = SPI_PERIPH_CFG + 0x001C;
|
||||
|
||||
//! Base address for SPI_FULL_RESPONSE_MSB Register
|
||||
constexpr uint32_t SPI_FULL_RESPONSE_MSB_REG = SPI_PERIPH_CFG + 0x0020;
|
||||
|
||||
//! Base address for SPI_CONTROLLER_INFO Register
|
||||
constexpr uint32_t SPI_CONTROLLER_INFO_REG = SPI_PERIPH_CFG + 0x001C;
|
||||
constexpr uint32_t SPI_CONTROLLER_INFO_REG = SPI_PERIPH_CFG + 0x0024;
|
||||
|
||||
} // namespace x400_regs
|
||||
|
||||
|
||||
@ -16,11 +16,13 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t SPI_PERIPH_CFG_ADDR = 0x100;
|
||||
constexpr uint32_t SPI_TRANSACTION_CFG_ADDR = 0x200;
|
||||
constexpr uint32_t SPI_TRANSACTION_GO_ADDR = 0x204;
|
||||
constexpr uint32_t SPI_STATUS_ADDR = 0x208;
|
||||
constexpr uint32_t SPI_CTRL_INFO_ADDR = 0x20C;
|
||||
constexpr uint32_t SPI_PERIPH_CFG_ADDR = 0x100;
|
||||
constexpr uint32_t SPI_TRANSACTION_CFG_ADDR = 0x200;
|
||||
constexpr uint32_t SPI_TRANSACTION_LSB_ADDR = 0x204;
|
||||
constexpr uint32_t SPI_TRANSACTION_GO_ADDR = 0x208;
|
||||
constexpr uint32_t SPI_STATUS_ADDR = 0x20C;
|
||||
constexpr uint32_t SPI_FULL_RESPONSE_MSB_ADDR = 0x210;
|
||||
constexpr uint32_t SPI_CTRL_INFO_ADDR = 0x214;
|
||||
|
||||
constexpr uint32_t num_peripherals = 2;
|
||||
|
||||
@ -47,12 +49,12 @@ uint32_t calc_periph_ctrl_expected(const uhd::spi_config_t& config,
|
||||
|
||||
uint32_t periph_ctrl = 0;
|
||||
if (config.mosi_edge == uhd::spi_config_t::EDGE_FALL) {
|
||||
periph_ctrl |= (1u << 27);
|
||||
periph_ctrl |= (1u << 28);
|
||||
}
|
||||
if (config.miso_edge == uhd::spi_config_t::EDGE_RISE) {
|
||||
periph_ctrl |= (1u << 26);
|
||||
periph_ctrl |= (1u << 27);
|
||||
}
|
||||
periph_ctrl |= (static_cast<uint32_t>(num_bits) & 0x3F) << 20;
|
||||
periph_ctrl |= (static_cast<uint32_t>(num_bits) & 0x7F) << 20;
|
||||
periph_ctrl |= get_bitfield(periph_cfg.periph_cs, 15);
|
||||
periph_ctrl |= get_bitfield(periph_cfg.periph_sdi, 10);
|
||||
periph_ctrl |= get_bitfield(periph_cfg.periph_sdo, 5);
|
||||
@ -72,21 +74,37 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_validates_configuration_and_arguments)
|
||||
return num_peripherals;
|
||||
}
|
||||
if (addr == SPI_STATUS_ADDR) {
|
||||
return (1u << 24) | 0x0055AA;
|
||||
return (1u << 31) | 0x0055AA; // SPI_READY + dummy response
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
auto poke64 = [&](uint32_t addr, uint64_t data) {
|
||||
writes.emplace_back(addr, static_cast<uint32_t>(data & 0xFFFFFFFF));
|
||||
writes.emplace_back(addr + 4, static_cast<uint32_t>(data >> 32));
|
||||
};
|
||||
auto poll32 = [&](uint32_t addr, uint32_t data, uint32_t mask, double, bool) {
|
||||
const uint32_t value = peek(addr);
|
||||
if ((value & mask) != data) {
|
||||
throw uhd::op_timeout("SPI status did not match the poll mask");
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
auto spi = uhd::cores::spi_core_4000::make(std::move(poke),
|
||||
std::move(poke64),
|
||||
std::move(peek),
|
||||
std::move(poll32),
|
||||
SPI_PERIPH_CFG_ADDR,
|
||||
SPI_TRANSACTION_CFG_ADDR,
|
||||
SPI_TRANSACTION_GO_ADDR,
|
||||
SPI_TRANSACTION_LSB_ADDR,
|
||||
SPI_STATUS_ADDR,
|
||||
SPI_FULL_RESPONSE_MSB_ADDR,
|
||||
SPI_CTRL_INFO_ADDR);
|
||||
|
||||
uhd::spi_config_t config;
|
||||
|
||||
// Expected to throw because unconfigured peripheral
|
||||
BOOST_CHECK_THROW(spi->transact_spi(0, config, 0, 8, false), uhd::value_error);
|
||||
|
||||
std::vector<uhd::features::spi_periph_config_t> too_many_cfgs = {
|
||||
@ -94,6 +112,7 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_validates_configuration_and_arguments)
|
||||
make_periph_cfg(4, 5, 6, 7),
|
||||
make_periph_cfg(8, 9, 10, 11),
|
||||
};
|
||||
// Expected to throw because more than num_peripherals peripherals
|
||||
BOOST_CHECK_THROW(spi->set_spi_periph_config(too_many_cfgs), uhd::value_error);
|
||||
|
||||
std::vector<uhd::features::spi_periph_config_t> valid_cfgs = {
|
||||
@ -102,8 +121,12 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_validates_configuration_and_arguments)
|
||||
};
|
||||
spi->set_spi_periph_config(valid_cfgs);
|
||||
|
||||
// Expected to throw because num_bits > 64
|
||||
BOOST_CHECK_THROW(spi->transact_spi(0, config, 0, 65, false), uhd::value_error);
|
||||
|
||||
uhd::spi_config_t invalid_divider_config;
|
||||
invalid_divider_config.divider = 0x10000;
|
||||
invalid_divider_config.divider = 0x10000;
|
||||
invalid_divider_config.use_custom_divider = true;
|
||||
BOOST_CHECK_THROW(
|
||||
spi->transact_spi(0, invalid_divider_config, 0x1, 1, false), uhd::value_error);
|
||||
}
|
||||
@ -118,17 +141,32 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_transact_writes_expected_register_values_and_
|
||||
return num_peripherals;
|
||||
}
|
||||
if (addr == SPI_STATUS_ADDR) {
|
||||
return (1u << 24) | 0x00ABCDEF;
|
||||
return (1u << 31) | 0x00ABCDEF; // SPI_READY + dummy response
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
auto poke64 = [&](uint32_t addr, uint64_t data) {
|
||||
writes.emplace_back(addr, static_cast<uint32_t>(data & 0xFFFFFFFF));
|
||||
writes.emplace_back(addr + 4, static_cast<uint32_t>(data >> 32));
|
||||
};
|
||||
auto poll32 = [&](uint32_t addr, uint32_t data, uint32_t mask, double, bool) {
|
||||
const uint32_t value = peek(addr);
|
||||
if ((value & mask) != data) {
|
||||
throw uhd::op_timeout("SPI status did not match the poll mask");
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
auto spi = uhd::cores::spi_core_4000::make(std::move(poke),
|
||||
std::move(poke64),
|
||||
std::move(peek),
|
||||
std::move(poll32),
|
||||
SPI_PERIPH_CFG_ADDR,
|
||||
SPI_TRANSACTION_CFG_ADDR,
|
||||
SPI_TRANSACTION_GO_ADDR,
|
||||
SPI_TRANSACTION_LSB_ADDR,
|
||||
SPI_STATUS_ADDR,
|
||||
SPI_FULL_RESPONSE_MSB_ADDR,
|
||||
SPI_CTRL_INFO_ADDR);
|
||||
|
||||
std::vector<uhd::features::spi_periph_config_t> cfgs = {
|
||||
@ -139,15 +177,16 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_transact_writes_expected_register_values_and_
|
||||
spi->set_spi_periph_config(cfgs);
|
||||
|
||||
uhd::spi_config_t config;
|
||||
config.mosi_edge = uhd::spi_config_t::EDGE_FALL;
|
||||
config.miso_edge = uhd::spi_config_t::EDGE_RISE;
|
||||
config.divider = 0x1234;
|
||||
config.mosi_edge = uhd::spi_config_t::EDGE_FALL;
|
||||
config.miso_edge = uhd::spi_config_t::EDGE_RISE;
|
||||
config.divider = 0x1234;
|
||||
config.use_custom_divider = true;
|
||||
|
||||
const uint32_t tx_data = 0xA5;
|
||||
const size_t tx_bits = 8;
|
||||
|
||||
const uint32_t response = spi->transact_spi(1, config, tx_data, tx_bits, true);
|
||||
BOOST_CHECK_EQUAL(response, 0x00ABCDEF);
|
||||
BOOST_CHECK_EQUAL(response, 0xEF);
|
||||
|
||||
BOOST_REQUIRE_EQUAL(writes.size(), 3);
|
||||
|
||||
@ -166,8 +205,83 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_transact_writes_expected_register_values_and_
|
||||
|
||||
// Same transaction should only issue GO write due to periph/transaction caching.
|
||||
const uint32_t second_response = spi->transact_spi(1, config, tx_data, tx_bits, true);
|
||||
BOOST_CHECK_EQUAL(second_response, 0x00ABCDEF);
|
||||
BOOST_CHECK_EQUAL(second_response, 0xEF);
|
||||
BOOST_REQUIRE_EQUAL(writes.size(), 4);
|
||||
BOOST_CHECK_EQUAL(writes.at(3).first, SPI_TRANSACTION_GO_ADDR);
|
||||
BOOST_CHECK_EQUAL(writes.at(3).second, expected_data_out);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(spi_core_4000_transact_spi64_writes_split_data)
|
||||
{
|
||||
std::vector<reg_write_t> writes;
|
||||
std::vector<uint32_t> reads;
|
||||
|
||||
auto poke = [&](uint32_t addr, uint32_t data) { writes.emplace_back(addr, data); };
|
||||
auto peek = [&](uint32_t addr) -> uint32_t {
|
||||
reads.push_back(addr);
|
||||
if (addr == SPI_CTRL_INFO_ADDR) {
|
||||
return num_peripherals;
|
||||
}
|
||||
if (addr == SPI_STATUS_ADDR) {
|
||||
return (1u << 31) | 0x3456789A; // SPI_READY + response bits 30:0
|
||||
}
|
||||
if (addr == SPI_FULL_RESPONSE_MSB_ADDR) {
|
||||
return 0x24; // Response bits 39:31
|
||||
}
|
||||
throw uhd::value_error("Unexpected 32-bit SPI register read");
|
||||
};
|
||||
auto poke64 = [&](uint32_t addr, uint64_t data) {
|
||||
writes.emplace_back(addr, static_cast<uint32_t>(data & 0xFFFFFFFF));
|
||||
writes.emplace_back(addr + 4, static_cast<uint32_t>(data >> 32));
|
||||
};
|
||||
auto poll32 = [&](uint32_t addr, uint32_t data, uint32_t mask, double, bool) {
|
||||
const uint32_t value = peek(addr);
|
||||
if ((value & mask) != data) {
|
||||
throw uhd::op_timeout("SPI status did not match the poll mask");
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
uhd::spi_iface::sptr spi = uhd::cores::spi_core_4000::make(std::move(poke),
|
||||
std::move(poke64),
|
||||
std::move(peek),
|
||||
std::move(poll32),
|
||||
SPI_PERIPH_CFG_ADDR,
|
||||
SPI_TRANSACTION_CFG_ADDR,
|
||||
SPI_TRANSACTION_GO_ADDR,
|
||||
SPI_TRANSACTION_LSB_ADDR,
|
||||
SPI_STATUS_ADDR,
|
||||
SPI_FULL_RESPONSE_MSB_ADDR,
|
||||
SPI_CTRL_INFO_ADDR);
|
||||
|
||||
auto spi_core = std::dynamic_pointer_cast<uhd::cores::spi_core_4000>(spi);
|
||||
BOOST_REQUIRE(spi_core);
|
||||
const auto periph_cfg = make_periph_cfg(0, 1, 2, 3);
|
||||
spi_core->set_spi_periph_config({periph_cfg});
|
||||
|
||||
uhd::spi_config_t config;
|
||||
// Different divider than previous test -> Expect TRANSACTION_CFG_ADDR
|
||||
config.divider = 1;
|
||||
config.use_custom_divider = true;
|
||||
const uint64_t tx_data = UINT64_C(0x123456789A);
|
||||
const size_t tx_bits = 40;
|
||||
|
||||
const uint64_t response = spi->transact_spi64(0, config, tx_data, tx_bits, true);
|
||||
BOOST_CHECK_EQUAL(response, UINT64_C(0x123456789A));
|
||||
|
||||
const std::vector<uint32_t> expected_reads = {
|
||||
SPI_CTRL_INFO_ADDR, SPI_STATUS_ADDR, SPI_FULL_RESPONSE_MSB_ADDR};
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(
|
||||
reads.begin(), reads.end(), expected_reads.begin(), expected_reads.end());
|
||||
|
||||
BOOST_REQUIRE_EQUAL(writes.size(), 4);
|
||||
BOOST_CHECK_EQUAL(writes.at(0).first, SPI_PERIPH_CFG_ADDR);
|
||||
BOOST_CHECK_EQUAL(
|
||||
writes.at(0).second, calc_periph_ctrl_expected(config, periph_cfg, tx_bits));
|
||||
BOOST_CHECK_EQUAL(writes.at(1).first, SPI_TRANSACTION_CFG_ADDR);
|
||||
BOOST_CHECK_EQUAL(writes.at(1).second, 1u);
|
||||
BOOST_CHECK_EQUAL(writes.at(2).first, SPI_TRANSACTION_LSB_ADDR);
|
||||
BOOST_CHECK_EQUAL(writes.at(2).second, 0x9A000000u);
|
||||
BOOST_CHECK_EQUAL(writes.at(3).first, SPI_TRANSACTION_GO_ADDR);
|
||||
BOOST_CHECK_EQUAL(writes.at(3).second, 0x12345678u);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user