aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp
diff options
context:
space:
mode:
authorBrent Stapleton <brent.stapleton@ettus.com>2018-04-17 19:33:53 -0700
committerBrent Stapleton <bstapleton@g.hmc.edu>2018-07-18 15:37:27 -0700
commit7d28d9140e2c57db36b37d60d9ce4bff86357bac (patch)
treeb775d13d8b0386bea39adaf279511e24c51f35f6 /host/lib/usrp
parent300a5e3f6e5e845b4b8d093222e1c51ca4640f79 (diff)
downloaduhd-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>
Diffstat (limited to 'host/lib/usrp')
-rw-r--r--host/lib/usrp/common/CMakeLists.txt4
-rw-r--r--host/lib/usrp/common/ad9361_ctrl.cpp8
-rw-r--r--host/lib/usrp/dboard/CMakeLists.txt2
-rw-r--r--host/lib/usrp/dboard/neon/CMakeLists.txt15
-rw-r--r--host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp162
-rw-r--r--host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp26
-rw-r--r--host/lib/usrp/dboard/neon/neon_bands.cpp157
-rw-r--r--host/lib/usrp/dboard/neon/neon_constants.hpp51
-rw-r--r--host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp621
-rw-r--r--host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp311
-rw-r--r--host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp495
-rw-r--r--host/lib/usrp/dboard/neon/neon_regs.hpp26
-rw-r--r--host/lib/usrp/mpmd/mpmd_devices.hpp3
-rw-r--r--host/lib/usrp/mpmd/mpmd_image_loader.cpp5
14 files changed, 1874 insertions, 12 deletions
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);
}