aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Anderseck <martin.anderseck@ni.com>2021-12-15 16:27:23 +0100
committerAaron Rossetto <aaron.rossetto@ni.com>2022-01-13 14:35:55 -0600
commitee8e4f2c9b1bae8085dff6199c2bade0914748f7 (patch)
treecf3ad50af9318a1556e4425b9389e760833fbc38
parent6bb7d61251abd303049dfd0f47bd0266656797fb (diff)
downloaduhd-ee8e4f2c9b1bae8085dff6199c2bade0914748f7.tar.gz
uhd-ee8e4f2c9b1bae8085dff6199c2bade0914748f7.tar.bz2
uhd-ee8e4f2c9b1bae8085dff6199c2bade0914748f7.zip
SPI: Implement SPI engine for x410
Add SPI Core host implementation for x410 and a discoverable feature to make it accessible.
-rw-r--r--host/docs/x400_gpio_api.dox63
-rw-r--r--host/include/uhd/features/discoverable_feature.hpp1
-rwxr-xr-xhost/include/uhd/features/spi_getter_iface.hpp60
-rw-r--r--host/lib/include/uhdlib/usrp/cores/gpio_port_mapper.hpp26
-rw-r--r--host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp46
-rw-r--r--host/lib/usrp/cores/CMakeLists.txt1
-rw-r--r--host/lib/usrp/cores/spi_core_4000.cpp175
-rw-r--r--host/lib/usrp/x400/x400_gpio_control.cpp37
-rw-r--r--host/lib/usrp/x400/x400_gpio_control.hpp17
-rw-r--r--host/lib/usrp/x400/x400_radio_control.cpp30
-rw-r--r--host/lib/usrp/x400/x400_radio_control.hpp49
-rw-r--r--host/tests/CMakeLists.txt1
12 files changed, 491 insertions, 15 deletions
diff --git a/host/docs/x400_gpio_api.dox b/host/docs/x400_gpio_api.dox
index 80c59cae7..592bff4ba 100644
--- a/host/docs/x400_gpio_api.dox
+++ b/host/docs/x400_gpio_api.dox
@@ -130,5 +130,68 @@ Valid values can be enumerated with the
uhd::features::gpio_power_iface::supported_voltages() call, and are "1V8",
"2V5", and "3V3".
+\section x4x0_spi_iface The x4x0 SPI Mode
+
+The GPIO ports of the x4x0 can be used with the Serial Peripheral Interface (SPI) to control
+external components. To use SPI mode, set the pins you need on the desired GPIO port to be
+controlled by the SPI engine and configure the data direction.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
+auto usrp = uhd::usrp::multi_usrp::make(args);
+auto& spi_getter_iface =
+ usrp->get_radio_control().get_feature<uhd::features::spi_getter_iface>();
+usrp->set_gpio_src("GPIO0",
+ {"DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI",
+ "DB0_SPI"});
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The example shows the usage of GPIO port 0 (GPIO0) for SPI and needs to be run for
+GPIO1 again to use that port with SPI, too.
+
+\subsection x4x0_spi_cfg Configuration of SPI lines
+
+The x4x0 SPI mode supports up to 4 slaves. All of these slaves may have a different SPI pin
+configuration. The pins available for the usage with SPI are listed in \ref x4x0gpio_fpanel_pins.
+For GPIO0 the available pins are enumerated from 0 through 11, for GPIO1 the available pins are
+from 12 through 23.
+The vector of slave configurations is passed to the spi_iface_getter to get the reference:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
+ uhd::features::spi_slave_config_t slave_cfg;
+ slave_cfg.slave_clk = 0;
+ slave_cfg.slave_miso = 1;
+ slave_cfg.slave_mosi = 2;
+ slave_cfg.slave_ss = 3;
+ std::vector<uhd::features::spi_slave_config_t> slave_cfgs;
+ slave_cfgs.push_back(slave_cfg);
+ auto spi_ref = spi_getter_iface.get_spi_ref(slave_cfgs);
+
+ // Set data direction register (set all to outgoing except for MISO)
+ usrp->set_gpio_attr("GPIOA", "DDR", 0xD, 0xF);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+\subsection x4x0_spi_r_w Write and read on SPI
+
+With the SPI reference read and write operations can be performed. For doing this,
+some characteristics of the SPI need to be configured:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
+ uhd::spi_config_t config;
+ config.divider = 4;
+ config.miso_edge = config.EDGE_RISE;
+ ...
+ spi_ref->write_spi(0, config, 0xFEFE, 32);
+ uint32_t read_data = spi_ref->read_spi(0, config, 0xFEFE, 32);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The SPI clock \f$SCLK\f$ is derived from the Radio clock and the SPI clock divider as follows:
+
+\f[SCLK = \frac{Radio\_Clk}{SPI\_CLK\_DIV + 1}\f]
+
*/
// vim:ft=doxygen:
diff --git a/host/include/uhd/features/discoverable_feature.hpp b/host/include/uhd/features/discoverable_feature.hpp
index e56c3f6e3..be01a36c7 100644
--- a/host/include/uhd/features/discoverable_feature.hpp
+++ b/host/include/uhd/features/discoverable_feature.hpp
@@ -36,6 +36,7 @@ public:
REF_CLK_CALIBRATION,
TRIG_IO_MODE,
GPIO_POWER,
+ SPI_GETTER_IFACE
};
virtual ~discoverable_feature() = default;
diff --git a/host/include/uhd/features/spi_getter_iface.hpp b/host/include/uhd/features/spi_getter_iface.hpp
new file mode 100755
index 000000000..1909de095
--- /dev/null
+++ b/host/include/uhd/features/spi_getter_iface.hpp
@@ -0,0 +1,60 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/features/discoverable_feature.hpp>
+#include <uhd/types/serial.hpp>
+#include <memory>
+
+namespace uhd { namespace features {
+
+/*!
+ * The SPI slave configuration struct:
+ * Used to configure the GPIO lines for SPI transactions
+ */
+struct spi_slave_config_t
+{
+ //! Indicates which GPIO line to use for this the CS signal.
+ uint8_t slave_ss;
+
+ //! Indicates which GPIO line to use for this the MISO signal.
+ uint8_t slave_miso;
+
+ //! Indicates which GPIO line to use for this the MOSI signal.
+ uint8_t slave_mosi;
+
+ //! Indicates which GPIO line to use for this the SCLK signal.
+ uint8_t slave_clk;
+};
+
+/*! Interface to provide access to SPI Interface.
+ */
+class spi_getter_iface : public discoverable_feature
+{
+public:
+ using sptr = std::shared_ptr<spi_getter_iface>;
+
+ static discoverable_feature::feature_id_t get_feature_id()
+ {
+ return discoverable_feature::SPI_GETTER_IFACE;
+ }
+
+ std::string get_feature_name() const
+ {
+ return "SPI Getter Interface";
+ }
+
+ virtual ~spi_getter_iface() = default;
+
+ /*! Return the SPI interface to read and write on.
+ * \return SPI interface
+ */
+ virtual uhd::spi_iface::sptr get_spi_ref(
+ const std::vector<uhd::features::spi_slave_config_t>& spi_slave_config) const = 0;
+};
+
+}} // namespace uhd::features
diff --git a/host/lib/include/uhdlib/usrp/cores/gpio_port_mapper.hpp b/host/lib/include/uhdlib/usrp/cores/gpio_port_mapper.hpp
new file mode 100644
index 000000000..e8280e45f
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/cores/gpio_port_mapper.hpp
@@ -0,0 +1,26 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+namespace uhd { namespace mapper {
+
+class gpio_port_mapper
+{
+public:
+ /*! Converts from user-facing GPIO port numbering to internal representation working
+ * on int value. To be used in SPI core.
+ */
+ virtual uint32_t map_value(const uint32_t& value) = 0;
+
+ /*! Converts internal GPIO port representation into user-facing port numbering working
+ * on int value. To be used in SPI core.
+ */
+ virtual uint32_t unmap_value(const uint32_t& value) = 0;
+};
+
+}} // namespace uhd::mapper
+
diff --git a/host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp b/host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp
new file mode 100644
index 000000000..4577b14cf
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp
@@ -0,0 +1,46 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/config.hpp>
+#include <uhd/types/serial.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include <uhd/utils/noncopyable.hpp>
+#include <uhdlib/usrp/cores/gpio_port_mapper.hpp>
+#include <functional>
+#include <memory>
+
+namespace uhd { namespace cores {
+class spi_core_4000 : uhd::noncopyable, public uhd::spi_iface
+{
+public:
+ using sptr = std::shared_ptr<spi_core_4000>;
+ using mapper_sptr = std::shared_ptr<uhd::mapper::gpio_port_mapper>;
+ using poke32_fn_t = std::function<void(uint32_t, uint32_t)>;
+ using peek32_fn_t = std::function<uint32_t(uint32_t)>;
+
+ virtual ~spi_core_4000(void) = default;
+
+ //! makes a new spi core from iface and slave base
+ static sptr make(uhd::wb_iface::sptr iface, const size_t base, const size_t readback);
+
+ //! makes a new spi core from register iface and slave
+ static sptr make(poke32_fn_t&& poke32_fn,
+ peek32_fn_t&& peek_fn,
+ const size_t spi_slave_cfg,
+ const size_t spi_transaction_cfg,
+ const size_t spi_transaction_go,
+ const size_t spi_status,
+ const mapper_sptr port_mapper);
+
+ //! Configures the SPI transaction. The vector index refers to the slave number.
+ virtual void set_spi_slave_config(
+ const std::vector<uhd::features::spi_slave_config_t>& spi_slave_configs) = 0;
+};
+
+}} // namespace uhd::cores
+
diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt
index 1c20ccc47..807eec685 100644
--- a/host/lib/usrp/cores/CMakeLists.txt
+++ b/host/lib/usrp/cores/CMakeLists.txt
@@ -30,6 +30,7 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_3000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/rx_vita_core_3000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spi_core_3000.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/spi_core_4000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/time_core_3000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_3000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tx_frontend_core_200.cpp
diff --git a/host/lib/usrp/cores/spi_core_4000.cpp b/host/lib/usrp/cores/spi_core_4000.cpp
new file mode 100644
index 000000000..1d72dd88d
--- /dev/null
+++ b/host/lib/usrp/cores/spi_core_4000.cpp
@@ -0,0 +1,175 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/exception.hpp>
+#include <uhd/features/spi_getter_iface.hpp>
+#include <uhdlib/usrp/cores/gpio_port_mapper.hpp>
+#include <uhdlib/usrp/cores/spi_core_4000.hpp>
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+
+namespace uhd { namespace cores {
+
+class spi_core_4000_impl : public spi_core_4000
+{
+public:
+ spi_core_4000_impl(poke32_fn_t&& poke32_fn,
+ peek32_fn_t&& peek32_fn,
+ const size_t spi_slave_cfg,
+ const size_t spi_transaction_cfg,
+ const size_t spi_transaction_go,
+ const size_t spi_status,
+ const mapper_sptr port_mapper)
+ : _poke32(std::move(poke32_fn))
+ , _peek32(std::move(peek32_fn))
+ , _spi_slave_cfg(spi_slave_cfg)
+ , _spi_transaction_cfg(spi_transaction_cfg)
+ , _spi_transaction_go(spi_transaction_go)
+ , _spi_status(spi_status)
+ , _port_mapper(port_mapper)
+ {
+ }
+
+ void set_spi_slave_config(
+ const std::vector<uhd::features::spi_slave_config_t>& ssc) override
+ {
+ if (ssc.size() > 4) {
+ throw uhd::value_error(
+ "Passed more than 4 SPI slaves. Maximum number of SPI slaves is 4.");
+ }
+
+ _spi_slave_config = ssc;
+ }
+
+ uint32_t transact_spi(const int which_slave,
+ const spi_config_t& config,
+ const uint32_t data,
+ const size_t num_bits,
+ const bool readback) override
+ {
+ if (static_cast<uint32_t>(which_slave) >= _spi_slave_config.size()) {
+ throw uhd::value_error("No configuration given for requested SPI slave.");
+ }
+ if (config.divider > 0xFFFF) {
+ throw uhd::value_error("Clock divider exceeds maximum value (65535).");
+ }
+ std::lock_guard<std::mutex> lock(_mutex);
+ uint32_t slave_ctrl = 0;
+ if (config.mosi_edge == spi_config_t::EDGE_FALL) {
+ slave_ctrl |= (1 << 27);
+ }
+ if (config.miso_edge == spi_config_t::EDGE_RISE) {
+ slave_ctrl |= (1 << 26);
+ }
+ slave_ctrl |= ((num_bits & 0x3F) << 20);
+ // slave_ss (which GPIO line for CS signal)
+ slave_ctrl |=
+ _port_mapper->map_value(_spi_slave_config[which_slave].slave_ss & 0x1F) << 15;
+ // slave_miso (which GPIO line for MISO signal)
+ slave_ctrl |=
+ _port_mapper->map_value(_spi_slave_config[which_slave].slave_miso & 0x1F)
+ << 10;
+ // slave_mosi (which GPIO line for MOSI signal)
+ slave_ctrl |=
+ _port_mapper->map_value(_spi_slave_config[which_slave].slave_mosi & 0x1F)
+ << 5;
+ // slave_clk (which GPIO line for clk signal)
+ slave_ctrl |=
+ _port_mapper->map_value(_spi_slave_config[which_slave].slave_clk & 0x1F) << 0;
+
+ // conditionally send slave control
+ if (_slave_ctrl_cache[which_slave] != slave_ctrl) {
+ _poke32(_spi_slave_cfg + (which_slave * 0x4), slave_ctrl);
+ _slave_ctrl_cache[which_slave] = slave_ctrl;
+ }
+
+ uint32_t transaction_config = 0;
+ // SPI slave select
+ transaction_config |= ((which_slave & 0x3) << 16);
+ // SPI clock divider
+ transaction_config |= ((config.divider & 0xFFFF) << 0);
+
+ // conditionally send transaction config
+ if (_transaction_cfg_cache != transaction_config) {
+ _poke32(_spi_transaction_cfg, transaction_config);
+ _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);
+
+ // 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;
+ }
+ }
+ return (0xFFFFFF & spi_response);
+ }
+
+ return 0;
+ }
+
+private:
+ poke32_fn_t _poke32;
+ peek32_fn_t _peek32;
+ const size_t _spi_slave_cfg;
+ const size_t _spi_transaction_cfg;
+ const size_t _spi_transaction_go;
+ const size_t _spi_status;
+ const mapper_sptr _port_mapper;
+ std::vector<uint32_t> _slave_ctrl_cache{0, 0, 0, 0};
+ uint32_t _transaction_cfg_cache = 0;
+ std::mutex _mutex;
+ std::vector<uhd::features::spi_slave_config_t> _spi_slave_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)
+ {
+ using namespace std::chrono;
+ return (duration_cast<seconds>(steady_clock::now() - start)).count() > timeout_s;
+ }
+};
+
+spi_core_4000::sptr spi_core_4000::make(spi_core_4000::poke32_fn_t&& poke32_fn,
+ spi_core_4000::peek32_fn_t&& peek32_fn,
+ const size_t spi_slave_cfg,
+ const size_t spi_transaction_cfg,
+ const size_t spi_transaction_go,
+ const size_t spi_status,
+ const mapper_sptr port_mapper)
+{
+ return std::make_shared<spi_core_4000_impl>(std::move(poke32_fn),
+ std::move(peek32_fn),
+ spi_slave_cfg,
+ spi_transaction_cfg,
+ spi_transaction_go,
+ spi_status,
+ port_mapper);
+}
+
+}} // namespace uhd::cores
diff --git a/host/lib/usrp/x400/x400_gpio_control.cpp b/host/lib/usrp/x400/x400_gpio_control.cpp
index 3213f6e64..18599eb88 100644
--- a/host/lib/usrp/x400/x400_gpio_control.cpp
+++ b/host/lib/usrp/x400/x400_gpio_control.cpp
@@ -6,6 +6,7 @@
#include "x400_gpio_control.hpp"
+
using namespace uhd::rfnoc::x400;
namespace {
@@ -33,6 +34,9 @@ constexpr uint32_t DIO_DIRECTION_REG = 0x4;
// There are two ports, each with 12 pins
constexpr size_t NUM_PINS_PER_PORT = 12;
+// Start of Port B pin numbers relative to Port A:
+constexpr size_t PORT_NUMBER_OFFSET = 16;
+
// These values should match the values in MPM's x4xx_periphs.py "DIO_PORT_MAP"
constexpr uint32_t PORTA_MAPPING[12] = {1, 0, 2, 3, 5, 4, 6, 7, 9, 8, 10, 11};
constexpr uint32_t PORTB_MAPPING[12] = {10, 11, 9, 8, 6, 7, 5, 4, 2, 3, 1, 0};
@@ -40,7 +44,8 @@ constexpr uint32_t PORTB_MAPPING[12] = {10, 11, 9, 8, 6, 7, 5, 4, 2, 3, 1, 0};
const char* uhd::rfnoc::x400::GPIO_BANK_NAME = "GPIO";
-gpio_control::gpio_control(uhd::usrp::x400_rpc_iface::sptr rpcc, uhd::wb_iface::sptr iface)
+gpio_control::gpio_control(
+ uhd::usrp::x400_rpc_iface::sptr rpcc, uhd::wb_iface::sptr iface)
: _rpcc(rpcc), _regs(iface)
{
_rpcc->dio_set_port_mapping("DIO");
@@ -87,8 +92,10 @@ void gpio_control::set_gpio_attr(
bool gpio_control::is_atr_attr(const uhd::usrp::gpio_atr::gpio_attr_t attr)
{
- return attr == uhd::usrp::gpio_atr::GPIO_ATR_0X || attr == uhd::usrp::gpio_atr::GPIO_ATR_RX
- || attr == uhd::usrp::gpio_atr::GPIO_ATR_TX || attr == uhd::usrp::gpio_atr::GPIO_ATR_XX;
+ return attr == uhd::usrp::gpio_atr::GPIO_ATR_0X
+ || attr == uhd::usrp::gpio_atr::GPIO_ATR_RX
+ || attr == uhd::usrp::gpio_atr::GPIO_ATR_TX
+ || attr == uhd::usrp::gpio_atr::GPIO_ATR_XX;
}
uint32_t gpio_control::internalize_value(const uint32_t value)
@@ -125,3 +132,27 @@ uint32_t gpio_control::get_gpio_attr(const uhd::usrp::gpio_atr::gpio_attr_t attr
return publicize_value(_gpios[0]->get_attr_reg(attr));
}
+
+uint32_t uhd::rfnoc::x400::x400_gpio_port_mapping::map_value(const uint32_t& value)
+{
+ const uint32_t bank = value >= NUM_PINS_PER_PORT ? 1 : 0;
+ uint32_t pin_intern = value % NUM_PINS_PER_PORT;
+ const uint32_t* const mapping = bank == 1 ? PORTB_MAPPING : PORTA_MAPPING;
+ for (size_t i = 0; i < NUM_PINS_PER_PORT; i++) {
+ if (mapping[i] == pin_intern) {
+ return i + (bank * PORT_NUMBER_OFFSET);
+ }
+ }
+ throw uhd::lookup_error(
+ "Could not find corresponding GPIO pin number for given SPI pin " + value);
+ return 0;
+}
+
+uint32_t uhd::rfnoc::x400::x400_gpio_port_mapping::unmap_value(const uint32_t& value)
+{
+ const uint32_t bank = value >= PORT_NUMBER_OFFSET ? 1 : 0;
+ uint32_t pin_number = value % PORT_NUMBER_OFFSET;
+ const uint32_t* const mapping = bank == 1 ? PORTB_MAPPING : PORTA_MAPPING;
+ UHD_ASSERT_THROW(pin_number < NUM_PINS_PER_PORT);
+ return mapping[pin_number] + (bank * NUM_PINS_PER_PORT);
+} \ No newline at end of file
diff --git a/host/lib/usrp/x400/x400_gpio_control.hpp b/host/lib/usrp/x400/x400_gpio_control.hpp
index a0c0593ec..01cfc134e 100644
--- a/host/lib/usrp/x400/x400_gpio_control.hpp
+++ b/host/lib/usrp/x400/x400_gpio_control.hpp
@@ -6,8 +6,9 @@
#pragma once
-#include <uhdlib/usrp/cores/gpio_atr_3000.hpp>
#include <uhdlib/usrp/common/rpc.hpp>
+#include <uhdlib/usrp/cores/gpio_atr_3000.hpp>
+#include <uhdlib/usrp/cores/gpio_port_mapper.hpp>
#include <vector>
namespace uhd { namespace rfnoc { namespace x400 {
@@ -32,7 +33,8 @@ extern const char* GPIO_BANK_NAME;
* internal radio control registers, as well as in MPM to configure the DIO
* board.
*/
-class gpio_control {
+class gpio_control
+{
public:
using sptr = std::shared_ptr<gpio_control>;
@@ -79,4 +81,13 @@ private:
std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _gpios;
};
-}}} // namespace uhd::rfnoc::x400
+class x400_gpio_port_mapping : public uhd::mapper::gpio_port_mapper
+{
+public:
+ x400_gpio_port_mapping(){};
+
+ uint32_t map_value(const uint32_t& value) override;
+
+ uint32_t unmap_value(const uint32_t& value) override;
+};
+}}} // namespace uhd::rfnoc::x400 \ No newline at end of file
diff --git a/host/lib/usrp/x400/x400_radio_control.cpp b/host/lib/usrp/x400/x400_radio_control.cpp
index a58f522bf..75719f6a2 100644
--- a/host/lib/usrp/x400/x400_radio_control.cpp
+++ b/host/lib/usrp/x400/x400_radio_control.cpp
@@ -5,14 +5,17 @@
//
#include "x400_radio_control.hpp"
+#include "x400_gpio_control.hpp"
#include <uhd/rfnoc/registry.hpp>
+#include <uhd/types/serial.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/utils/math.hpp>
+#include <uhdlib/rfnoc/reg_iface_adapter.hpp>
#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <uhdlib/usrp/cores/spi_core_4000.hpp>
#include <uhdlib/usrp/dboard/debug_dboard.hpp>
#include <uhdlib/usrp/dboard/null_dboard.hpp>
#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp>
-#include <uhdlib/rfnoc/reg_iface_adapter.hpp>
namespace uhd { namespace rfnoc {
@@ -182,8 +185,25 @@ x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args)
auto mpm_rpc = _mb_control->dynamic_cast_rpc_as<uhd::usrp::mpmd_rpc_iface>();
if (mpm_rpc->get_gpio_banks().size() > 0) {
- _gpios = std::make_shared<x400::gpio_control>(_rpcc,
- RFNOC_MAKE_WB_IFACE(regmap::PERIPH_BASE + 0xC000, 0));
+ _gpios = std::make_shared<x400::gpio_control>(
+ _rpcc, RFNOC_MAKE_WB_IFACE(regmap::PERIPH_BASE + 0xC000, 0));
+
+ auto gpio_port_mapper = std::shared_ptr<uhd::mapper::gpio_port_mapper>(
+ new uhd::rfnoc::x400::x400_gpio_port_mapping);
+ auto spicore = uhd::cores::spi_core_4000::make(
+ [this](const uint32_t addr, const uint32_t data) {
+ regs().poke32(addr, data, get_command_time(0));
+ },
+ [this](
+ const uint32_t addr) { return regs().peek32(addr, get_command_time(0)); },
+ x400_regs::SPI_SLAVE_CFG,
+ x400_regs::SPI_TRANSACTION_CFG_REG,
+ x400_regs::SPI_TRANSACTION_GO_REG,
+ x400_regs::SPI_STATUS_REG,
+ gpio_port_mapper);
+
+ _spi_getter_iface = std::make_shared<x400_spi_getter>(spicore);
+ register_feature(_spi_getter_iface);
}
}
@@ -328,8 +348,8 @@ double x400_radio_control_impl::set_rate(const double rate)
// X400 does not support runtime rate changes
if (!uhd::math::frequencies_are_equal(rate, get_rate())) {
RFNOC_LOG_WARNING("Requesting invalid sampling rate from device: "
- << (rate / 1e6) << " MHz. Actual rate is: "
- << (get_rate() / 1e6) << " MHz.");
+ << (rate / 1e6)
+ << " MHz. Actual rate is: " << (get_rate() / 1e6) << " MHz.");
}
return get_rate();
}
diff --git a/host/lib/usrp/x400/x400_radio_control.hpp b/host/lib/usrp/x400/x400_radio_control.hpp
index 8848926c7..cd45291bc 100644
--- a/host/lib/usrp/x400/x400_radio_control.hpp
+++ b/host/lib/usrp/x400/x400_radio_control.hpp
@@ -7,24 +7,28 @@
#pragma once
#include "adc_self_calibration.hpp"
+#include "x400_gpio_control.hpp"
+#include <uhd/features/spi_getter_iface.hpp>
#include <uhd/rfnoc/noc_block_base.hpp>
#include <uhd/types/device_addr.hpp>
#include <uhd/types/direction.hpp>
#include <uhd/types/ranges.hpp>
#include <uhdlib/features/fpga_load_notification_iface.hpp>
#include <uhdlib/rfnoc/radio_control_impl.hpp>
+#include <uhdlib/rfnoc/reg_iface_adapter.hpp>
#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp>
#include <uhdlib/usrp/common/mpmd_mb_controller.hpp>
#include <uhdlib/usrp/common/rpc.hpp>
#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <uhdlib/usrp/cores/gpio_atr_3000.hpp>
+#include <uhdlib/usrp/cores/spi_core_4000.hpp>
#include <uhdlib/utils/rpc.hpp>
#include <stddef.h>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
-#include <uhdlib/usrp/cores/gpio_atr_3000.hpp>
-#include "x400_gpio_control.hpp"
+
namespace uhd { namespace rfnoc {
@@ -34,6 +38,22 @@ namespace x400_regs {
// other things in the RFDC.
constexpr uint32_t RFDC_CTRL_BASE = radio_control_impl::regmap::PERIPH_BASE + 0x8000;
+constexpr uint32_t DIO_REGMAP_OFFSET = 0x2000;
+constexpr uint32_t DIO_WINDOW_OFFSET = 0xC000;
+
+// SPI control registers
+constexpr uint32_t SPI_SLAVE_CFG =
+ DIO_REGMAP_OFFSET + DIO_WINDOW_OFFSET + radio_control_impl::regmap::PERIPH_BASE;
+
+//! Base address for SPI_TRANSACTION_CONFIG Register
+constexpr uint32_t SPI_TRANSACTION_CFG_REG = SPI_SLAVE_CFG + 0x0010;
+
+//! Base address for SPI_TRANSACTION_GO Register
+constexpr uint32_t SPI_TRANSACTION_GO_REG = SPI_SLAVE_CFG + 0x0014;
+
+//! Base address for SPI_STATUS Register
+constexpr uint32_t SPI_STATUS_REG = SPI_SLAVE_CFG + 0x0018;
+
} // namespace x400_regs
class x400_radio_control_impl : public radio_control_impl
@@ -116,8 +136,7 @@ public:
const std::string& name, const size_t chan) override;
void set_rx_lo_export_enabled(
bool enabled, const std::string& name, const size_t chan) override;
- bool get_rx_lo_export_enabled(
- const std::string& name, const size_t chan) override;
+ bool get_rx_lo_export_enabled(const std::string& name, const size_t chan) override;
double set_rx_lo_freq(
double freq, const std::string& name, const size_t chan) override;
double get_rx_lo_freq(const std::string& name, const size_t chan) override;
@@ -179,6 +198,8 @@ private:
uhd::features::adc_self_calibration_iface::sptr _adc_self_calibration;
+ uhd::features::spi_getter_iface::sptr _spi_getter_iface;
+
x400::gpio_control::sptr _gpios;
class fpga_onload : public uhd::features::fpga_load_notification_iface
@@ -203,6 +224,26 @@ private:
};
fpga_onload::sptr _fpga_onload;
+
+ class x400_spi_getter : public uhd::features::spi_getter_iface
+ {
+ public:
+ using sptr = std::shared_ptr<spi_getter_iface>;
+
+ x400_spi_getter(uhd::cores::spi_core_4000::sptr _spi) : _spicore(_spi) {}
+
+ uhd::spi_iface::sptr get_spi_ref(
+ const std::vector<uhd::features::spi_slave_config_t>& spi_slave_config)
+ const override
+ {
+ _spicore->set_spi_slave_config(spi_slave_config);
+ return _spicore;
+ }
+
+ private:
+ uhd::cores::spi_core_4000::sptr _spicore;
+ };
};
+
}} // namespace uhd::rfnoc
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index 758d2bbf0..7857c00d5 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -387,6 +387,7 @@ IF(ENABLE_X400)
${UHD_SOURCE_DIR}/lib/utils/compat_check.cpp
${UHD_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/gpio_atr_3000.cpp
+ ${UHD_SOURCE_DIR}/lib/usrp/cores/spi_core_4000.cpp
$<TARGET_OBJECTS:uhd_rpclib>
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/rpclib/include
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/flatbuffers/include