diff options
author | Brent Stapleton <brent.stapleton@ettus.com> | 2018-04-17 19:33:53 -0700 |
---|---|---|
committer | Brent Stapleton <bstapleton@g.hmc.edu> | 2018-07-18 15:37:27 -0700 |
commit | 7d28d9140e2c57db36b37d60d9ce4bff86357bac (patch) | |
tree | b775d13d8b0386bea39adaf279511e24c51f35f6 | |
parent | 300a5e3f6e5e845b4b8d093222e1c51ca4640f79 (diff) | |
download | uhd-7d28d9140e2c57db36b37d60d9ce4bff86357bac.tar.gz uhd-7d28d9140e2c57db36b37d60d9ce4bff86357bac.tar.bz2 uhd-7d28d9140e2c57db36b37d60d9ce4bff86357bac.zip |
uhd: initial commit of UHD support for E320
Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com>
-rw-r--r-- | host/include/uhd/rfnoc/blocks/radio_neon.xml | 53 | ||||
-rw-r--r-- | host/lib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/usrp/common/CMakeLists.txt | 4 | ||||
-rw-r--r-- | host/lib/usrp/common/ad9361_ctrl.cpp | 8 | ||||
-rw-r--r-- | host/lib/usrp/dboard/CMakeLists.txt | 2 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/CMakeLists.txt | 15 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp | 162 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp | 26 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/neon_bands.cpp | 157 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/neon_constants.hpp | 51 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp | 621 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp | 311 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp | 495 | ||||
-rw-r--r-- | host/lib/usrp/dboard/neon/neon_regs.hpp | 26 | ||||
-rw-r--r-- | host/lib/usrp/mpmd/mpmd_devices.hpp | 3 | ||||
-rw-r--r-- | host/lib/usrp/mpmd/mpmd_image_loader.cpp | 5 |
16 files changed, 1928 insertions, 12 deletions
diff --git a/host/include/uhd/rfnoc/blocks/radio_neon.xml b/host/include/uhd/rfnoc/blocks/radio_neon.xml new file mode 100644 index 000000000..01f286324 --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/radio_neon.xml @@ -0,0 +1,53 @@ +<!--This defines one NoC-Block.--> +<nocblock> + <name>Radio (Neon)</name> + <blockname>Radio</blockname> + <key>NeonRadio</key> + <!--There can be several of these:--> + <ids> + <id revision="0">12AD100000003320</id> + </ids> + <!-- Registers --> + <registers> + <!--<setreg>--> + <!--<name>FFT_RESET</name>--> + <!--<address>131</address>--> + <!--</setreg>--> + <!--<readback>--> + <!--<name>RB_MAGNITUDE_OUT</name>--> + <!--<address>1</address>--> + <!--</readback>--> + </registers> + <!-- Args --> + <args> + <arg> + <name>spp</name> + <type>int</type> + <value>364</value> + </arg> + <arg> + <name>gain_mode</name> + <type>string</type> + <value>auto</value> + </arg> + </args> + <ports> + <sink> + <name>in0</name> + <type>sc16</type> + </sink> + <source> + <name>out0</name> + <type>sc16</type> + </source> + <sink> + <name>in1</name> + <type>sc16</type> + </sink> + <source> + <name>out1</name> + <type>sc16</type> + </source> + </ports> +</nocblock> + diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index 984f16f96..cbc022533 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -75,6 +75,7 @@ LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF) LIBUHD_REGISTER_COMPONENT("N230" ENABLE_N230 ON "ENABLE_LIBUHD" OFF OFF) LIBUHD_REGISTER_COMPONENT("MPMD" ENABLE_MPMD ON "ENABLE_LIBUHD" OFF OFF) LIBUHD_REGISTER_COMPONENT("N300" ENABLE_N300 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("E320" ENABLE_E320 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF) LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF) ######################################################################## diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 082a047e7..feb4390e3 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -14,14 +14,14 @@ IF(ENABLE_USB) ) ENDIF(ENABLE_USB) -IF(ENABLE_E300 OR ENABLE_B200 OR ENABLE_N230) +IF(ENABLE_E300 OR ENABLE_B200 OR ENABLE_N230 OR ENABLE_E320) INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver") LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp ) -ENDIF(ENABLE_E300 OR ENABLE_B200 OR ENABLE_N230) +ENDIF(ENABLE_E300 OR ENABLE_B200 OR ENABLE_N230 OR ENABLE_E320) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/host/lib/usrp/common/ad9361_ctrl.cpp b/host/lib/usrp/common/ad9361_ctrl.cpp index 3cf89ad7a..003711d52 100644 --- a/host/lib/usrp/common/ad9361_ctrl.cpp +++ b/host/lib/usrp/common/ad9361_ctrl.cpp @@ -212,14 +212,6 @@ public: _device.set_dc_offset_auto(direction,on); } - std::string get_lo_source(const std::string &which) - { - const auto dir = _get_direction_from_antenna(which); - //TODO: Implement this - boost::lock_guard<boost::mutex> lock(_mutex); - return "internal"; - } - void set_iq_balance_auto(const std::string &which, const bool on) { boost::lock_guard<boost::mutex> lock(_mutex); diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 83bb0a96c..8ebe8e642 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -52,3 +52,5 @@ ENDIF(ENABLE_N300) IF(ENABLE_MPMD AND ENABLE_EISCAT) INCLUDE_SUBDIRECTORY(eiscat) ENDIF(ENABLE_MPMD AND ENABLE_EISCAT) + +INCLUDE_SUBDIRECTORY(neon) diff --git a/host/lib/usrp/dboard/neon/CMakeLists.txt b/host/lib/usrp/dboard/neon/CMakeLists.txt new file mode 100644 index 000000000..e1ee0fba4 --- /dev/null +++ b/host/lib/usrp/dboard/neon/CMakeLists.txt @@ -0,0 +1,15 @@ +# +# Copyright 2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +IF(ENABLE_E320) + LIST(APPEND NEON_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/neon_radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/neon_radio_ctrl_init.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/neon_ad9361_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/neon_bands.cpp + ) + LIBUHD_APPEND_SOURCES(${NEON_SOURCES}) +ENDIF(ENABLE_E320) diff --git a/host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp b/host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp new file mode 100644 index 000000000..e19f890ca --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp @@ -0,0 +1,162 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "neon_ad9361_iface.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <boost/format.hpp> + +using namespace uhd; + +class neon_ad9361_iface : public ad9361_ctrl +{ +public: + neon_ad9361_iface( + rpc_client::sptr rpcc + ) : _rpcc(rpcc) + , _rpc_prefix("db_0_") + , _log_prefix("AD9361") + { + UHD_LOG_TRACE(_log_prefix, + "Initialized controls with RPC prefix " << _rpc_prefix); + } + + double set_bw_filter(const std::string &which, const double bw) + { + return _rpcc->request_with_token<double>( + this->_rpc_prefix + "set_bw_filter", which, bw); + } + + double set_gain(const std::string &which, const double value) + { + return _rpcc->request_with_token<double>( + this->_rpc_prefix + "set_gain", which, value); + } + + void set_agc(const std::string &which, bool enable) + { + _rpcc->request_with_token<void>( + this->_rpc_prefix + "set_agc", which, enable); + } + + void set_agc_mode(const std::string &which, const std::string &mode) + { + _rpcc->request_with_token<void>( + this->_rpc_prefix + "set_agc_mode", which, mode); + } + + double set_clock_rate(const double rate) + { + return _rpcc->request_with_token<double>( + this->_rpc_prefix + "set_clock_rate", rate); + } + + void set_active_chains(bool tx1, bool tx2, bool rx1, bool rx2) + { + _rpcc->request_with_token<void>( + this->_rpc_prefix + "set_active_chains", tx1, tx2, rx1, rx2); + } + + double tune(const std::string &which, const double value) + { + return _rpcc->request_with_token<double>( + this->_rpc_prefix + "tune", which, value); + } + + void set_dc_offset_auto(const std::string &which, const bool on) + { + _rpcc->request_with_token<void>( + this->_rpc_prefix + "set_dc_offset_auto", which, on); + } + + void set_timing_mode(const std::string &timing_mode) + { + _rpcc->request_with_token<void>( + this->_rpc_prefix + "set_timing_mode", timing_mode); + } + + void set_iq_balance_auto(const std::string &which, const bool on) + { + _rpcc->request_with_token<void>( + this->_rpc_prefix + "set_iq_balance_auto", which, on); + } + + double get_freq(const std::string &which) + { + return _rpcc->request_with_token<double>( + this->_rpc_prefix + "get_freq", which); + } + + void data_port_loopback(const bool on) + { + _rpcc->request_with_token<void>( + this->_rpc_prefix + "data_port_loopback", on); + } + + sensor_value_t get_rssi(const std::string &which) + { + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + this->_rpc_prefix + "get_rssi", which)); + } + + sensor_value_t get_temperature() + { + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + this->_rpc_prefix + "get_temperature")); + } + + std::vector<std::string> get_filter_names(const std::string &which) + { + return _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_filter_names", which); + } + + filter_info_base::sptr get_filter(const std::string &/*which*/, + const std::string &/*filter_name*/) + { + throw uhd::runtime_error("ad9361_ctrl::get_filter is not supported over an RPC connection"); + } + + void set_filter(const std::string &/*which*/, + const std::string &/*filter_name*/, + const filter_info_base::sptr /*filter*/) + { + throw uhd::runtime_error("ad9361_ctrl::set_filter is not supported over an RPC connection"); + } + + void output_digital_test_tone(bool enb) + { + _rpcc->request_with_token<void>( + this->_rpc_prefix + "output_digital_test_tone", enb); + } + +private: + //! Reference to the RPC client + rpc_client::sptr _rpcc; + + //! Stores the prefix to RPC calls + const std::string _rpc_prefix; + + //! Logger prefix + const std::string _log_prefix; +}; + +//! Factory function for Neon's AD9361 RPC Controller +ad9361_ctrl::sptr make_rpc(rpc_client::sptr rpcc) +{ + return ad9361_ctrl::sptr(new neon_ad9361_iface(rpcc)); +} + +/*! Helper function to convert direction and channel to the 'which' required by most Catalina + driver functions */ +std::string get_which_ad9361_chain(const direction_t dir, const size_t chan) +{ + UHD_ASSERT_THROW(dir == RX_DIRECTION or dir == TX_DIRECTION); + UHD_ASSERT_THROW(chan == 0 or chan == 1); + return str(boost::format("%s%d") + % (dir == RX_DIRECTION ? "RX" : "TX") + % (chan + 1)); +} diff --git a/host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp b/host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp new file mode 100644 index 000000000..1f055aca4 --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp @@ -0,0 +1,26 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_NEON_AD9361_IFACE_HPP +#define INCLUDED_LIBUHD_RFNOC_NEON_AD9361_IFACE_HPP + +#include <uhd/types/direction.hpp> +#include <uhd/types/filters.hpp> +#include <uhd/types/sensors.hpp> +#include <uhdlib/usrp/common/ad9361_ctrl.hpp> +#include <uhdlib/utils/rpc.hpp> +#include <memory> +#include <string> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; + +ad9361_ctrl::sptr make_rpc(rpc_client::sptr rpcc); +std::string get_which_ad9361_chain(const direction_t dir, const size_t chan); + +#endif /* INCLUDED_LIBUHD_RFNOC_NEON_AD9361_IFACE_HPP */ +// vim: sw=4 et: diff --git a/host/lib/usrp/dboard/neon/neon_bands.cpp b/host/lib/usrp/dboard/neon/neon_bands.cpp new file mode 100644 index 000000000..3b0ab2ca1 --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_bands.cpp @@ -0,0 +1,157 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "neon_radio_ctrl_impl.hpp" +#include "neon_constants.hpp" +#include <uhd/utils/math.hpp> + +/* + * Neon frequency bands: + * + * For RX: + * Band Freq BSEL-210 BSEL-543 TX/RX RX2 + * SEL_10 | FE_SEL_210 SEL_10 + * chan 1 | chan 2 + * ------------------------------------------------------------------------- + * LB_B2: < 450 RF5 100 RF6 101 01 RF3 100 RF1 001 10 + * LB_B3: 450-700 RF6 101 RF5 100 01 RF3 100 RF1 001 10 + * LB_B4: 700-1200 RF3 010 RF4 011 01 RF3 100 RF1 001 10 + * LB_B5: 1200-1800 RF4 011 RF3 010 01 RF3 100 RF1 001 10 + * LB_B6: 1800-2350 RF1 000 RF2 001 01 RF3 100 RF1 001 10 + * LB_B7: 2350-2600 RF2 001 RF1 000 01 RF3 100 RF1 001 10 + * HB: 2600+ --- 111 --- 111 10 RF3 100 RF1 001 01 + * + * SEL_1 SEL_0 + * 1 0 ANT1(RX_HB)--TX/RX, ANT2(RX_LB)--RX + * 0 1 ANT1(RX_HB)--RX, ANT2(RX_LB)--TX/RX + * + * For TX: + * Band-Freq BSEL-210 BSEL-543 TX/RX + * FE_SEL_210 + * chan 1 | chan 2 + * ------------------------------------------------------------ + * LB_80 < 117.7 RF7 011 RF8 111 RF1 001 RF3 100 + * LB_160 117.7-178.2 RF8 111 RF7 011 RF1 001 RF3 100 + * LB_225 178.2-284.3 RF5 001 RF6 101 RF1 001 RF3 100 + * LB_400 284.3-453.7 RF6 101 RF5 001 RF1 001 RF3 100 + * LB_575 453.7-723.8 RF3 010 RF4 110 RF1 001 RF3 100 + * LB_1000 723.8-1154.9 RF4 110 RF3 010 RF1 001 RF3 100 + * LB_1700 1154.9-1842.6 RF1 000 RF2 100 RF1 001 RF3 100 + * LB_2750 1842.6-2940.0 RF2 100 RF1 000 RF1 001 RF3 100 + * HB_5850 > 2490.0 --- --- --- --- RF2 010 RF2 010 + * + */ + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +namespace { + /* Note on the RX filter bank: + * + * The RX path has 7 bands, which we call LB_B2, B3, .. HB same as + * the schematic. + * + * The following constants define lower cutoff frequencies for each band. + * LB_B2 does not have a lower cutoff frequency, it is implied by + * AD9361_MIN_FREQ. NEON_RX_BAND1_MIN_FREQ is the cutover frequency + * for switching from LB_B2 to LB_B3, and so on. + * + * Bands 1-6 have both high- and low-pass filters (effectively band + * passes). Frequencies need to be chosen to allow as much of the full + * bandwidth through unattenuated. + */ + constexpr double NEON_RX_LB_BAND3_MIN_FREQ = 450e6; + constexpr double NEON_RX_LB_BAND4_MIN_FREQ = 700e6; + constexpr double NEON_RX_LB_BAND5_MIN_FREQ = 1200e6; + constexpr double NEON_RX_LB_BAND6_MIN_FREQ = 1800e6; + constexpr double NEON_RX_LB_BAND7_MIN_FREQ = 2350e6; + constexpr double NEON_RX_HB_MIN_FREQ = 2600e6; + + /* Note on the TX filter bank: + * + * The TX path has 9 bands, which we name according to the schematic. + * + * The following constants define lower cutoff frequencies for each band. + * LB_80 does not have a lower cutoff frequency, it is implied by + * AD9361_MIN_FREQ. NEON_TX_LB_160_MIN_FREQ is the cutover frequency + * for switching from LB_80 to LB_160, and so on. + * + * On current Neon revisions, all filters on the TX filter bank are + * low pass filters (no high pass filters). + * Frequencies need to be chosen to allow as much of the full bandwidth + * through unattenuated (so don't go all the way up to the cutoff frequency + * of that filter). + */ + constexpr double NEON_TX_LB_160_MIN_FREQ = 117.7e6; + constexpr double NEON_TX_LB_225_MIN_FREQ = 178.2e6; + constexpr double NEON_TX_LB_400_MIN_FREQ = 284.3e6; + constexpr double NEON_TX_LB_575_MIN_FREQ = 453.7e6; + constexpr double NEON_TX_LB_1000_MIN_FREQ = 723.8e6; + constexpr double NEON_TX_LB_1700_MIN_FREQ = 1154.9e6; + constexpr double NEON_TX_LB_2750_MIN_FREQ = 1842.6e6; + constexpr double NEON_TX_HB_MIN_FREQ = 2940.0e6; +} + +neon_radio_ctrl_impl::rx_band +neon_radio_ctrl_impl::_map_freq_to_rx_band(const double freq) { + neon_radio_ctrl_impl::rx_band band; + + if (fp_compare_epsilon<double>(freq) < AD9361_MIN_FREQ) { + band = rx_band::INVALID_BAND; + } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND3_MIN_FREQ) { + band = rx_band::LB_B2; + } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND4_MIN_FREQ) { + band = rx_band::LB_B3; + } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND5_MIN_FREQ) { + band = rx_band::LB_B4; + } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND6_MIN_FREQ) { + band = rx_band::LB_B5; + } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND7_MIN_FREQ) { + band = rx_band::LB_B6; + } else if (fp_compare_epsilon<double>(freq) < NEON_RX_HB_MIN_FREQ) { + band = rx_band::LB_B7; + } else if (fp_compare_epsilon<double>(freq) <= AD9361_MAX_FREQ) { + band = rx_band::HB; + } else { + band = rx_band::INVALID_BAND; + } + + return band; +} + +neon_radio_ctrl_impl::tx_band +neon_radio_ctrl_impl::_map_freq_to_tx_band(const double freq) { + neon_radio_ctrl_impl::tx_band band; + + if (fp_compare_epsilon<double>(freq) < AD9361_MIN_FREQ) { + band = tx_band::INVALID_BAND; + } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_160_MIN_FREQ) { + band = tx_band::LB_80; + } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_225_MIN_FREQ) { + band = tx_band::LB_160; + } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_400_MIN_FREQ) { + band = tx_band::LB_225; + } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_575_MIN_FREQ) { + band = tx_band::LB_400; + } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_1000_MIN_FREQ) { + band = tx_band::LB_575; + } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_1700_MIN_FREQ) { + band = tx_band::LB_1000; + } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_2750_MIN_FREQ) { + band = tx_band::LB_1700; + } else if (fp_compare_epsilon<double>(freq) < NEON_TX_HB_MIN_FREQ) { + band = tx_band::LB_2750; + } else if (fp_compare_epsilon<double>(freq) <= AD9361_MAX_FREQ) { + band = tx_band::HB; + } else { + band = tx_band::INVALID_BAND; + } + + return band; +} + diff --git a/host/lib/usrp/dboard/neon/neon_constants.hpp b/host/lib/usrp/dboard/neon/neon_constants.hpp new file mode 100644 index 000000000..dc560ca93 --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_constants.hpp @@ -0,0 +1,51 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_NEON_CONSTANTS_HPP +#define INCLUDED_LIBUHD_NEON_CONSTANTS_HPP + +#include <vector> +#include <string> +#include <cstddef> + +static constexpr size_t FPGPIO_MASTER_RADIO = 0; +static constexpr size_t TOTAL_RADIO_PORTS = 2; +static constexpr double AD9361_RX_MIN_BANDWIDTH = 20.0e6; // HZ +static constexpr double AD9361_RX_MAX_BANDWIDTH = 40.0e6; // HZ + +static constexpr double AD9361_TX_MIN_BANDWIDTH = 20.0e6; // HZ +static constexpr double AD9361_TX_MAX_BANDWIDTH = 40.0e6; // HZ + +static constexpr double AD9361_MIN_FREQ = 70.0e6; // Hz +static constexpr double AD9361_MAX_FREQ = 6.0e9; // Hz + +static constexpr double NEON_RADIO_RATE = 16e6; // Hz + +static constexpr double AD9361_MIN_RX_GAIN = 0.0; // dB +static constexpr double AD9361_MAX_RX_GAIN = 76; // dB +static constexpr double AD9361_RX_GAIN_STEP = 1.0; // dB +static constexpr double AD9361_MIN_TX_GAIN = 0.0; // dB +static constexpr double AD9361_MAX_TX_GAIN = 89.75; // dB +static constexpr double AD9361_TX_GAIN_STEP = 0.25; // dB + + +static constexpr bool NEON_DEFAULT_AUTO_DC_OFFSET = true; +static constexpr bool NEON_DEFAULT_AUTO_IQ_BALANCE = true; +static constexpr bool NEON_DEFAULT_AGC_ENABLE = false; + +static constexpr double NEON_DEFAULT_GAIN = 0.0; +static constexpr double NEON_DEFAULT_FREQ = 2.4e9; // Hz +static constexpr double NEON_DEFAULT_BANDWIDTH = 40e6; // Hz +static constexpr char NEON_DEFAULT_RX_ANTENNA[] = "RX2"; +static constexpr char NEON_DEFAULT_TX_ANTENNA[] = "TX/RX"; + +static const std::vector<std::string> NEON_RX_ANTENNAS = { + "TX/RX", "RX2" +}; + +static constexpr size_t NEON_NUM_CHANS = 2; + +#endif /* INCLUDED_LIBUHD_NEON_CONSTANTS_HPP */ diff --git a/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp new file mode 100644 index 000000000..a753a447c --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp @@ -0,0 +1,621 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "neon_radio_ctrl_impl.hpp" +#include "neon_constants.hpp" +#include "neon_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/math.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/exception.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/make_shared.hpp> +#include <boost/format.hpp> +#include <sstream> +#include <cmath> +#include <cstdlib> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +/****************************************************************************** + * Structors + *****************************************************************************/ +UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(neon_radio_ctrl) +{ + UHD_LOG_TRACE(unique_id(), "Entering neon_radio_ctrl_impl ctor..."); + const char radio_slot_name[1] = {'A'}; + _radio_slot = radio_slot_name[get_block_id().get_block_count()]; + UHD_LOG_TRACE(unique_id(), "Radio slot: " << _radio_slot); + _rpc_prefix = "db_0_"; + + _init_defaults(); + _init_peripherals(); + _init_prop_tree(); +} + +neon_radio_ctrl_impl::~neon_radio_ctrl_impl() +{ + UHD_LOG_TRACE(unique_id(), "neon_radio_ctrl_impl::dtor() "); +} + + +/****************************************************************************** + * API Calls + *****************************************************************************/ + +bool neon_radio_ctrl_impl::check_radio_config() +{ + // mapping of frontend to radio perif index + static const size_t FE0 = 0; + static const size_t FE1 = 1; + const size_t num_rx = _is_streamer_active(RX_DIRECTION, FE0) + _is_streamer_active(RX_DIRECTION, FE1); + const size_t num_tx = _is_streamer_active(TX_DIRECTION, FE0) + _is_streamer_active(TX_DIRECTION, FE1); + + //setup the active chains in the codec + if ((num_rx + num_tx) == 0) { + // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock + this->set_streaming_mode(false, false, true, false); + } else { + this->set_streaming_mode( + _is_streamer_active(TX_DIRECTION, FE0), + _is_streamer_active(TX_DIRECTION, FE1), + _is_streamer_active(RX_DIRECTION, FE0), + _is_streamer_active(RX_DIRECTION, FE1) + ); + } + return true; +} + +void neon_radio_ctrl_impl::set_streaming_mode(const bool tx1, const bool tx2, const bool rx1, const bool rx2) +{ + UHD_LOG_TRACE(unique_id(), "Setting up streaming ...") + const size_t num_rx = rx1 + rx2; + const size_t num_tx = tx1 + tx2; + + //setup the active chains in the codec + _ad9361->set_active_chains(tx1, tx2, rx1, rx2); + + const std::string TIMING_MODE_2R2T = "2R2T"; + const std::string TIMING_MODE_1R1T = "1R1T"; + const std::string MIMO = "MIMO"; // 2R2T + const std::string SISO_TX1 = "SISO_TX1"; // 1R1T + const std::string SISO_TX0 = "SISO_TX0"; // 1R1T + // setup 1R1T/2R2T mode in catalina and fpga + // The Catalina interface in the fpga needs to know which TX channel to use for + // the data on the LVDS lines. + if ((num_rx == 2) or (num_tx == 2)) { + // AD9361 is in 2R2T mode + _ad9361->set_timing_mode(TIMING_MODE_2R2T); + this->set_channel_mode(MIMO); + } else { + // AD9361 is in 1R1T mode + _ad9361->set_timing_mode(TIMING_MODE_1R1T); + + // Set to SIS0_TX1 if we're using the second TX antenna, otherwise + // default to SISO_TX0 + this->set_channel_mode(tx2 ? SISO_TX1 : SISO_TX0); + } +} + +void neon_radio_ctrl_impl::set_channel_mode(const std::string &channel_mode) +{ + //MIMO for 2R2T mode for 2 channels + //SISO_TX1 for 1R1T mode for 1 channel - TX1 + //SISO_TX0 for 1R1T mode for 1 channel - TX0 + + _rpcc->request_with_token<void>("set_channel_mode", channel_mode); +} + +/* loopback_self_test checks the integrity of the FPGA->AD936x->FPGA sample interface. + The AD936x is put in loopback mode that sends the TX data unchanged to the RX side. + A test value is written to the codec_idle register in the TX side of the radio. + The readback register is then used to capture the values on the TX and RX sides + simultaneously for comparison. It is a reasonably effective test for AC timing + since I/Q Ch0/Ch1 alternate over the same wires. Note, however, that it uses + whatever timing is configured at the time the test is called rather than select + worst case conditions to stress the interface. + Note: This currently only tests 2R2T mode +*/ +void neon_radio_ctrl_impl::loopback_self_test( + std::function<void(uint32_t)> poker_functor, + std::function<uint64_t()> peeker_functor +) { + // Set 2R2T mode, stream on all channels + this->set_streaming_mode(true, true, true, true); + // Set maximum rate for 2R2T mode + this->set_rate(30.72e6); + // Put AD936x in loopback mode + _ad9361->data_port_loopback(true); + UHD_LOG_INFO(unique_id(), "Performing CODEC loopback test... "); + size_t hash = size_t(time(NULL)); + constexpr size_t loopback_count = 100; + + // Allow some time for AD936x to enter loopback mode. + // There is no clear statement in the documentation of how long it takes, + // but UG-570 does say to "allow six ADC_CLK/64 clock cycles of flush time" + // when leaving the TX or RX states. That works out to ~75us at the + // minimum clock rate of 5 MHz, which lines up with test results. + // Sleeping 1ms is far more than enough. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + for (size_t i = 0; i < loopback_count; i++) + { + // Create test word + boost::hash_combine(hash, i); + const uint32_t word32 = uint32_t(hash) & 0xfff0fff0; + //const uint32_t word32 = 0xCA00C100; + // Write test word to codec_idle idle register (on TX side) + poker_functor(word32); + + // Read back values - TX is lower 32-bits and RX is upper 32-bits + const uint64_t rb_word64 = peeker_functor(); + const uint32_t rb_tx = uint32_t(rb_word64 >> 32); + const uint32_t rb_rx = uint32_t(rb_word64 & 0xffffffff); + + // Compare TX and RX values to test word + bool test_fail = word32 != rb_tx or word32 != rb_rx; + if(test_fail) + { + UHD_LOG_WARNING(unique_id(), "CODEC loopback test failed! " + << boost::format("Expected: 0x%08X Received (TX/RX): 0x%08X/0x%08X") + % word32 % rb_tx % rb_rx); + throw uhd::runtime_error("CODEC loopback test failed."); + } + } + UHD_LOG_INFO(unique_id(), "CODEC loopback test passed"); + + // Zero out the idle data. + poker_functor(0); + + // Take AD936x out of loopback mode + _ad9361->data_port_loopback(false); + this->set_streaming_mode(true, false, true, false); +} + +double neon_radio_ctrl_impl::set_rate(const double rate) +{ + std::lock_guard<std::mutex> l(_set_lock); + UHD_LOG_DEBUG(unique_id(), "Asking for clock rate " << rate/1e6 << " MHz\n"); + double actual_tick_rate = _ad9361->set_clock_rate(rate); + UHD_LOG_DEBUG(unique_id(), "Actual clock rate " << actual_tick_rate/1e6 << " MHz\n"); + + radio_ctrl_impl::set_rate(rate); + return rate; +} + +void neon_radio_ctrl_impl::set_tx_antenna( + const std::string &ant, + const size_t chan +) { + if (ant != get_tx_antenna(chan)) { + throw uhd::value_error(str( + boost::format("[%s] Requesting invalid TX antenna value: %s") + % unique_id() + % ant + )); + } + // We can't actually set the TX antenna, so let's stop here. +} + +void neon_radio_ctrl_impl::set_rx_antenna( + const std::string &ant, + const size_t chan +) { + UHD_ASSERT_THROW(chan <= NEON_NUM_CHANS); + if (std::find(NEON_RX_ANTENNAS.begin(), + NEON_RX_ANTENNAS.end(), + ant) == NEON_RX_ANTENNAS.end()) { + throw uhd::value_error(str( + boost::format("[%s] Requesting invalid RX antenna value: %s") + % unique_id() + % ant + )); + } + UHD_LOG_TRACE(unique_id(), + "Setting RX antenna to " << ant << " for chan " << chan); + + _set_atr_bits(chan, radio_ctrl_impl::get_rx_frequency(chan), ant); + radio_ctrl_impl::set_rx_antenna(ant, chan); +} + +double neon_radio_ctrl_impl::set_tx_frequency( + const double freq, + const size_t chan +) { + UHD_LOG_TRACE(unique_id(), + "set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); + std::lock_guard<std::mutex> l(_set_lock); + + double coerced_freq = _ad9361->tune( + get_which_ad9361_chain(TX_DIRECTION, chan), + freq); + radio_ctrl_impl::set_tx_frequency(coerced_freq, chan); + // Antenna settings + auto ant = get_tx_antenna(chan); + // Front-end switching + _set_atr_bits(chan, freq, ant); + + return coerced_freq; +} + +double neon_radio_ctrl_impl::set_rx_frequency( + const double freq, + const size_t chan +) { + UHD_LOG_TRACE(unique_id(), + "set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); + std::lock_guard<std::mutex> l(_set_lock); + + double coerced_freq = _ad9361->tune( + get_which_ad9361_chain(RX_DIRECTION, chan), + freq); + radio_ctrl_impl::set_rx_frequency(coerced_freq, chan); + // Antenna settings + auto ant = get_rx_antenna(chan); + // Front-end switching + _set_atr_bits(chan, coerced_freq, ant); + + return coerced_freq; +} + +double neon_radio_ctrl_impl::set_rx_bandwidth( + const double bandwidth, + const size_t chan +) { + std::lock_guard<std::mutex> l(_set_lock); + double clipped_bw = _ad9361->set_bw_filter( + get_which_ad9361_chain(RX_DIRECTION, chan), + bandwidth); + return radio_ctrl_impl::set_rx_bandwidth(clipped_bw, chan); +} + +double neon_radio_ctrl_impl::set_tx_bandwidth( + const double bandwidth, + const size_t chan +) { + std::lock_guard<std::mutex> l(_set_lock); + double clipped_bw = _ad9361->set_bw_filter( + get_which_ad9361_chain(TX_DIRECTION, chan), + bandwidth); + return radio_ctrl_impl::set_tx_bandwidth(clipped_bw, chan); +} + +double neon_radio_ctrl_impl::set_tx_gain( + const double gain, + const size_t chan +) { + std::lock_guard<std::mutex> l(_set_lock); + UHD_LOG_TRACE(unique_id(), + "set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); + double clip_gain = uhd::clip(gain, AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN); + _ad9361->set_gain( + get_which_ad9361_chain(TX_DIRECTION, chan), + clip_gain); + radio_ctrl_impl::set_tx_gain(clip_gain, chan); + return clip_gain; +} + +double neon_radio_ctrl_impl::set_rx_gain( + const double gain, + const size_t chan +) { + std::lock_guard<std::mutex> l(_set_lock); + UHD_LOG_TRACE(unique_id(), + "set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); + double clip_gain = uhd::clip(gain, AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN); + _ad9361->set_gain( + get_which_ad9361_chain(RX_DIRECTION, chan), + clip_gain); + radio_ctrl_impl::set_rx_gain(clip_gain, chan); + return clip_gain; +} + +size_t neon_radio_ctrl_impl::get_chan_from_dboard_fe( + const std::string &fe, const direction_t /* dir */ +) { + const size_t chan = boost::lexical_cast<size_t>(fe); + if (chan > _get_num_radios()-1) { + UHD_LOG_WARNING(unique_id(), + boost::format("Invalid channel determined from dboard frontend %s.") % fe); + } + return chan; +} + +std::string neon_radio_ctrl_impl::get_dboard_fe_from_chan( + const size_t chan, + const direction_t /* dir */ +) { + return std::to_string(chan); +} + +void neon_radio_ctrl_impl::set_rpc_client( + uhd::rpc_client::sptr rpcc, + const uhd::device_addr_t &block_args +) { + _rpcc = rpcc; + _block_args = block_args; + UHD_LOG_TRACE(unique_id(), "Instantiating AD9361 control object..."); + _ad9361 = make_rpc(_rpcc); + + UHD_LOG_TRACE(unique_id(), "Setting Catalina Defaults... "); + //Initialize catalina + this->_init_codec(); + + if (block_args.has_key("identify")) { + const std::string identify_val = block_args.get("identify"); + int identify_duration = std::atoi(identify_val.c_str()); + if (identify_duration == 0) { + identify_duration = 5; + } + UHD_LOG_INFO(unique_id(), + "Running LED identification process for " << identify_duration + << " seconds."); + _identify_with_leds(identify_duration); + } + // Note: MCR gets set during the init() call (prior to this), which takes + // in arguments from the device args. So if block_args contains a + // master_clock_rate key, then it should better be whatever the device is + // configured to do. + _master_clock_rate = _rpcc->request_with_token<double>( + _rpc_prefix + "get_master_clock_rate"); + if (block_args.cast<double>("master_clock_rate", _master_clock_rate) + != _master_clock_rate) { + throw uhd::runtime_error(str( + boost::format("Master clock rate mismatch. Device returns %f MHz, " + "but should have been %f MHz.") + % (_master_clock_rate / 1e6) + % (block_args.cast<double>( + "master_clock_rate", _master_clock_rate) / 1e6) + )); + + } + UHD_LOG_DEBUG(unique_id(), + "Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); + this->set_rate(_master_clock_rate); + + // Loopback test + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + loopback_self_test( + [this, chan](const uint32_t value){ + this->sr_write(regs::CODEC_IDLE, value, chan); + }, + [this, chan](){ + return this->user_reg_read64(regs::RB_CODEC_READBACK, chan); + } + ); + } + + const size_t db_idx = get_block_id().get_block_count(); + _tree->access<eeprom_map_t>(_root_path / "eeprom") + .add_coerced_subscriber([this, db_idx](const eeprom_map_t& db_eeprom){ + this->_rpcc->notify_with_token("set_db_eeprom", db_idx, db_eeprom); + }) + .set_publisher([this, db_idx](){ + return this->_rpcc->request_with_token<eeprom_map_t>( + "get_db_eeprom", db_idx + ); + }) + ; + + // Init sensors + for (const auto &dir : std::vector<direction_t>{RX_DIRECTION, TX_DIRECTION}) { + for (size_t chan_idx = 0; chan_idx < NEON_NUM_CHANS; chan_idx++) { + _init_mpm_sensors(dir, chan_idx); + } + } +} + +bool neon_radio_ctrl_impl::get_lo_lock_status( + const direction_t dir +) { + if (not (bool(_rpcc))) { + UHD_LOG_DEBUG(unique_id(), + "Reported no LO lock due to lack of RPC connection."); + return false; + } + + const std::string trx = (dir == RX_DIRECTION) ? "rx" : "tx"; + bool lo_lock = _rpcc->request_with_token<bool>( + _rpc_prefix + "get_ad9361_lo_lock", trx); + UHD_LOG_TRACE(unique_id(), + "AD9361 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); + + return lo_lock; +} + +void neon_radio_ctrl_impl::_set_atr_bits( + const size_t chan, + const double freq, + const std::string &ant +) { + + const uint32_t rx_regs = _get_rx_switches(chan, freq, ant); + const uint32_t tx_regs = _get_tx_switches(chan, freq); + const uint32_t idle_regs = TX_AMP_OFF << TX_AMP_SHIFT | + TRX1_SW_TX_HB << TRX_SW_SHIFT | + TX_SW2_LB_80 << TX_SW2_SHIFT | + TX_SW1_LB_80 << TX_SW1_SHIFT | + RX_SW3_OFF << RX_SW3_SHIFT | + RX_SW2_OFF << RX_SW2_SHIFT | + RX_SW1_OFF << RX_SW1_SHIFT; + + _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_IDLE, idle_regs); + _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_RX_ONLY, rx_regs); + _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_TX_ONLY, tx_regs); + _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_FULL_DUPLEX, rx_regs | tx_regs); + + // The LED signal names are reversed, but are consistent with the schematic + const int idle_led = 0; + const bool is_txrx = ant == "TX/RX"; + const int rx_led = 1 << TRX_LED_GRN_SHIFT; + const int tx_led = 1 << TX_LED_RED_SHIFT; + const int txrx_led = 1 << RX_LED_GRN_SHIFT; + + _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_IDLE, idle_led); + _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_RX_ONLY, is_txrx ? txrx_led : rx_led); + _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_TX_ONLY, tx_led); + _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_FULL_DUPLEX, rx_led | tx_led); +} + +void neon_radio_ctrl_impl::_identify_with_leds( + const int identify_duration +) { + auto end_time = std::chrono::steady_clock::now() + + std::chrono::seconds(identify_duration); + bool led_state = true; + while (std::chrono::steady_clock::now() < end_time) { + // Add update_leds + led_state = !led_state; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } +} + +uint32_t neon_radio_ctrl_impl::_get_tx_switches( + const size_t chan, + const double freq +) { + + UHD_LOG_TRACE(unique_id(), + "Update all TX freq related switches. f=" << freq << " Hz, " + ); + auto tx_sw1 = TX_SW1_LB_160; + auto tx_sw2 = TX_SW2_LB_160; + auto trx_sw = (chan == 0) ? TRX1_SW_TX_LB: TRX2_SW_TX_LB; + auto tx_amp = TX_AMP_LF_ON; + + const auto band = _map_freq_to_tx_band(freq); + switch(band) { + case tx_band::LB_80: + tx_sw1 = TX_SW1_LB_80; + tx_sw2 = TX_SW2_LB_80; + break; + case tx_band::LB_160: + tx_sw1 = TX_SW1_LB_160; + tx_sw2 = TX_SW2_LB_160; + break; + case tx_band::LB_225: + tx_sw1 = TX_SW1_LB_225; + tx_sw2 = TX_SW2_LB_225; + break; + case tx_band::LB_400: + tx_sw1 = TX_SW1_LB_400; + tx_sw2 = TX_SW2_LB_400; + break; + case tx_band::LB_575: + tx_sw1 = TX_SW1_LB_575; + tx_sw2 = TX_SW2_LB_575; + break; + case tx_band::LB_1000: + tx_sw1 = TX_SW1_LB_1000; + tx_sw2 = TX_SW2_LB_1000; + break; + case tx_band::LB_1700: + tx_sw1 = TX_SW1_LB_1700; + tx_sw2 = TX_SW2_LB_1700; + break; + case tx_band::LB_2750: + tx_sw1 = TX_SW1_LB_2750; + tx_sw2 = TX_SW2_LB_2750; + break; + case tx_band::HB: + trx_sw = (chan == 0) ? TRX1_SW_TX_HB: TRX2_SW_TX_HB; + tx_amp = TX_AMP_HF_ON; + break; + case tx_band::INVALID_BAND: + UHD_LOG_ERROR(unique_id(), + "Cannot map TX frequency to band: " << freq); + UHD_THROW_INVALID_CODE_PATH(); + break; + } + + auto tx_regs = tx_amp << TX_AMP_SHIFT | + trx_sw << TRX_SW_SHIFT | + tx_sw2 << TX_SW2_SHIFT | + tx_sw1 << TX_SW1_SHIFT; + return tx_regs; +} + +uint32_t neon_radio_ctrl_impl::_get_rx_switches( + const size_t chan, + const double freq, + const std::string &ant +){ + UHD_LOG_TRACE(unique_id(), + "Update all RX freq related switches. f=" << freq << " Hz, " + ); + // Default to OFF + auto rx_sw1 = RX_SW1_OFF; + auto rx_sw2 = RX_SW2_OFF; + auto rx_sw3 = RX_SW3_OFF; + auto trx_sw = (chan == 0) ? TRX1_SW_RX: TRX2_SW_RX; + if (ant == "TX/RX") { + rx_sw3 = RX_SW3_HBRX_LBTRX; + trx_sw = (chan == 0) ? TRX1_SW_RX: TRX2_SW_RX; + } + else if (ant == "RX2") { + rx_sw3 = RX_SW3_HBTRX_LBRX; + // Set TRX switch to TX when receiving on RX2 + trx_sw = TRX1_SW_TX_HB; + } + + const auto band = _map_freq_to_rx_band(freq); + switch(band) { + case rx_band::LB_B2: + rx_sw1 = RX_SW1_LB_B2; + rx_sw2 = RX_SW2_LB_B2; + break; + case rx_band::LB_B3: + rx_sw1 = RX_SW1_LB_B3; + rx_sw2 = RX_SW2_LB_B3; + break; + case rx_band::LB_B4: + rx_sw1 = RX_SW1_LB_B4; + rx_sw2 = RX_SW2_LB_B4; + break; + case rx_band::LB_B5: + rx_sw1 = RX_SW1_LB_B5; + rx_sw2 = RX_SW2_LB_B5; + break; + case rx_band::LB_B6: + rx_sw1 = RX_SW1_LB_B6; + rx_sw2 = RX_SW2_LB_B6; + break; + case rx_band::LB_B7: + rx_sw1 = RX_SW1_LB_B7; + rx_sw2 = RX_SW2_LB_B7; + break; + case rx_band::HB: + rx_sw1 = RX_SW1_OFF; + rx_sw2 = RX_SW2_OFF; + if (ant == "TX/RX") { + rx_sw3 = RX_SW3_HBTRX_LBRX; + } + else if (ant == "RX2") { + rx_sw3 = RX_SW3_HBRX_LBTRX; + } + break; + case rx_band::INVALID_BAND: + UHD_LOG_ERROR(unique_id(), + "Cannot map RX frequency to band: " << freq); + UHD_THROW_INVALID_CODE_PATH(); + break; + } + + auto rx_regs = trx_sw << TRX_SW_SHIFT | + rx_sw3 << RX_SW3_SHIFT | + rx_sw2 << RX_SW2_SHIFT | + rx_sw1 << RX_SW1_SHIFT; + return rx_regs; +} + +UHD_RFNOC_BLOCK_REGISTER(neon_radio_ctrl, "NeonRadio"); diff --git a/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp new file mode 100644 index 000000000..64b970d54 --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp @@ -0,0 +1,311 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_NEON_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_NEON_RADIO_CTRL_IMPL_HPP + +#include "neon_ad9361_iface.hpp" +#include <uhd/types/serial.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <uhdlib/usrp/cores/gpio_atr_3000.hpp> +#include <uhdlib/rfnoc/rpc_block_ctrl.hpp> +#include <uhdlib/rfnoc/radio_ctrl_impl.hpp> +#include <mutex> + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to an Magnesium radio. + */ +class neon_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl +{ +public: + typedef boost::shared_ptr<neon_radio_ctrl_impl> sptr; + + //! Frequency bands for RX. Bands are a function of the analog filter banks + enum class rx_band { + INVALID_BAND, + LB_B2, + LB_B3, + LB_B4, + LB_B5, + LB_B6, + LB_B7, + HB + }; + + //! Frequency bands for TX. Bands are a function of the analog filter banks + enum class tx_band { + INVALID_BAND, + LB_80, + LB_160, + LB_225, + LB_400, + LB_575, + LB_1000, + LB_1700, + LB_2750, + HB + }; + + /************************************************************************** + * ATR/ Switches Types + *************************************************************************/ + //! ATR state + enum atr_state_t { + IDLE, + RX_ONLY, + TX_ONLY, + FULL_DUPLEX + }; + + //! Channel select: + enum chan_sel_t { + CHAN1, + CHAN2, + BOTH + }; + + enum tx_sw1_t { + TX_SW1_LB_80 = 3, + TX_SW1_LB_160 = 7, + TX_SW1_LB_225 = 1, + TX_SW1_LB_400 = 5, + TX_SW1_LB_575 = 2, + TX_SW1_LB_1000 = 6, + TX_SW1_LB_1700 = 0, + TX_SW1_LB_2750 = 4 + }; + + enum tx_sw2_t { + TX_SW2_LB_80 = 7, + TX_SW2_LB_160 = 3, + TX_SW2_LB_225 = 5, + TX_SW2_LB_400 = 1, + TX_SW2_LB_575 = 6, + TX_SW2_LB_1000 = 2, + TX_SW2_LB_1700 = 4, + TX_SW2_LB_2750 = 0 + }; + + enum trx_sw_t { + TRX1_SW_TX_HB = 2, + TRX1_SW_TX_LB = 1, + TRX1_SW_RX = 4, + TRX2_SW_TX_HB = 2, + TRX2_SW_TX_LB = 4, + TRX2_SW_RX = 1 + }; + + enum rx_sw1_t { + RX_SW1_LB_B2 = 4, + RX_SW1_LB_B3 = 5, + RX_SW1_LB_B4 = 2, + RX_SW1_LB_B5 = 3, + RX_SW1_LB_B6 = 0, + RX_SW1_LB_B7 = 1, + RX_SW1_OFF = 7 + + }; + + enum rx_sw2_t { + RX_SW2_LB_B2 = 5, + RX_SW2_LB_B3 = 4, + RX_SW2_LB_B4 = 3, + RX_SW2_LB_B5 = 2, + RX_SW2_LB_B6 = 1, + RX_SW2_LB_B7 = 0, + RX_SW2_OFF = 7 + }; + + enum rx_sw3_t { + RX_SW3_HBRX_LBTRX = 1, + RX_SW3_HBTRX_LBRX = 2, + RX_SW3_OFF = 0 //or 3 + }; + + enum tx_amp_t { + TX_AMP_HF_ON = 1, + TX_AMP_LF_ON = 2, + TX_AMP_OFF = 3 + }; + + /************************************************************************ + * Structors + ***********************************************************************/ + UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(neon_radio_ctrl) + virtual ~neon_radio_ctrl_impl(); + + /************************************************************************ + * API calls + ***********************************************************************/ + // Note: We use the cached values in radio_ctrl_impl, so most getters are + // not reimplemented here + + virtual bool check_radio_config(); + + //! Set which channel mode is used + void set_channel_mode(const std::string &channel_mode); + + //! Set streaming mode - active chains, channel_mode, timing_mode + void set_streaming_mode(const bool tx1, const bool tx2, const bool rx1, const bool rx2); + + /*! Run a loopback self test. + * + * This will write data to the AD936x and read it back again. + * If this test fails, it generally means the interface is broken, + * so we assume it passes and throw otherwise. Running this requires + * a core that we can peek and poke the loopback values into. + * + * \param iface An interface to the associated radio control core + * \param iface The radio control core's address to write the loopback value + * \param iface The radio control core's readback address to read back the returned value + * + * \throws a uhd::runtime_error if the loopback value didn't match. + */ + void loopback_self_test( + std::function<void(uint32_t)> poker_functor, + std::function<uint64_t()> peeker_functor + ); + + double set_rate(const double rate); + + void set_tx_antenna(const std::string &ant, const size_t chan); + void set_rx_antenna(const std::string &ant, const size_t chan); + + double set_tx_frequency(const double freq, const size_t chan); + double set_rx_frequency(const double freq, const size_t chan); + double set_tx_bandwidth(const double bandwidth, const size_t chan); + double set_rx_bandwidth(const double bandwidth, const size_t chan); + + // gain + double set_tx_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + + size_t get_chan_from_dboard_fe(const std::string &fe, const direction_t dir); + std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); + + void set_rpc_client( + uhd::rpc_client::sptr rpcc, + const uhd::device_addr_t &block_args + ); + +private: + /************************************************************************** + * Helpers + *************************************************************************/ + //! Initialize all the peripherals connected to this block + void _init_peripherals(); + + //! Set state of this class to sensible defaults + void _init_defaults(); + + //! Init a subtree for the RF frontends + void _init_frontend_subtree( + uhd::property_tree::sptr subtree, + const size_t chan_idx + ); + + //! Initialize Catalina defaults + void _init_codec(); + + //! Initialize property tree + void _init_prop_tree(); + + void _init_mpm_sensors( + const direction_t dir, + const size_t chan_idx + ); + + //! Map a frequency in Hz to an rx_band value. Will return + // rx_band::INVALID_BAND if the frequency is out of range. + rx_band _map_freq_to_rx_band(const double freq); + //! Map a frequency in Hz to an tx_band value. Will return + // tx_band::INVALID_BAND if the frequency is out of range. + tx_band _map_freq_to_tx_band(const double freq); + + /************************************************************************* + * Sensors + *************************************************************************/ + //! Return LO lock status. Factors in current band (low/high) and + // direction (TX/RX) + bool get_lo_lock_status( + const direction_t dir + ); + + /************************************************************************** + * Misc Controls + *************************************************************************/ + //! Blink the front-panel LEDs for \p identify_duration, + // and resume normal operation. + void _identify_with_leds( + const int identify_duration + ); + + uint32_t _get_rx_switches( + const size_t chan, + const double freq, + const std::string &ant + ); + + uint32_t _get_tx_switches( + const size_t chan, + const double freq + ); + + void _set_atr_bits( + const size_t chan, + const double freq, + const std::string &ant + ); + + /************************************************************************** + * Private attributes + *************************************************************************/ + //! Locks access to setter APIs + std::mutex _set_lock; + + //! Letter representation of the radio we're currently running + std::string _radio_slot; + + //! Prepended for all dboard RPC calls + std::string _rpc_prefix; + + //! Additional block args; gets set during set_rpc_client() + uhd::device_addr_t _block_args; + + //! Reference to the RPC client + uhd::rpc_client::sptr _rpcc; + + //! Reference to the SPI core + uhd::spi_iface::sptr _spi; + + //! Reference to the AD9361 controls + //neon_ad9361_iface::uptr _ad9361; + ad9361_ctrl::sptr _ad9361; + + //! ATR controls. These control the AD9361 gain + // up/down bits. + // Every radio channel gets its own ATR state register. + std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _db_gpio; + + // ATR controls for LEDs + std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _leds_gpio; + + //! Front panel GPIO controller. Note that only one radio block per + // module can be the FP-GPIO master. + usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + + //! Sampling rate + double _master_clock_rate = 1.0; +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NEON_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: + diff --git a/host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp b/host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp new file mode 100644 index 000000000..9c2396f2d --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp @@ -0,0 +1,495 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "neon_radio_ctrl_impl.hpp" +#include "neon_constants.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/algorithm/string/case_conv.hpp> +#include <vector> +#include <string> + +using namespace uhd; +using namespace uhd::rfnoc; + +//! Helper function to extract single value of port number. +// +// Each GPIO pins can be controlled by each radio output ports. +// This function convert the format of attribute "Radio_N_M" +// to a single value port number = N*number_of_port_per_radio + M + +uint32_t _extract_port_number(std::string radio_src_string, uhd::property_tree::sptr ptree){ + std::string s_val = "0"; + std::vector<std::string> radio_strings; + boost::algorithm::split( + radio_strings, + radio_src_string, + boost::is_any_of("_/"), + boost::token_compress_on); + boost::to_lower(radio_strings[0]); + if (radio_strings.size()<3) { + throw uhd::runtime_error(str(boost::format("%s is an invalid GPIO source string.") % radio_src_string)); + } + size_t radio_num = std::stoi(radio_strings[1]); + size_t port_num = std::stoi(radio_strings[2]); + if (radio_strings[0] != "radio") { + throw uhd::runtime_error("Front panel GPIO bank can only accept a radio block as its driver."); + } + std::string radio_port_out = "Radio_"+ radio_strings[1] + "/ports/out"; + std::string radio_port_path = radio_port_out + "/"+ radio_strings[2]; + auto found = ptree->exists(fs_path("xbar")/ radio_port_path); + if (not found){ + throw uhd::runtime_error(str(boost::format( + "Could not find radio port %s.\n") % radio_port_path)); + } + size_t port_size = ptree->list(fs_path("xbar")/ radio_port_out).size(); + return radio_num*port_size + port_num; +} + +void neon_radio_ctrl_impl::_init_defaults() +{ + UHD_LOG_TRACE(unique_id(), "Initializing defaults..."); + const size_t num_rx_chans = get_output_ports().size(); + const size_t num_tx_chans = get_input_ports().size(); + + UHD_LOG_TRACE(unique_id(), + "Num TX chans: " << num_tx_chans + << " Num RX chans: " << num_rx_chans); + + for (size_t chan = 0; chan < num_rx_chans; chan++) { + radio_ctrl_impl::set_rx_frequency(NEON_DEFAULT_FREQ, chan); + radio_ctrl_impl::set_rx_gain(NEON_DEFAULT_GAIN, chan); + radio_ctrl_impl::set_rx_antenna(NEON_DEFAULT_RX_ANTENNA, chan); + radio_ctrl_impl::set_rx_bandwidth(NEON_DEFAULT_BANDWIDTH, chan); + } + + for (size_t chan = 0; chan < num_tx_chans; chan++) { + radio_ctrl_impl::set_tx_frequency(NEON_DEFAULT_FREQ, chan); + radio_ctrl_impl::set_tx_gain(NEON_DEFAULT_GAIN, chan); + radio_ctrl_impl::set_tx_antenna(NEON_DEFAULT_TX_ANTENNA, chan); + } + + /** Update default SPP (overwrites the default value from the XML file) **/ + const size_t max_bytes_header = + uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); + const size_t default_spp = + (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) + / (2 * sizeof(int16_t)); + UHD_LOG_DEBUG(unique_id(), + "Setting default spp to " << default_spp); + _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); +} + +void neon_radio_ctrl_impl::_init_peripherals() +{ + UHD_LOG_TRACE(unique_id(), "Initializing peripherals..."); + + _db_gpio.clear(); // Following the as-if rule, this can get optimized out + for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { + UHD_LOG_TRACE(unique_id(), + "Initializing GPIOs for channel " << radio_idx); + _db_gpio.emplace_back( + usrp::gpio_atr::gpio_atr_3000::make_write_only( + _get_ctrl(radio_idx), + regs::sr_addr(regs::GPIO) + ) + ); + _db_gpio[radio_idx]->set_atr_mode(usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + } + _leds_gpio.clear(); // Following the as-if rule, this can get optimized out + for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { + UHD_LOG_TRACE(unique_id(), + "Initializing GPIOs for channel " << radio_idx); + _leds_gpio.emplace_back( + usrp::gpio_atr::gpio_atr_3000::make_write_only( + _get_ctrl(radio_idx), + regs::sr_addr(regs::LEDS) + ) + ); + + _leds_gpio[radio_idx]->set_atr_mode(usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + } + UHD_LOG_TRACE(unique_id(), "Initializing front-panel GPIO control...") + _fp_gpio = usrp::gpio_atr::gpio_atr_3000::make( + _get_ctrl(0), regs::sr_addr(regs::FP_GPIO), regs::RB_FP_GPIO); +} + +void neon_radio_ctrl_impl::_init_frontend_subtree( + uhd::property_tree::sptr subtree, + const size_t chan_idx +) { + const fs_path tx_fe_path = fs_path("tx_frontends") / chan_idx; + const fs_path rx_fe_path = fs_path("rx_frontends") / chan_idx; + UHD_LOG_TRACE(unique_id(), + "Adding non-RFNoC block properties for channel " << chan_idx << + " to prop tree path " << tx_fe_path << " and " << rx_fe_path); + // TX Standard attributes + subtree->create<std::string>(tx_fe_path / "name") + .set(str(boost::format("Neon"))) + ; + subtree->create<std::string>(tx_fe_path / "connection") + .set("IQ") + ; + // RX Standard attributes + subtree->create<std::string>(rx_fe_path / "name") + .set(str(boost::format("Neon"))) + ; + subtree->create<std::string>(rx_fe_path / "connection") + .set("IQ") + ; + // TX Antenna + subtree->create<std::string>(tx_fe_path / "antenna" / "value") + .add_coerced_subscriber([this, chan_idx](const std::string &ant){ + this->set_tx_antenna(ant, chan_idx); + }) + .set_publisher([this, chan_idx](){ + return this->get_tx_antenna(chan_idx); + }) + ; + subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") + .set({NEON_DEFAULT_TX_ANTENNA}) + .add_coerced_subscriber([](const std::vector<std::string> &){ + throw uhd::runtime_error( + "Attempting to update antenna options!"); + }) + ; + // RX Antenna + subtree->create<std::string>(rx_fe_path / "antenna" / "value") + .add_coerced_subscriber([this, chan_idx](const std::string &ant){ + this->set_rx_antenna(ant, chan_idx); + }) + .set_publisher([this, chan_idx](){ + return this->get_rx_antenna(chan_idx); + }) + ; + subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") + .set(NEON_RX_ANTENNAS) + .add_coerced_subscriber([](const std::vector<std::string> &){ + throw uhd::runtime_error( + "Attempting to update antenna options!"); + }) + ; + // TX frequency + subtree->create<double>(tx_fe_path / "freq" / "value") + .set_coercer([this, chan_idx](const double freq){ + return this->set_tx_frequency(freq, chan_idx); + }) + .set_publisher([this, chan_idx](){ + return this->get_tx_frequency(chan_idx); + }) + ; + subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") + .set(meta_range_t(AD9361_MIN_FREQ, AD9361_MAX_FREQ, 1.0)) + .add_coerced_subscriber([](const meta_range_t &){ + throw uhd::runtime_error( + "Attempting to update freq range!"); + }) + ; + // RX frequency + subtree->create<double>(rx_fe_path / "freq" / "value") + .set_coercer([this, chan_idx](const double freq){ + return this->set_rx_frequency(freq, chan_idx); + }) + .set_publisher([this, chan_idx](){ + return this->get_rx_frequency(chan_idx); + }) + ; + subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") + .set(meta_range_t(AD9361_MIN_FREQ, AD9361_MAX_FREQ, 1.0)) + .add_coerced_subscriber([](const meta_range_t &){ + throw uhd::runtime_error( + "Attempting to update freq range!"); + }) + ; + // TX bandwidth + subtree->create<double>(tx_fe_path / "bandwidth" / "value") + .set(AD9361_TX_MAX_BANDWIDTH) + .set_coercer([this, chan_idx](const double bw){ + return this->set_tx_bandwidth(bw, chan_idx); + }) + .set_publisher([this, chan_idx](){ + return this->get_tx_bandwidth(chan_idx); + }) + ; + subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") + .set(meta_range_t(AD9361_TX_MIN_BANDWIDTH, AD9361_TX_MAX_BANDWIDTH)) + .add_coerced_subscriber([](const meta_range_t &){ + throw uhd::runtime_error( + "Attempting to update bandwidth range!"); + }) + ; + // RX bandwidth + subtree->create<double>(rx_fe_path / "bandwidth" / "value") + .set(AD9361_RX_MAX_BANDWIDTH) + .set_coercer([this, chan_idx](const double bw){ + return this->set_rx_bandwidth(bw, chan_idx); + }) + ; + subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") + .set(meta_range_t(AD9361_RX_MIN_BANDWIDTH, AD9361_RX_MAX_BANDWIDTH)) + .add_coerced_subscriber([](const meta_range_t &){ + throw uhd::runtime_error( + "Attempting to update bandwidth range!"); + }) + ; + + // TX gains + const std::vector<std::string> tx_gain_names = ad9361_ctrl::get_gain_names("TX1"); + for (auto tx_gain_name : tx_gain_names) { + subtree->create<double>(tx_fe_path / "gains" / tx_gain_name / "value") + .set_coercer([this, chan_idx](const double gain){ + return this->set_tx_gain(gain, chan_idx); + }) + .set_publisher([this, chan_idx](){ + return radio_ctrl_impl::get_tx_gain(chan_idx); + }) + ; + subtree->create<meta_range_t>(tx_fe_path / "gains" / tx_gain_name / "range") + .add_coerced_subscriber([](const meta_range_t &){ + throw uhd::runtime_error( + "Attempting to update gain range!"); + }) + .set_publisher([this](){ + return meta_range_t( + AD9361_MIN_TX_GAIN, + AD9361_MAX_TX_GAIN, + AD9361_TX_GAIN_STEP + ); + }) + ; + } + + // RX gains + const std::vector<std::string> rx_gain_names = ad9361_ctrl::get_gain_names("RX1"); + for (auto rx_gain_name : rx_gain_names) { + subtree->create<double>(rx_fe_path / "gains" / rx_gain_name / "value") + .set_coercer([this, chan_idx](const double gain){ + return this->set_rx_gain(gain, chan_idx); + }) + .set_publisher([this, chan_idx](){ + return radio_ctrl_impl::get_rx_gain(chan_idx); + }) + ; + + subtree->create<meta_range_t>(rx_fe_path / "gains" / rx_gain_name / "range") + .add_coerced_subscriber([](const meta_range_t &){ + throw uhd::runtime_error( + "Attempting to update gain range!"); + }) + .set_publisher([this](){ + return meta_range_t( + AD9361_MIN_RX_GAIN, + AD9361_MAX_RX_GAIN, + AD9361_RX_GAIN_STEP + ); + }) + ; + } + + // TX LO lock sensor ////////////////////////////////////////////////////// + // Note: The AD9361 LO lock sensors are generated programmatically in + // set_rpc_client(). The actual lo_locked publisher is also set there. + subtree->create<sensor_value_t>(tx_fe_path / "sensors" / "lo_locked") + .set(sensor_value_t("all_los", false, "locked", "unlocked")) + .add_coerced_subscriber([](const sensor_value_t &){ + throw uhd::runtime_error( + "Attempting to write to sensor!"); + }) + .set_publisher([this](){ + return sensor_value_t( + "all_los", + this->get_lo_lock_status(TX_DIRECTION), + "locked", "unlocked" + ); + }) + ; + // RX LO lock sensor (see not on TX LO lock sensor) + subtree->create<sensor_value_t>(rx_fe_path / "sensors" / "lo_locked") + .set(sensor_value_t("all_los", false, "locked", "unlocked")) + .add_coerced_subscriber([](const sensor_value_t &){ + throw uhd::runtime_error( + "Attempting to write to sensor!"); + }) + .set_publisher([this](){ + return sensor_value_t( + "all_los", + this->get_lo_lock_status(RX_DIRECTION), + "locked", "unlocked" + ); + }) + ; +} + +void neon_radio_ctrl_impl::_init_prop_tree() +{ + const fs_path fe_base = fs_path("dboards") / _radio_slot; + for (size_t chan_idx = 0; chan_idx < NEON_NUM_CHANS; chan_idx++) { + this->_init_frontend_subtree( + _tree->subtree(fe_base), chan_idx); + } + + _tree->create<eeprom_map_t>(_root_path / "eeprom") + .set(eeprom_map_t()); + + _tree->create<int>("rx_codecs" / _radio_slot / "gains"); + _tree->create<int>("tx_codecs" / _radio_slot / "gains"); + _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("AD9361 Dual ADC"); + _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("AD9361 Dual DAC"); + + if (not _tree->exists("tick_rate")) { + _tree->create<double>("tick_rate") + .set_coercer([this](double tick_rate){ + return this->set_rate(tick_rate); + }) + .set_publisher([this](){ + return this->get_rate(); + }) + ; + } else { + UHD_LOG_WARNING(unique_id(), "Cannot set tick_rate again"); + } + + // *****FP_GPIO************************ + for(const auto& attr: usrp::gpio_atr::gpio_attr_map) { + if (not _tree->exists(fs_path("gpio") / "FP0" / attr.second)){ + switch (attr.first){ + case usrp::gpio_atr::GPIO_SRC: + //FIXME: move this creation of this branch of ptree out side of radio impl; + // since there's no data dependency between radio and SRC setting for FP0 + _tree->create<std::vector<std::string>>(fs_path("gpio") / "FP0" / attr.second) + .set(std::vector<std::string>( + 32, + usrp::gpio_atr::default_attr_value_map.at(attr.first))) + .add_coerced_subscriber([this, attr]( + const std::vector<std::string> str_val){ + uint32_t radio_src_value = 0; + uint32_t master_value = 0; + for(size_t i = 0 ; i<str_val.size(); i++){ + if(str_val[i] == "PS"){ + master_value += 1<<i;; + }else{ + auto port_num = _extract_port_number(str_val[i],_tree); + radio_src_value =(1<<(2*i))*port_num + radio_src_value; + } + } + _rpcc->notify_with_token("set_fp_gpio_master", master_value); + _rpcc->notify_with_token("set_fp_gpio_radio_src", radio_src_value); + }); + break; + case usrp::gpio_atr::GPIO_CTRL: + case usrp::gpio_atr::GPIO_DDR: + _tree->create<std::vector<std::string>>(fs_path("gpio") / "FP0" / attr.second) + .set(std::vector<std::string>( + 32, + usrp::gpio_atr::default_attr_value_map.at(attr.first))) + .add_coerced_subscriber([this, attr]( + const std::vector<std::string> str_val){ + uint32_t val = 0; + for(size_t i = 0 ; i < str_val.size() ; i++){ + val += usrp::gpio_atr::gpio_attr_value_pair.at(attr.second).at(str_val[i])<<i; + } + _fp_gpio->set_gpio_attr(attr.first, val); + }); + break; + case usrp::gpio_atr::GPIO_READBACK:{ + _tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second) + .set_publisher([this](){ + return _fp_gpio->read_gpio(); + } + ); + } + break; + default: + _tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second) + .set(0) + .add_coerced_subscriber([this, attr](const uint32_t val){ + _fp_gpio->set_gpio_attr(attr.first, val); + }); + } + }else{ + switch (attr.first){ + case usrp::gpio_atr::GPIO_SRC: + break; + case usrp::gpio_atr::GPIO_CTRL: + case usrp::gpio_atr::GPIO_DDR: + _tree->access<std::vector<std::string>>(fs_path("gpio") / "FP0" / attr.second) + .set(std::vector<std::string>(32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) + .add_coerced_subscriber([this, attr](const std::vector<std::string> str_val){ + uint32_t val = 0; + for(size_t i = 0 ; i < str_val.size() ; i++){ + val += usrp::gpio_atr::gpio_attr_value_pair.at(attr.second).at(str_val[i])<<i; + } + _fp_gpio->set_gpio_attr(attr.first, val); + }); + break; + case usrp::gpio_atr::GPIO_READBACK: + break; + default: + _tree->access<uint32_t>(fs_path("gpio") / "FP0" / attr.second) + .set(0) + .add_coerced_subscriber([this, attr](const uint32_t val){ + _fp_gpio->set_gpio_attr(attr.first, val); + }); + } + } + } +} + +void neon_radio_ctrl_impl::_init_codec() +{ + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + this->set_rx_gain(NEON_DEFAULT_GAIN, chan); + this->set_rx_frequency(NEON_DEFAULT_FREQ, chan); + this->set_rx_antenna(NEON_DEFAULT_RX_ANTENNA, chan); + this->set_rx_bandwidth(NEON_DEFAULT_BANDWIDTH, chan); + _ad9361->set_dc_offset_auto(rx_fe, NEON_DEFAULT_AUTO_DC_OFFSET); + _ad9361->set_iq_balance_auto(rx_fe, NEON_DEFAULT_AUTO_IQ_BALANCE); + _ad9361->set_agc(rx_fe, NEON_DEFAULT_AGC_ENABLE); + std::string tx_fe = get_which_ad9361_chain(TX_DIRECTION, chan); + this->set_tx_gain(NEON_DEFAULT_GAIN, chan); + this->set_tx_frequency(NEON_DEFAULT_FREQ, chan); + this->set_tx_bandwidth(NEON_DEFAULT_BANDWIDTH, chan); + } +} + +void neon_radio_ctrl_impl::_init_mpm_sensors( + const direction_t dir, + const size_t chan_idx +) { + const std::string trx = (dir == RX_DIRECTION) ? "RX" : "TX"; + const fs_path fe_path = + fs_path("dboards") / _radio_slot / + (dir == RX_DIRECTION ? "rx_frontends" : "tx_frontends") / chan_idx; + auto sensor_list = + _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", trx); + UHD_LOG_TRACE(unique_id(), + "Chan " << chan_idx << ": Found " + << sensor_list.size() << " " << trx << " sensors."); + for (const auto &sensor_name : sensor_list) { + UHD_LOG_TRACE(unique_id(), + "Adding " << trx << " sensor " << sensor_name); + _tree->create<sensor_value_t>(fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t &){ + throw uhd::runtime_error( + "Attempting to write to sensor!"); + }) + .set_publisher([this, trx, sensor_name, chan_idx](){ + return sensor_value_t( + this->_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + this->_rpc_prefix + "get_sensor", + trx, sensor_name, chan_idx) + ); + }) + ; + } +} + diff --git a/host/lib/usrp/dboard/neon/neon_regs.hpp b/host/lib/usrp/dboard/neon/neon_regs.hpp new file mode 100644 index 000000000..39ceca01c --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_regs.hpp @@ -0,0 +1,26 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_NEON_REGS_HPP +#define INCLUDED_NEON_REGS_HPP + +#include <uhd/config.hpp> +#include <cstdint> + +static const uint32_t TX_AMP_SHIFT = 17; +static const uint32_t TRX_SW_SHIFT = 14; +static const uint32_t RX_SW1_SHIFT = 0; +static const uint32_t RX_SW2_SHIFT = 3; +static const uint32_t RX_SW3_SHIFT = 6; +static const uint32_t TX_SW1_SHIFT = 8; +static const uint32_t TX_SW2_SHIFT = 11; + +static const uint32_t TRX_LED_GRN_SHIFT = 0; +static const uint32_t TX_LED_RED_SHIFT = 1; +static const uint32_t RX_LED_GRN_SHIFT = 2; + +#endif /* INCLUDED_NEON_REGS_HPP */ + diff --git a/host/lib/usrp/mpmd/mpmd_devices.hpp b/host/lib/usrp/mpmd/mpmd_devices.hpp index 7e12fe31a..ab0cc1271 100644 --- a/host/lib/usrp/mpmd/mpmd_devices.hpp +++ b/host/lib/usrp/mpmd/mpmd_devices.hpp @@ -17,7 +17,8 @@ static constexpr char MPM_CATCHALL_DEVICE_TYPE[] = "mpm"; // to make decisions. This vector is for filtering purposes. static const std::vector<std::string> MPM_DEVICE_TYPES = { MPM_CATCHALL_DEVICE_TYPE, - "n3xx" + "n3xx", + "e3xx", }; #endif /* INCLUDED_MPMD_DEVICES_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_image_loader.cpp b/host/lib/usrp/mpmd/mpmd_image_loader.cpp index fa09dc08b..e0b85f897 100644 --- a/host/lib/usrp/mpmd/mpmd_image_loader.cpp +++ b/host/lib/usrp/mpmd/mpmd_image_loader.cpp @@ -81,7 +81,9 @@ uhd::usrp::component_file_t generate_component( */ static bool mpmd_image_loader(const image_loader::image_loader_args_t &image_loader_args){ // See if any MPM devices with the given args are found - device_addrs_t devs = mpmd_find(image_loader_args.args); + device_addr_t find_hint = image_loader_args.args; + find_hint.set("find_all", "1"); // We need to find all devices + device_addrs_t devs = mpmd_find(find_hint); if (devs.size() != 1) { // TODO: Do we want to handle multiple devices here? @@ -189,4 +191,5 @@ UHD_STATIC_BLOCK(register_mpm_image_loader){ //TODO: 'n3xx' doesn't really fit the MPM abstraction, but this is simpler for the time being image_loader::register_image_loader("n3xx", mpmd_image_loader, recovery_instructions); + image_loader::register_image_loader("e3xx", mpmd_image_loader, recovery_instructions); } |