diff options
author | Martin Anderseck <martin.anderseck@ni.com> | 2021-12-15 16:27:23 +0100 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2022-01-13 14:35:55 -0600 |
commit | ee8e4f2c9b1bae8085dff6199c2bade0914748f7 (patch) | |
tree | cf3ad50af9318a1556e4425b9389e760833fbc38 | |
parent | 6bb7d61251abd303049dfd0f47bd0266656797fb (diff) | |
download | uhd-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.dox | 63 | ||||
-rw-r--r-- | host/include/uhd/features/discoverable_feature.hpp | 1 | ||||
-rwxr-xr-x | host/include/uhd/features/spi_getter_iface.hpp | 60 | ||||
-rw-r--r-- | host/lib/include/uhdlib/usrp/cores/gpio_port_mapper.hpp | 26 | ||||
-rw-r--r-- | host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp | 46 | ||||
-rw-r--r-- | host/lib/usrp/cores/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/usrp/cores/spi_core_4000.cpp | 175 | ||||
-rw-r--r-- | host/lib/usrp/x400/x400_gpio_control.cpp | 37 | ||||
-rw-r--r-- | host/lib/usrp/x400/x400_gpio_control.hpp | 17 | ||||
-rw-r--r-- | host/lib/usrp/x400/x400_radio_control.cpp | 30 | ||||
-rw-r--r-- | host/lib/usrp/x400/x400_radio_control.hpp | 49 | ||||
-rw-r--r-- | host/tests/CMakeLists.txt | 1 |
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 |