diff options
Diffstat (limited to 'host/lib/usrp/dboard')
39 files changed, 5956 insertions, 5923 deletions
diff --git a/host/lib/usrp/dboard/e3xx/CMakeLists.txt b/host/lib/usrp/dboard/e3xx/CMakeLists.txt index 5d452fb53..6a14c0766 100644 --- a/host/lib/usrp/dboard/e3xx/CMakeLists.txt +++ b/host/lib/usrp/dboard/e3xx/CMakeLists.txt @@ -1,13 +1,14 @@ # # Copyright 2018 Ettus Research, a National Instruments Company +# Copyright 2019 Ettus Research, a National Instruments Brand # # SPDX-License-Identifier: GPL-3.0-or-later # IF(ENABLE_E300 OR ENABLE_E320) LIST(APPEND E3XX_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_radio_ctrl_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_radio_ctrl_init.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_radio_control_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_radio_control_init.cpp ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_ad9361_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/e3xx_bands.cpp ) @@ -16,14 +17,14 @@ ENDIF(ENABLE_E300 OR ENABLE_E320) IF(ENABLE_E300) LIST(APPEND E300_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/e31x_radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e31x_radio_control_impl.cpp ) LIBUHD_APPEND_SOURCES(${E300_SOURCES}) ENDIF(ENABLE_E300) IF(ENABLE_E320) LIST(APPEND E320_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/e320_radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/e320_radio_control_impl.cpp ) LIBUHD_APPEND_SOURCES(${E320_SOURCES}) ENDIF(ENABLE_E320) diff --git a/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.cpp b/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.cpp new file mode 100644 index 000000000..b7524e04c --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.cpp @@ -0,0 +1,212 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "e31x_radio_control_impl.hpp" +#include "e31x_regs.hpp" +#include <uhd/rfnoc/registry.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +e31x_radio_control_impl::e31x_radio_control_impl(make_args_ptr make_args) + : e3xx_radio_control_impl(std::move(make_args)) +{ + // Swap front ends for E310 + _fe_swap = true; + _init_mpm(); +} + +e31x_radio_control_impl::~e31x_radio_control_impl() +{ + RFNOC_LOG_TRACE("e31x_radio_control_impl::dtor()"); +} + +/****************************************************************************** + * API Calls + *****************************************************************************/ +uint32_t e31x_radio_control_impl::get_tx_switches( + const size_t chan, + const double freq +) { + RFNOC_LOG_TRACE( + "Update all TX freq related switches. f=" << freq << " Hz, " + ); + + size_t fe_chan = _fe_swap ? (chan ? 0 : 1): chan; + + auto tx_sw1 = TX_SW1_LB_2750; // SW1 = 0 + auto vctxrx_sw = VCTXRX_SW_OFF; + auto tx_bias = (fe_chan == 0) ? TX1_BIAS_LB_ON: TX2_BIAS_LB_ON; + + const auto band = e3xx_radio_control_impl::map_freq_to_tx_band(freq); + switch(band) { + case tx_band::LB_80: + tx_sw1 = TX_SW1_LB_80; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_160: + tx_sw1 = TX_SW1_LB_160; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_225: + tx_sw1 = TX_SW1_LB_225; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_400: + tx_sw1 = TX_SW1_LB_400; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_575: + tx_sw1 = TX_SW1_LB_575; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_1000: + tx_sw1 = TX_SW1_LB_1000; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_1700: + tx_sw1 = TX_SW1_LB_1700; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::LB_2750: + tx_sw1 = TX_SW1_LB_2750; + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; + break; + case tx_band::HB: + tx_sw1 = TX_SW1_LB_80; + vctxrx_sw = VCTXRX_SW_TX_HB; + tx_bias = (fe_chan == 0) ? TX1_BIAS_HB_ON: TX2_BIAS_HB_ON; + break; + case tx_band::INVALID_BAND: + RFNOC_LOG_ERROR( + "Cannot map TX frequency to band: " << freq); + UHD_THROW_INVALID_CODE_PATH(); + break; + } + auto tx_regs = 0 | + vctxrx_sw << VCTXRX_SW_SHIFT | + tx_bias << TX_BIAS_SHIFT | + tx_sw1 << TX_SW1_SHIFT; + return tx_regs; +} + +uint32_t e31x_radio_control_impl::get_rx_switches( + const size_t chan, + const double freq, + const std::string &ant +){ + RFNOC_LOG_TRACE( + "Update all RX freq related switches. f=" << freq << " Hz, " + ); + + size_t fe_chan = _fe_swap ? (chan ? 0 : 1): chan; + + // Default to OFF + auto rx_sw1 = RX_SW1_OFF; + auto rx_swc = RX_SWC_OFF; + auto rx_swb = RX_SWB_OFF; + auto vctxrx_sw = VCTXRX_SW_OFF; + auto vcrx_sw = VCRX_SW_LB; + if (ant == "TX/RX") { + vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_RX: VCTXRX2_SW_RX; + } + + RFNOC_LOG_INFO("RX freq = " << freq); + const auto band = e3xx_radio_control_impl::map_freq_to_rx_band(freq); + RFNOC_LOG_INFO("RX band = " << int(band)); + + switch(band) { + case rx_band::LB_B2: + rx_sw1 = RX_SW1_LB_B2; + rx_swc = RX_SWC_LB_B2; + rx_swb = RX_SWB_OFF; + break; + case rx_band::LB_B3: + rx_sw1 = RX_SW1_LB_B3; + rx_swc = RX_SWC_LB_B3; + rx_swb = RX_SWB_OFF; + break; + case rx_band::LB_B4: + rx_sw1 = RX_SW1_LB_B4; + rx_swc = RX_SWC_LB_B4; + rx_swb = RX_SWB_OFF; + break; + case rx_band::LB_B5: + rx_sw1 = RX_SW1_LB_B5; + rx_swc = RX_SWC_OFF; + rx_swb = RX_SWB_LB_B5; + break; + case rx_band::LB_B6: + rx_sw1 = RX_SW1_LB_B6; + rx_swc = RX_SWC_OFF; + rx_swb = RX_SWB_LB_B6; + break; + case rx_band::LB_B7: + rx_sw1 = RX_SW1_LB_B7; + rx_swc = RX_SWC_OFF; + rx_swb = RX_SWB_LB_B7; + break; + case rx_band::HB: + rx_sw1 = RX_SW1_OFF; + rx_swc = RX_SWC_OFF; + rx_swb = RX_SWB_OFF; + vcrx_sw = VCRX_SW_HB; + break; + case rx_band::INVALID_BAND: + RFNOC_LOG_ERROR("Cannot map RX frequency to band: " << freq); + UHD_THROW_INVALID_CODE_PATH(); + break; + } + RFNOC_LOG_INFO("RX SW1 = " << rx_sw1); + RFNOC_LOG_INFO("RX SWC = " << rx_swc); + RFNOC_LOG_INFO("RX SWB = " << rx_swb); + RFNOC_LOG_INFO("RX VCRX_SW = " << vcrx_sw); + RFNOC_LOG_INFO("RX VCTXRX_SW = " << vctxrx_sw); + + auto rx_regs = 0 | + vcrx_sw << VCRX_SW_SHIFT | + vctxrx_sw << VCTXRX_SW_SHIFT | + rx_swc << RX_SWC_SHIFT | + rx_swb << RX_SWB_SHIFT | + rx_sw1 << RX_SW1_SHIFT; + return rx_regs; +} + +uint32_t e31x_radio_control_impl::get_idle_switches() +{ + uint32_t idle_regs = VCRX_SW_OFF << VCRX_SW_SHIFT | + VCTXRX_SW_OFF << VCTXRX_SW_SHIFT | + TX_BIAS_OFF << TX_BIAS_SHIFT | + RX_SWC_OFF << RX_SWC_SHIFT | + RX_SWB_OFF << RX_SWB_SHIFT | + RX_SW1_OFF << RX_SW1_SHIFT | + TX_SW1_LB_2750 << TX_SW1_SHIFT; + return idle_regs; +} + +uint32_t e31x_radio_control_impl::get_idle_led() +{ + return 0; +} + +uint32_t e31x_radio_control_impl::get_rx_led() +{ + return 1 << LED_RX_RX_SHIFT; +} + +uint32_t e31x_radio_control_impl::get_tx_led() +{ + return 1 << LED_TXRX_TX_SHIFT; +} + +uint32_t e31x_radio_control_impl::get_txrx_led() +{ + return 1 << LED_TXRX_RX_SHIFT; +} + +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + e31x_radio_control, RADIO_BLOCK, E310, "Radio", true, "radio_clk", "bus_clk") diff --git a/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.hpp index 581a90c8e..c51d74203 100644 --- a/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/e3xx/e31x_radio_control_impl.hpp @@ -1,5 +1,6 @@ // // Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -8,30 +9,37 @@ #define INCLUDED_LIBUHD_RFNOC_E31X_RADIO_CTRL_IMPL_HPP #include "e3xx_constants.hpp" -#include "e3xx_radio_ctrl_impl.hpp" +#include "e3xx_radio_control_impl.hpp" -namespace uhd { - namespace rfnoc { +namespace { +static constexpr char E31x_GPIO_BANK[] = "INT0"; +} -/*! \brief Provide access to an E31X radio. +namespace uhd { namespace rfnoc { + +/*! Provide access to an E31X radio. + * + * This class only contains hardware-specific things that are different between + * E31X and E320. */ -class e31x_radio_ctrl_impl : public e3xx_radio_ctrl_impl +class e31x_radio_control_impl : public e3xx_radio_control_impl { public: /************************************************************************ - * Structors + * Structors and deinit ***********************************************************************/ - e31x_radio_ctrl_impl( - const make_args_t &make_args - ); - virtual ~e31x_radio_ctrl_impl(); + e31x_radio_control_impl(make_args_ptr make_args); + virtual ~e31x_radio_control_impl(); -protected: + std::vector<std::string> get_gpio_banks() const + { + return {E31x_GPIO_BANK}; + } +private: /************************************************************************** * ATR/ Switches Types *************************************************************************/ - enum tx_sw1_t { TX_SW1_LB_80 = 7, TX_SW1_LB_160 = 6, @@ -93,32 +101,13 @@ protected: }; /************************************************************************ - * API calls + * E3XX API calls ***********************************************************************/ - virtual bool check_radio_config(); - const std::string get_default_timing_mode() { return TIMING_MODE_1R1T; }; - /*! 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); - uint32_t get_rx_switches( const size_t chan, const double freq, @@ -136,7 +125,7 @@ protected: uint32_t get_rx_led(); uint32_t get_txrx_led(); uint32_t get_idle_led(); -}; /* class radio_ctrl_impl */ +}; }} /* namespace uhd::rfnoc */ diff --git a/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.cpp deleted file mode 100644 index 60df247a2..000000000 --- a/host/lib/usrp/dboard/e3xx/e31x_radio_ctrl_impl.cpp +++ /dev/null @@ -1,300 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "e31x_radio_ctrl_impl.hpp" -#include "e31x_regs.hpp" - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -e31x_radio_ctrl_impl::e31x_radio_ctrl_impl( - const make_args_t &make_args -): block_ctrl_base(make_args) -{ - // Swap front ends for E310 - _fe_swap = true; -} - -e31x_radio_ctrl_impl::~e31x_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "e31x_radio_ctrl_impl::dtor() "); -} - -/****************************************************************************** - * API Calls - *****************************************************************************/ -bool e31x_radio_ctrl_impl::check_radio_config() -{ - // mapping of frontend to radio perif index - static const size_t FE0 = 1; - static const size_t FE1 = 0; - 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(true, 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; -} - -/* 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 e31x_radio_ctrl_impl::loopback_self_test( - std::function<void(uint32_t)> poker_functor, std::function<uint64_t()> peeker_functor) -{ - // Save current rate before running this test - const double current_rate = this->get_rate(); - // Set 2R2T mode, stream on all channels - this->set_streaming_mode(true, false, true, false); - // 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); - // Switch back to current rate - this->set_rate(current_rate); -} - - -uint32_t e31x_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, " - ); - - size_t fe_chan = _fe_swap ? (chan ? 0 : 1): chan; - - auto tx_sw1 = TX_SW1_LB_2750; // SW1 = 0 - auto vctxrx_sw = VCTXRX_SW_OFF; - auto tx_bias = (fe_chan == 0) ? TX1_BIAS_LB_ON: TX2_BIAS_LB_ON; - - const auto band = e3xx_radio_ctrl_impl::map_freq_to_tx_band(freq); - switch(band) { - case tx_band::LB_80: - tx_sw1 = TX_SW1_LB_80; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_160: - tx_sw1 = TX_SW1_LB_160; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_225: - tx_sw1 = TX_SW1_LB_225; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_400: - tx_sw1 = TX_SW1_LB_400; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_575: - tx_sw1 = TX_SW1_LB_575; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_1000: - tx_sw1 = TX_SW1_LB_1000; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_1700: - tx_sw1 = TX_SW1_LB_1700; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::LB_2750: - tx_sw1 = TX_SW1_LB_2750; - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_TX_LB: VCTXRX2_SW_TX_LB; - break; - case tx_band::HB: - tx_sw1 = TX_SW1_LB_80; - vctxrx_sw = VCTXRX_SW_TX_HB; - tx_bias = (fe_chan == 0) ? TX1_BIAS_HB_ON: TX2_BIAS_HB_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 = 0 | - vctxrx_sw << VCTXRX_SW_SHIFT | - tx_bias << TX_BIAS_SHIFT | - tx_sw1 << TX_SW1_SHIFT; - return tx_regs; -} - -uint32_t e31x_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, " - ); - - size_t fe_chan = _fe_swap ? (chan ? 0 : 1): chan; - - // Default to OFF - auto rx_sw1 = RX_SW1_OFF; - auto rx_swc = RX_SWC_OFF; - auto rx_swb = RX_SWB_OFF; - auto vctxrx_sw = VCTXRX_SW_OFF; - auto vcrx_sw = VCRX_SW_LB; - if (ant == "TX/RX") { - vctxrx_sw = (fe_chan == 0) ? VCTXRX1_SW_RX: VCTXRX2_SW_RX; - } - - const auto band = e3xx_radio_ctrl_impl::map_freq_to_rx_band(freq); - - switch(band) { - case rx_band::LB_B2: - rx_sw1 = RX_SW1_LB_B2; - rx_swc = RX_SWC_LB_B2; - rx_swb = RX_SWB_OFF; - break; - case rx_band::LB_B3: - rx_sw1 = RX_SW1_LB_B3; - rx_swc = RX_SWC_LB_B3; - rx_swb = RX_SWB_OFF; - break; - case rx_band::LB_B4: - rx_sw1 = RX_SW1_LB_B4; - rx_swc = RX_SWC_LB_B4; - rx_swb = RX_SWB_OFF; - break; - case rx_band::LB_B5: - rx_sw1 = RX_SW1_LB_B5; - rx_swc = RX_SWC_OFF; - rx_swb = RX_SWB_LB_B5; - break; - case rx_band::LB_B6: - rx_sw1 = RX_SW1_LB_B6; - rx_swc = RX_SWC_OFF; - rx_swb = RX_SWB_LB_B6; - break; - case rx_band::LB_B7: - rx_sw1 = RX_SW1_LB_B7; - rx_swc = RX_SWC_OFF; - rx_swb = RX_SWB_LB_B7; - break; - case rx_band::HB: - rx_sw1 = RX_SW1_OFF; - rx_swc = RX_SWC_OFF; - rx_swb = RX_SWB_OFF; - vcrx_sw = VCRX_SW_HB; - break; - case rx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), - "Cannot map RX frequency to band: " << freq); - UHD_THROW_INVALID_CODE_PATH(); - break; - } - - UHD_LOG_TRACE(unique_id(), - "RX band = " << int(band) << "RX SW1 = " << rx_sw1 << "RX SWC = " << rx_swc - << "RX SWB = " << rx_swb << "RX VCRX_SW = " << vcrx_sw - << "RX VCTXRX_SW = " << vctxrx_sw); - - auto rx_regs = 0 | - vcrx_sw << VCRX_SW_SHIFT | - vctxrx_sw << VCTXRX_SW_SHIFT | - rx_swc << RX_SWC_SHIFT | - rx_swb << RX_SWB_SHIFT | - rx_sw1 << RX_SW1_SHIFT; - return rx_regs; -} - -uint32_t e31x_radio_ctrl_impl::get_idle_switches() -{ - uint32_t idle_regs = VCRX_SW_OFF << VCRX_SW_SHIFT | - VCTXRX_SW_OFF << VCTXRX_SW_SHIFT | - TX_BIAS_OFF << TX_BIAS_SHIFT | - RX_SWC_OFF << RX_SWC_SHIFT | - RX_SWB_OFF << RX_SWB_SHIFT | - RX_SW1_OFF << RX_SW1_SHIFT | - TX_SW1_LB_2750 << TX_SW1_SHIFT; - return idle_regs; -} - -uint32_t e31x_radio_ctrl_impl::get_idle_led() -{ - return 0; -} - -uint32_t e31x_radio_ctrl_impl::get_rx_led() -{ - return 1 << LED_RX_RX_SHIFT; -} - -uint32_t e31x_radio_ctrl_impl::get_tx_led() -{ - return 1 << LED_TXRX_TX_SHIFT; -} - -uint32_t e31x_radio_ctrl_impl::get_txrx_led() -{ - return 1 << LED_TXRX_RX_SHIFT; -} -UHD_RFNOC_BLOCK_REGISTER(e31x_radio_ctrl, "E31XRadio"); diff --git a/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.cpp b/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.cpp new file mode 100644 index 000000000..df325bb75 --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.cpp @@ -0,0 +1,183 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "e320_radio_control_impl.hpp" +#include "e320_regs.hpp" +#include <uhd/rfnoc/registry.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +e320_radio_control_impl::e320_radio_control_impl(make_args_ptr make_args) + : e3xx_radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("e320_radio_control_impl::ctor()"); + // Don't swap front ends for E320 + _fe_swap = false; + _init_mpm(); +} + +e320_radio_control_impl::~e320_radio_control_impl() +{ + RFNOC_LOG_TRACE("e320_radio_control_impl::dtor() "); +} + +/****************************************************************************** + * E320 API Calls + *****************************************************************************/ +uint32_t e320_radio_control_impl::get_tx_switches(const size_t chan, const double freq) +{ + RFNOC_LOG_TRACE("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 = e3xx_radio_control_impl::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: + RFNOC_LOG_ERROR("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 e320_radio_control_impl::get_rx_switches( + const size_t chan, const double freq, const std::string& ant) +{ + RFNOC_LOG_TRACE("Update all RX freq related switches. f=" << (freq / 1e6) << " MHz"); + // 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 = e3xx_radio_control_impl::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: + RFNOC_LOG_ERROR("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; +} + +uint32_t e320_radio_control_impl::get_idle_switches() +{ + 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; + return idle_regs; +} + +uint32_t e320_radio_control_impl::get_idle_led() +{ + return 0; +} + +uint32_t e320_radio_control_impl::get_rx_led() +{ + return 1 << TRX_LED_GRN_SHIFT; +} + +uint32_t e320_radio_control_impl::get_tx_led() +{ + return 1 << TX_LED_RED_SHIFT; +} + +uint32_t e320_radio_control_impl::get_txrx_led() +{ + return 1 << RX_LED_GRN_SHIFT; +} + +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + e320_radio_control, RADIO_BLOCK, E320, "Radio", true, "radio_clk", "bus_clk") diff --git a/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.hpp index 7f75cadc7..f781eb49d 100644 --- a/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/e3xx/e320_radio_control_impl.hpp @@ -8,20 +8,32 @@ # define INCLUDED_LIBUHD_RFNOC_E320_RADIO_CTRL_IMPL_HPP # include "e3xx_constants.hpp" -# include "e3xx_radio_ctrl_impl.hpp" +# include "e3xx_radio_control_impl.hpp" + +namespace { +static constexpr char E320_GPIO_BANK[] = "FP0"; +} namespace uhd { namespace rfnoc { /*! \brief Provide access to an E320 radio. + * + * This class only contains hardware-specific things that are different between + * E320 and E31X. */ -class e320_radio_ctrl_impl : public e3xx_radio_ctrl_impl +class e320_radio_control_impl : public e3xx_radio_control_impl { public: /************************************************************************ * Structors ***********************************************************************/ - e320_radio_ctrl_impl(const make_args_t& make_args); - virtual ~e320_radio_ctrl_impl(); + e320_radio_control_impl(make_args_ptr make_args); + virtual ~e320_radio_control_impl(); + + std::vector<std::string> get_gpio_banks() const + { + return {E320_GPIO_BANK}; + } protected: /************************************************************************** @@ -89,32 +101,13 @@ protected: enum tx_amp_t { TX_AMP_HF_ON = 2, TX_AMP_LF_ON = 1, TX_AMP_OFF = 3 }; /************************************************************************ - * API calls + * E3XX API calls ***********************************************************************/ - virtual bool check_radio_config(); - const std::string get_default_timing_mode() { return TIMING_MODE_2R2T; }; - /*! 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); - uint32_t get_rx_switches( const size_t chan, const double freq, const std::string& ant); @@ -126,7 +119,7 @@ protected: uint32_t get_rx_led(); uint32_t get_txrx_led(); uint32_t get_idle_led(); -}; /* class radio_ctrl_impl */ +}; }} /* namespace uhd::rfnoc */ diff --git a/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.cpp deleted file mode 100644 index c48cabc9c..000000000 --- a/host/lib/usrp/dboard/e3xx/e320_radio_ctrl_impl.cpp +++ /dev/null @@ -1,272 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "e320_radio_ctrl_impl.hpp" -#include "e320_regs.hpp" - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -e320_radio_ctrl_impl::e320_radio_ctrl_impl(const make_args_t& make_args) - : block_ctrl_base(make_args) -{ - UHD_LOG_TRACE(unique_id(), "Entering e320_radio_ctrl_impl ctor..."); - // Don't swap front ends for E320 - _fe_swap = false; -} - -e320_radio_ctrl_impl::~e320_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "e320_radio_ctrl_impl::dtor() "); -} - -/****************************************************************************** - * API Calls - *****************************************************************************/ -bool e320_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(true, 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; -} - -/* 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 e320_radio_ctrl_impl::loopback_self_test( - std::function<void(uint32_t)> poker_functor, std::function<uint64_t()> peeker_functor) -{ - // Save current rate before running this test - const double current_rate = this->get_rate(); - // 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); - // Switch back to current rate - this->set_rate(current_rate); -} - -uint32_t e320_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 = e3xx_radio_ctrl_impl::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 e320_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 = e3xx_radio_ctrl_impl::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; -} - -uint32_t e320_radio_ctrl_impl::get_idle_switches() -{ - 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; - return idle_regs; -} - -uint32_t e320_radio_ctrl_impl::get_idle_led() -{ - return 0; -} - -uint32_t e320_radio_ctrl_impl::get_rx_led() -{ - return 1 << TRX_LED_GRN_SHIFT; -} - -uint32_t e320_radio_ctrl_impl::get_tx_led() -{ - return 1 << TX_LED_RED_SHIFT; -} - -uint32_t e320_radio_ctrl_impl::get_txrx_led() -{ - return 1 << RX_LED_GRN_SHIFT; -} -UHD_RFNOC_BLOCK_REGISTER(e320_radio_ctrl, "NeonRadio"); diff --git a/host/lib/usrp/dboard/e3xx/e3xx_bands.cpp b/host/lib/usrp/dboard/e3xx/e3xx_bands.cpp index 001cf5d1b..83e96b3ec 100644 --- a/host/lib/usrp/dboard/e3xx/e3xx_bands.cpp +++ b/host/lib/usrp/dboard/e3xx/e3xx_bands.cpp @@ -5,7 +5,7 @@ // #include "e3xx_constants.hpp" -#include "e3xx_radio_ctrl_impl.hpp" +#include "e3xx_radio_control_impl.hpp" #include <uhd/utils/math.hpp> /* @@ -131,9 +131,9 @@ constexpr double E3XX_TX_LB_2750_MIN_FREQ = 1842.6e6; constexpr double E3XX_TX_HB_MIN_FREQ = 2940.0e6; } // namespace -e3xx_radio_ctrl_impl::rx_band e3xx_radio_ctrl_impl::map_freq_to_rx_band(const double freq) +e3xx_radio_control_impl::rx_band e3xx_radio_control_impl::map_freq_to_rx_band(const double freq) { - e3xx_radio_ctrl_impl::rx_band band; + e3xx_radio_control_impl::rx_band band; if (fp_compare_epsilon<double>(freq) < AD9361_RX_MIN_FREQ) { band = rx_band::INVALID_BAND; @@ -158,9 +158,9 @@ e3xx_radio_ctrl_impl::rx_band e3xx_radio_ctrl_impl::map_freq_to_rx_band(const do return band; } -e3xx_radio_ctrl_impl::tx_band e3xx_radio_ctrl_impl::map_freq_to_tx_band(const double freq) +e3xx_radio_control_impl::tx_band e3xx_radio_control_impl::map_freq_to_tx_band(const double freq) { - e3xx_radio_ctrl_impl::tx_band band; + e3xx_radio_control_impl::tx_band band; if (fp_compare_epsilon<double>(freq) < AD9361_TX_MIN_FREQ) { band = tx_band::INVALID_BAND; diff --git a/host/lib/usrp/dboard/e3xx/e3xx_constants.hpp b/host/lib/usrp/dboard/e3xx/e3xx_constants.hpp index 53f64d837..f883a7d72 100644 --- a/host/lib/usrp/dboard/e3xx/e3xx_constants.hpp +++ b/host/lib/usrp/dboard/e3xx/e3xx_constants.hpp @@ -12,12 +12,11 @@ #include <vector> 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_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_TX_MIN_BANDWIDTH = 20.0e6; // Hz +static constexpr double AD9361_TX_MAX_BANDWIDTH = 40.0e6; // Hz static constexpr double AD9361_TX_MIN_FREQ = 47.0e6; // Hz static constexpr double AD9361_TX_MAX_FREQ = 6.0e9; // Hz @@ -44,7 +43,10 @@ static constexpr double E3XX_DEFAULT_BANDWIDTH = 40e6; // Hz static constexpr char E3XX_DEFAULT_RX_ANTENNA[] = "RX2"; static constexpr char E3XX_DEFAULT_TX_ANTENNA[] = "TX/RX"; -static const std::vector<std::string> E3XX_RX_ANTENNAS = {"RX2", "TX/RX"}; +static const std::vector<std::string> E3XX_RX_ANTENNAS = { + E3XX_DEFAULT_RX_ANTENNA, E3XX_DEFAULT_TX_ANTENNA}; + +static constexpr char E3XX_GPIO_BANK[] = "INT0"; static constexpr size_t E3XX_NUM_CHANS = 2; diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp new file mode 100644 index 000000000..29381a53c --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp @@ -0,0 +1,621 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "e3xx_radio_control_impl.hpp" +#include "e3xx_constants.hpp" +#include <uhd/transport/chdr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include <cmath> +#include <cstdlib> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +/****************************************************************************** + * Structors + *****************************************************************************/ +e3xx_radio_control_impl::e3xx_radio_control_impl(make_args_ptr make_args) + : radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("Entering e3xx_radio_control_impl ctor..."); + UHD_ASSERT_THROW(get_block_id().get_block_count() == 0); + UHD_ASSERT_THROW( + std::max(get_num_output_ports(), get_num_input_ports()) == E3XX_NUM_CHANS); + UHD_ASSERT_THROW(get_mb_controller()); + _e3xx_mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller()); + UHD_ASSERT_THROW(_e3xx_mb_control); + _e3xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>( + _e3xx_mb_control->get_timekeeper(0)); + UHD_ASSERT_THROW(_e3xx_timekeeper); + _rpcc = _e3xx_mb_control->get_rpc_client(); + UHD_ASSERT_THROW(_rpcc); + RFNOC_LOG_TRACE("Instantiating AD9361 control object..."); + _ad9361 = make_rpc(_rpcc); + + _init_defaults(); + _init_peripherals(); + _init_prop_tree(); + + // Properties + for (auto& samp_rate_prop : _samp_rate_in) { + samp_rate_prop.set(_master_clock_rate); + } + for (auto& samp_rate_prop : _samp_rate_out) { + samp_rate_prop.set(_master_clock_rate); + } +} + +e3xx_radio_control_impl::~e3xx_radio_control_impl() +{ + RFNOC_LOG_TRACE("e3xx_radio_control_impl::dtor() "); +} + +void e3xx_radio_control_impl::deinit() +{ + _db_gpio.clear(); + _leds_gpio.clear(); + _fp_gpio.reset(); + _wb_ifaces.clear(); +} + + +/****************************************************************************** + * API Calls + *****************************************************************************/ +bool e3xx_radio_control_impl::check_topology(const std::vector<size_t>& connected_inputs, + const std::vector<size_t>& connected_outputs) +{ + if (!node_t::check_topology(connected_inputs, connected_outputs)) { + return false; + } + // Now we know that the connected ports are either 0 or 1 + + // Check if we're running a 2x1 or 1x2 configuration -- the device does not + // support this! + if ((connected_outputs.size() == 1 && connected_inputs.size() == 2) + || (connected_outputs.size() == 2 && connected_inputs.size() == 1)) { + const std::string err_msg("Invalid channel configuration: This device does not " + "support 1 TX x 2 RX or 2 TX x 1 RX configurations!"); + RFNOC_LOG_ERROR(err_msg); + throw uhd::runtime_error(err_msg); + } + // mapping of frontend to radio perif index + const size_t FE0 = _fe_swap ? 1 : 0; + const size_t FE1 = _fe_swap ? 0 : 1; + + const bool tx_fe0_active = std::any_of(connected_inputs.begin(), + connected_inputs.end(), + [FE0](const size_t port) { return port == FE0; }); + const bool tx_fe1_active = std::any_of(connected_inputs.begin(), + connected_inputs.end(), + [FE1](const size_t port) { return port == FE1; }); + const bool rx_fe0_active = std::any_of(connected_outputs.begin(), + connected_outputs.end(), + [FE0](const size_t port) { return port == FE0; }); + const bool rx_fe1_active = std::any_of(connected_outputs.begin(), + connected_outputs.end(), + [FE1](const size_t port) { return port == FE1; }); + RFNOC_LOG_TRACE("TX FE0 Active: " << tx_fe0_active); + RFNOC_LOG_TRACE("TX FE1 Active: " << tx_fe1_active); + RFNOC_LOG_TRACE("RX FE0 Active: " << rx_fe0_active); + RFNOC_LOG_TRACE("RX FE1 Active: " << rx_fe1_active); + + //setup the active chains in the codec + if (connected_inputs.size() + connected_outputs.size() == 0) { + // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock + this->set_streaming_mode(true, false, true, false); + } else { + this->set_streaming_mode( + tx_fe0_active, tx_fe1_active, rx_fe0_active, rx_fe1_active); + } + return true; +} + + +void e3xx_radio_control_impl::set_streaming_mode( + const bool tx1, const bool tx2, const bool rx1, const bool rx2) +{ + RFNOC_LOG_TRACE("Setting streaming mode...") + const size_t num_rx = rx1 + rx2; + const size_t num_tx = tx1 + tx2; + + // 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 + _ad9361->set_active_chains(true, false, true, false); + } else { + // setup the active chains in the codec + _ad9361->set_active_chains(tx1, tx2, rx1, rx2); + } + + // 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 1R1T mode + _ad9361->set_timing_mode(this->get_default_timing_mode()); + 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 e3xx_radio_control_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); +} + +double e3xx_radio_control_impl::set_rate(const double rate) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_DEBUG("Asking for clock rate " << rate / 1e6 << " MHz\n"); + // On E3XX, tick rate and samp rate are always the same + double actual_tick_rate = _ad9361->set_clock_rate(rate); + RFNOC_LOG_DEBUG("Actual clock rate " << actual_tick_rate / 1e6 << " MHz\n"); + set_tick_rate(actual_tick_rate); + radio_control_impl::set_rate(actual_tick_rate); + _e3xx_timekeeper->update_tick_rate(rate); + return rate; +} + +uhd::meta_range_t e3xx_radio_control_impl::get_rate_range() const +{ + return _ad9361->get_clock_rate_range(); +} + +/****************************************************************************** + * RF API calls + *****************************************************************************/ +void e3xx_radio_control_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") + % get_unique_id() % ant)); + } + radio_control_impl::set_tx_antenna(ant, chan); + // We can't actually set the TX antenna, so let's stop here. +} + +void e3xx_radio_control_impl::set_rx_antenna(const std::string& ant, const size_t chan) +{ + UHD_ASSERT_THROW(chan <= E3XX_NUM_CHANS); + if (std::find(E3XX_RX_ANTENNAS.begin(), E3XX_RX_ANTENNAS.end(), ant) + == E3XX_RX_ANTENNAS.end()) { + throw uhd::value_error( + str(boost::format("[%s] Requesting invalid RX antenna value: %s") + % get_unique_id() % ant)); + } + RFNOC_LOG_TRACE("Setting RX antenna to " << ant << " for chan " << chan); + + radio_control_impl::set_rx_antenna(ant, chan); + _set_atr_bits(chan); +} + +double e3xx_radio_control_impl::set_tx_frequency(const double freq, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); + std::lock_guard<std::mutex> l(_set_lock); + + double clipped_freq = uhd::clip(freq, AD9361_TX_MIN_FREQ, AD9361_TX_MAX_FREQ); + + double coerced_freq = + _ad9361->tune(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), clipped_freq); + radio_control_impl::set_tx_frequency(coerced_freq, chan); + // Front-end switching + _set_atr_bits(chan); + + return coerced_freq; +} + +double e3xx_radio_control_impl::set_rx_frequency(const double freq, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); + std::lock_guard<std::mutex> l(_set_lock); + + double clipped_freq = uhd::clip(freq, AD9361_RX_MIN_FREQ, AD9361_RX_MAX_FREQ); + + double coerced_freq = + _ad9361->tune(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), clipped_freq); + radio_control_impl::set_rx_frequency(coerced_freq, chan); + // Front-end switching + _set_atr_bits(chan); + + return coerced_freq; +} + +void e3xx_radio_control_impl::set_rx_agc(const bool enb, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_rx_agc(enb=" << enb << ", chan=" << chan << ")"); + const std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + _ad9361->set_agc(rx_fe, enb); +} + +double e3xx_radio_control_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, _fe_swap), bandwidth); + return radio_control_impl::set_rx_bandwidth(clipped_bw, chan); +} + +double e3xx_radio_control_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, _fe_swap), bandwidth); + return radio_control_impl::set_tx_bandwidth(clipped_bw, chan); +} + +double e3xx_radio_control_impl::set_tx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("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, _fe_swap), clip_gain); + radio_control_impl::set_tx_gain(clip_gain, chan); + return clip_gain; +} + +double e3xx_radio_control_impl::set_rx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + UHD_ASSERT_THROW(chan < get_num_output_ports()); + RFNOC_LOG_TRACE("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, _fe_swap), clip_gain); + radio_control_impl::set_rx_gain(clip_gain, chan); + return clip_gain; +} + +std::vector<std::string> e3xx_radio_control_impl::get_tx_antennas(const size_t) const +{ + return {E3XX_DEFAULT_TX_ANTENNA}; +} + +std::vector<std::string> e3xx_radio_control_impl::get_rx_antennas(const size_t) const +{ + return E3XX_RX_ANTENNAS; +} + +freq_range_t e3xx_radio_control_impl::get_tx_frequency_range(const size_t) const +{ + return freq_range_t(AD9361_TX_MIN_FREQ, AD9361_TX_MAX_FREQ, 1.0); +} + +freq_range_t e3xx_radio_control_impl::get_rx_frequency_range(const size_t) const +{ + return freq_range_t(AD9361_RX_MIN_FREQ, AD9361_RX_MAX_FREQ, 1.0); +} + +uhd::gain_range_t e3xx_radio_control_impl::get_tx_gain_range(const size_t) const +{ + return meta_range_t(AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN, AD9361_TX_GAIN_STEP); +} + +uhd::gain_range_t e3xx_radio_control_impl::get_rx_gain_range(const size_t) const +{ + return meta_range_t(AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN, AD9361_RX_GAIN_STEP); +} + +meta_range_t e3xx_radio_control_impl::get_tx_bandwidth_range(size_t) const +{ + return meta_range_t(AD9361_TX_MIN_BANDWIDTH, AD9361_TX_MAX_BANDWIDTH); +} + +meta_range_t e3xx_radio_control_impl::get_rx_bandwidth_range(size_t) const +{ + return meta_range_t(AD9361_RX_MIN_BANDWIDTH, AD9361_RX_MAX_BANDWIDTH); +} + +/************************************************************************** + * Calibration-Related API Calls + *************************************************************************/ +void e3xx_radio_control_impl::set_rx_dc_offset(const bool enb, size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_rx_dc_offset(enb=" << enb << ", chan=" << chan << ")"); + const std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + _ad9361->set_dc_offset_auto(rx_fe, enb); +} + +void e3xx_radio_control_impl::set_rx_iq_balance(const bool enb, size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_rx_iq_balance(enb=" << enb << ", chan=" << chan << ")"); + const std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + _ad9361->set_iq_balance_auto(rx_fe, enb); +} + +/************************************************************************** + * GPIO Controls + *************************************************************************/ +void e3xx_radio_control_impl::set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value) +{ + if (bank != get_gpio_banks().front()) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + if (!gpio_atr::gpio_attr_rev_map.count(attr)) { + RFNOC_LOG_ERROR("Invalid GPIO attr: " << attr); + throw uhd::key_error("Invalid GPIO attr!"); + } + + const gpio_atr::gpio_attr_t gpio_attr = gpio_atr::gpio_attr_rev_map.at(attr); + + if (gpio_attr == gpio_atr::GPIO_READBACK) { + RFNOC_LOG_WARNING("Cannot set READBACK attr."); + return; + } + + _fp_gpio->set_gpio_attr(gpio_attr, value); +} + +uint32_t e3xx_radio_control_impl::get_gpio_attr( + const std::string& bank, const std::string& attr) +{ + if (bank != get_gpio_banks().front()) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + + const gpio_atr::gpio_attr_t gpio_attr = gpio_atr::gpio_attr_rev_map.at(attr); + return _fp_gpio->get_attr_reg(gpio_attr); +} + +/****************************************************************************** + * Sensor API + *****************************************************************************/ +std::vector<std::string> e3xx_radio_control_impl::get_rx_sensor_names(const size_t) const +{ + return _rx_sensor_names; +} + +uhd::sensor_value_t e3xx_radio_control_impl::get_rx_sensor( + const std::string& sensor_name, const size_t chan) +{ + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "RX", sensor_name, chan)); +} + +std::vector<std::string> e3xx_radio_control_impl::get_tx_sensor_names(const size_t) const +{ + return _tx_sensor_names; +} + +uhd::sensor_value_t e3xx_radio_control_impl::get_tx_sensor( + const std::string& sensor_name, const size_t chan) +{ + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "TX", sensor_name, chan)); +} + +/* 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 e3xx_radio_control_impl::loopback_self_test(const size_t chan) +{ + // Save current rate before running this test + const double current_rate = this->get_rate(); + // Set 2R2T mode, stream on all channels + this->set_streaming_mode(true, true, true, true); + // This was in there in the E320 code, but the comments didn't make sense: + //this->set_streaming_mode(true, true, true, true); + // Set maximum rate for 2R2T mode + /* FIXME + * We're directly setting the master clock rate here because we want to + * avoid property propagation, etc, and we know that we're going to set it + * back once we're done + * this->set_rate(30.72e6); + */ + _ad9361->set_clock_rate(30.72e6); + // Put AD936x in loopback mode + _ad9361->data_port_loopback(true); + RFNOC_LOG_INFO("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; + // Write test word to codec_idle idle register (on TX side) + regs().poke32( + regmap::RADIO_BASE_ADDR + chan * regmap::REG_CHAN_OFFSET + regmap::REG_TX_IDLE_VALUE, word32); + + // Read back values - TX is lower 32-bits and RX is upper 32-bits + const uint32_t rb_tx = + regs().peek32(regmap::RADIO_BASE_ADDR + chan * regmap::REG_CHAN_OFFSET + regmap::REG_TX_IDLE_VALUE); + const uint32_t rb_rx = + regs().peek32(regmap::RADIO_BASE_ADDR + chan * regmap::REG_CHAN_OFFSET + regmap::REG_RX_DATA); + + // Compare TX and RX values to test word + bool test_fail = word32 != rb_tx or word32 != rb_rx; + if (test_fail) { + RFNOC_LOG_WARNING( + "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."); + } + } + RFNOC_LOG_INFO("CODEC loopback test passed"); + + // Zero out the idle data. + regs().poke32(regmap::RADIO_BASE_ADDR + chan * regmap::REG_CHAN_OFFSET + regmap::REG_TX_IDLE_VALUE, 0); + + // Take AD936x out of loopback mode + _ad9361->data_port_loopback(false); + this->set_streaming_mode(true, false, true, false); + // Switch back to current rate + // FIXME along with the other comment above + // this->set_rate(current_rate); + _ad9361->set_clock_rate(current_rate); +} + +void e3xx_radio_control_impl::_identify_with_leds(const int identify_duration) +{ + RFNOC_LOG_INFO( + "Running LED identification process for " << identify_duration << " seconds."); + 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)); + } +} + +void e3xx_radio_control_impl::_set_atr_bits(const size_t chan) +{ + const auto rx_freq = radio_control_impl::get_rx_frequency(chan); + const auto tx_freq = radio_control_impl::get_tx_frequency(chan); + const auto rx_ant = radio_control_impl::get_rx_antenna(chan); + const uint32_t rx_regs = this->get_rx_switches(chan, rx_freq, rx_ant); + const uint32_t tx_regs = this->get_tx_switches(chan, tx_freq); + const uint32_t idle_regs = this->get_idle_switches(); + + _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 bool is_txrx = rx_ant == "TX/RX"; + const int idle_led = 0; + const int rx_led = this->get_rx_led(); + const int tx_led = this->get_tx_led(); + const int txrx_led = this->get_txrx_led(); + + _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 e3xx_radio_control_impl::set_db_eeprom(const eeprom_map_t& db_eeprom) +{ + _rpcc->notify_with_token("set_db_eeprom", 0, db_eeprom); +} + +eeprom_map_t e3xx_radio_control_impl::get_db_eeprom() +{ + return _rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", 0); +} + +/************************************************************************** + * Filter API + *************************************************************************/ +std::vector<std::string> e3xx_radio_control_impl::get_rx_filter_names(const size_t) const +{ + return _rx_filter_names; +} + +uhd::filter_info_base::sptr e3xx_radio_control_impl::get_rx_filter( + const std::string& name, const size_t chan) +{ + return _ad9361->get_filter( + get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), name); +} + +void e3xx_radio_control_impl::set_rx_filter( + const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) +{ + _ad9361->set_filter( + get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), name, filter); +} + +std::vector<std::string> e3xx_radio_control_impl::get_tx_filter_names(const size_t) const +{ + return _tx_filter_names; +} + +uhd::filter_info_base::sptr e3xx_radio_control_impl::get_tx_filter( + const std::string& name, const size_t chan) +{ + return _ad9361->get_filter( + get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), name); +} + +void e3xx_radio_control_impl::set_tx_filter( + const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan) +{ + _ad9361->set_filter( + get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), name, filter); +} + + +/************************************************************************** + * Radio Identification API Calls + *************************************************************************/ +size_t e3xx_radio_control_impl::get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t) const +{ + // A and B are available here for backward compat + if (fe == "A" || fe == "0") { + return 0; + } + if (fe == "B" || fe == "1") { + return 1; + } + throw uhd::key_error(std::string("[E3xx] Invalid frontend: ") + fe); +} + +std::string e3xx_radio_control_impl::get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t) const +{ + if (chan == 0) { + return "0"; + } + if (chan == 1) { + return "1"; + } + throw uhd::lookup_error( + std::string("[E3xx] Invalid channel: ") + std::to_string(chan)); +} + +std::string e3xx_radio_control_impl::get_fe_name( + const size_t, const uhd::direction_t) const +{ + return "E3xx"; +} diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.hpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.hpp new file mode 100644 index 000000000..f49cde64a --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.hpp @@ -0,0 +1,293 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP + +#include "e3xx_ad9361_iface.hpp" +#include <uhd/types/eeprom.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <uhd/rfnoc/filter_node.hpp> +#include <uhdlib/rfnoc/radio_control_impl.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> +#include <uhdlib/usrp/cores/gpio_atr_3000.hpp> +#include <mutex> + +namespace uhd { namespace rfnoc { + +namespace e3xx_regs { + constexpr uint32_t PERIPH_BASE = 0x80000; + constexpr uint32_t PERIPH_REG_OFFSET = 8; + constexpr uint32_t PERIPH_REG_CHAN_OFFSET = 0x800; + constexpr uint32_t SR_LEDS = PERIPH_BASE + 176 * PERIPH_REG_OFFSET; + constexpr uint32_t SR_FP_GPIO = PERIPH_BASE + 184 * PERIPH_REG_OFFSET; + constexpr uint32_t SR_DB_GPIO = PERIPH_BASE + 192 * PERIPH_REG_OFFSET; + + constexpr uint32_t RB_DB_GPIO = PERIPH_BASE + 19 * PERIPH_REG_OFFSET; + constexpr uint32_t RB_FP_GPIO = PERIPH_BASE + 20 * PERIPH_REG_OFFSET; + + +} + +/*! \brief Provide access to an E3xx radio. + */ +class e3xx_radio_control_impl : public radio_control_impl, public uhd::rfnoc::detail::filter_node +{ +public: + //! 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 }; + + /************************************************************************ + * Structors + ***********************************************************************/ + e3xx_radio_control_impl(make_args_ptr make_args); + virtual ~e3xx_radio_control_impl(); + + /************************************************************************ + * node_t && noc_block_base API calls + ***********************************************************************/ + void deinit(); + + bool check_topology(const std::vector<size_t>& connected_inputs, + const std::vector<size_t>& connected_outputs); + + /************************************************************************ + * radio_control API calls + ***********************************************************************/ + double set_rate(const double rate); + uhd::meta_range_t get_rate_range() const; + + // Setters + 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_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + void set_rx_agc(const bool enable, 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); + + // Getters + std::vector<std::string> get_tx_antennas(const size_t chan) const; + std::vector<std::string> get_rx_antennas(const size_t chan) const; + uhd::freq_range_t get_tx_frequency_range(const size_t chan) const; + uhd::freq_range_t get_rx_frequency_range(const size_t chan) const; + uhd::gain_range_t get_tx_gain_range(const size_t) const; + uhd::gain_range_t get_rx_gain_range(const size_t) const; + meta_range_t get_tx_bandwidth_range(size_t chan) const; + meta_range_t get_rx_bandwidth_range(size_t chan) const; + + /************************************************************************** + * Calibration-Related API Calls + *************************************************************************/ + virtual void set_rx_dc_offset(const bool enb, size_t chan = ALL_CHANS); + virtual void set_rx_iq_balance(const bool enb, size_t chan); + + /************************************************************************** + * GPIO Controls + *************************************************************************/ + virtual void set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value); + virtual uint32_t get_gpio_attr(const std::string& bank, const std::string& attr); + + /************************************************************************** + * Sensor API + *************************************************************************/ + std::vector<std::string> get_rx_sensor_names(size_t chan) const; + uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan); + std::vector<std::string> get_tx_sensor_names(size_t chan) const; + uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan); + + /************************************************************************** + * Filter API + *************************************************************************/ + std::vector<std::string> get_rx_filter_names(const size_t chan) const; + uhd::filter_info_base::sptr get_rx_filter(const std::string& name, const size_t chan); + void set_rx_filter( + const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan); + + std::vector<std::string> get_tx_filter_names(const size_t chan) const; + uhd::filter_info_base::sptr get_tx_filter(const std::string& name, const size_t chan); + void set_tx_filter( + const std::string& name, uhd::filter_info_base::sptr filter, const size_t chan); + + /************************************************************************** + * Radio Identification API Calls + *************************************************************************/ + std::string get_slot_name() const { return "A"; } + virtual size_t get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t direction) const; + virtual std::string get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t direction) const; + virtual std::string get_fe_name( + const size_t chan, const uhd::direction_t direction) const; + +protected: + //! 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); + + virtual const std::string get_default_timing_mode() = 0; + + /*! 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(const size_t chan); + + virtual uint32_t get_rx_switches( + const size_t chan, const double freq, const std::string& ant) = 0; + + virtual uint32_t get_tx_switches(const size_t chan, const double freq) = 0; + + virtual uint32_t get_idle_switches() = 0; + + virtual uint32_t get_tx_led() = 0; + virtual uint32_t get_rx_led() = 0; + virtual uint32_t get_txrx_led() = 0; + virtual uint32_t get_idle_led() = 0; + + //! Reference to the AD9361 controls + // e3xx_ad9361_iface::uptr _ad9361; + ad9361_ctrl::sptr _ad9361; + + //! Swap RFA and RFB for catalina + bool _fe_swap; + + //! Init RPC-related items + void _init_mpm(); + +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(); + + //! 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); + + //! Set which channel mode is used + void set_channel_mode(const std::string& channel_mode); + + /************************************************************************** + * Misc Controls + *************************************************************************/ + //! Blink the front-panel LEDs for \p identify_duration, + // and resume normal operation. + void _identify_with_leds(const int identify_duration); + + void _set_atr_bits(const size_t chan); + + void set_db_eeprom(const uhd::eeprom_map_t& db_eeprom); + + uhd::eeprom_map_t get_db_eeprom(); + + /************************************************************************** + * Private attributes + *************************************************************************/ + //! Locks access to setter APIs + mutable std::mutex _set_lock; + + //! Prepended for all dboard RPC calls + std::string _rpc_prefix = "db_0_"; + + //! Reference to the MB controller + uhd::rfnoc::mpmd_mb_controller::sptr _e3xx_mb_control; + + //! Reference to the MB timekeeper + uhd::rfnoc::mpmd_mb_controller::mpmd_timekeeper::sptr _e3xx_timekeeper; + + //! Reference to wb_iface adapters + std::vector<uhd::timed_wb_iface::sptr> _wb_ifaces; + + //! Reference to the RPC client + uhd::rpc_client::sptr _rpcc; + + //! 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 + // Every radio channel gets its own ATR state register. + 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; + + //! RX sensor names (they get cached) + std::vector<std::string> _rx_sensor_names; + + //! TX sensor names (they get cached) + std::vector<std::string> _tx_sensor_names; + + //! RX filter names (they get cached) + std::vector<std::string> _rx_filter_names; + + //! TX filter names (they get cached) + std::vector<std::string> _tx_filter_names; +}; + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP */ diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp new file mode 100644 index 000000000..f97feeb68 --- /dev/null +++ b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp @@ -0,0 +1,305 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "e3xx_constants.hpp" +#include "e3xx_radio_control_impl.hpp" +#include <uhd/transport/chdr.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/rfnoc/reg_iface_adapter.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/split.hpp> +#include <string> +#include <vector> + +using namespace uhd; +using namespace uhd::rfnoc; + +void e3xx_radio_control_impl::_init_defaults() +{ + RFNOC_LOG_TRACE("Initializing defaults..."); + const size_t num_rx_chans = get_num_output_ports(); + const size_t num_tx_chans = get_num_input_ports(); + + RFNOC_LOG_TRACE( + "Num TX chans: " << num_tx_chans << " Num RX chans: " << num_rx_chans); + + + // 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. + auto block_args = get_block_args(); + _master_clock_rate = + _rpcc->request_with_token<double>(_rpc_prefix + "get_master_clock_rate"); + const double block_args_mcr = + block_args.cast<double>("master_clock_rate", _master_clock_rate); + if (block_args_mcr != _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_mcr / 1e6))); + } + RFNOC_LOG_DEBUG("Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); + set_tick_rate(_master_clock_rate); + _e3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + for (size_t chan = 0; chan < num_rx_chans; chan++) { + radio_control_impl::set_rx_frequency(E3XX_DEFAULT_FREQ, chan); + radio_control_impl::set_rx_gain(E3XX_DEFAULT_GAIN, chan); + radio_control_impl::set_rx_antenna(E3XX_DEFAULT_RX_ANTENNA, chan); + radio_control_impl::set_rx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); + } + + for (size_t chan = 0; chan < num_tx_chans; chan++) { + radio_control_impl::set_tx_frequency(E3XX_DEFAULT_FREQ, chan); + radio_control_impl::set_tx_gain(E3XX_DEFAULT_GAIN, chan); + radio_control_impl::set_tx_antenna(E3XX_DEFAULT_TX_ANTENNA, chan); + radio_control_impl::set_tx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); + } + + _rx_sensor_names = _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", "RX"); + _tx_sensor_names = _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", "TX"); + + // Cache the filter names + // FIXME: Uncomment this + //_rx_filter_names = _ad9361->get_filter_names( + // get_which_ad9361_chain(RX_DIRECTION, 0, _fe_swap)); + //_tx_filter_names = _ad9361->get_filter_names( + // get_which_ad9361_chain(TX_DIRECTION, 0, _fe_swap)); +} + +void e3xx_radio_control_impl::_init_peripherals() +{ + RFNOC_LOG_TRACE("Initializing peripherals..."); + for (size_t radio_idx = 0; radio_idx < E3XX_NUM_CHANS; radio_idx++) { + _wb_ifaces.push_back(RFNOC_MAKE_WB_IFACE(0, radio_idx)); + } + _db_gpio.clear(); // Following the as-if rule, this can get optimized out + for (size_t radio_idx = 0; radio_idx < E3XX_NUM_CHANS; radio_idx++) { + RFNOC_LOG_TRACE("Initializing DB GPIOs for channel " << radio_idx); + // Note: The register offset is baked into the different _wb_iface + // objects! + _db_gpio.emplace_back( + usrp::gpio_atr::gpio_atr_3000::make_write_only(_wb_ifaces.at(radio_idx), + e3xx_regs::SR_DB_GPIO + (radio_idx * e3xx_regs::PERIPH_REG_CHAN_OFFSET), + e3xx_regs::PERIPH_REG_OFFSET)); + _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 < E3XX_NUM_CHANS; radio_idx++) { + RFNOC_LOG_TRACE("Initializing LED GPIOs for channel " << radio_idx); + _leds_gpio.emplace_back( + usrp::gpio_atr::gpio_atr_3000::make_write_only(_wb_ifaces.at(radio_idx), + e3xx_regs::SR_LEDS + (radio_idx * e3xx_regs::PERIPH_REG_CHAN_OFFSET), + e3xx_regs::PERIPH_REG_OFFSET)); + _leds_gpio[radio_idx]->set_atr_mode( + usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + } + RFNOC_LOG_TRACE("Initializing front-panel GPIO control...") + _fp_gpio = usrp::gpio_atr::gpio_atr_3000::make( + _wb_ifaces.at(0), e3xx_regs::SR_FP_GPIO, e3xx_regs::RB_FP_GPIO, e3xx_regs::PERIPH_REG_OFFSET); + + + auto block_args = get_block_args(); + 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; + } + _identify_with_leds(identify_duration); + } +} + +void e3xx_radio_control_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; + RFNOC_LOG_TRACE( + "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("E3xx"); + subtree->create<std::string>(tx_fe_path / "connection").set("IQ"); + // RX Standard attributes + subtree->create<std::string>(rx_fe_path / "name").set("E3xx"); + 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({E3XX_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(E3XX_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_publisher([this]() { return get_tx_frequency_range(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_publisher([this]() { return get_rx_frequency_range(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_publisher([this, chan_idx]() { return get_tx_bandwidth(chan_idx); }) + .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_publisher([this]() { return get_tx_bandwidth_range(0); }) + .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_publisher([this, chan_idx]() { return get_rx_bandwidth(chan_idx); }) + .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_publisher([this]() { return get_rx_bandwidth_range(0); }) + .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_control_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_control_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); + }); + } + + auto rx_sensor_names = get_rx_sensor_names(chan_idx); + for (const auto& rx_sensor_name : rx_sensor_names) { + RFNOC_LOG_TRACE("Adding RX sensor " << rx_sensor_name); + get_tree()->create<sensor_value_t>(rx_fe_path / "sensors" / rx_sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, rx_sensor_name, chan_idx]() { + return get_rx_sensor(rx_sensor_name, chan_idx); + }); + } + auto tx_sensor_names = get_tx_sensor_names(chan_idx); + for (const auto& tx_sensor_name : tx_sensor_names) { + RFNOC_LOG_TRACE("Adding TX sensor " << tx_sensor_name); + get_tree()->create<sensor_value_t>(tx_fe_path / "sensors" / tx_sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, tx_sensor_name, chan_idx]() { + return get_tx_sensor(tx_sensor_name, chan_idx); + }); + } +} + +void e3xx_radio_control_impl::_init_prop_tree() +{ + for (size_t chan_idx = 0; chan_idx < E3XX_NUM_CHANS; chan_idx++) { + this->_init_frontend_subtree(get_tree()->subtree(DB_PATH), chan_idx); + } + get_tree()->create<std::string>("rx_codec/name").set("AD9361 Dual ADC"); + get_tree()->create<std::string>("tx_codec/name").set("AD9361 Dual DAC"); +} + +void e3xx_radio_control_impl::_init_mpm() +{ + // Initialize catalina + _init_codec(); + + // Loopback test + for (size_t chan = 0; chan < E3XX_NUM_CHANS; chan++) { + loopback_self_test(chan); + } +} + +void e3xx_radio_control_impl::_init_codec() +{ + RFNOC_LOG_TRACE("Setting Catalina Defaults... "); + for (size_t chan = 0; chan < E3XX_NUM_CHANS; chan++) { + std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + this->set_rx_gain(E3XX_DEFAULT_GAIN, chan); + this->set_rx_frequency(E3XX_DEFAULT_FREQ, chan); + this->set_rx_antenna(E3XX_DEFAULT_RX_ANTENNA, chan); + this->set_rx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); + _ad9361->set_dc_offset_auto(rx_fe, E3XX_DEFAULT_AUTO_DC_OFFSET); + _ad9361->set_iq_balance_auto(rx_fe, E3XX_DEFAULT_AUTO_IQ_BALANCE); + _ad9361->set_agc(rx_fe, E3XX_DEFAULT_AGC_ENABLE); + std::string tx_fe = get_which_ad9361_chain(TX_DIRECTION, chan); + this->set_tx_gain(E3XX_DEFAULT_GAIN, chan); + this->set_tx_frequency(E3XX_DEFAULT_FREQ, chan); + this->set_tx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); + } +} + diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.cpp deleted file mode 100644 index 989b73b82..000000000 --- a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.cpp +++ /dev/null @@ -1,343 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "e3xx_radio_ctrl_impl.hpp" -#include "e3xx_constants.hpp" -#include <uhd/rfnoc/node_ctrl_base.hpp> -#include <uhd/transport/chdr.hpp> -#include <uhd/types/direction.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/utils/math.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/format.hpp> -#include <boost/make_shared.hpp> -#include <cmath> -#include <cstdlib> -#include <sstream> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; -using namespace uhd::math::fp_compare; - -/****************************************************************************** - * Structors - *****************************************************************************/ -e3xx_radio_ctrl_impl::e3xx_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "Entering e3xx_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(); -} - -e3xx_radio_ctrl_impl::~e3xx_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "e3xx_radio_ctrl_impl::dtor() "); -} - - -/****************************************************************************** - * API Calls - *****************************************************************************/ - -void e3xx_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 - if ((num_rx + num_tx) == 0) { - // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock - _ad9361->set_active_chains(true, false, true, false); - } else { - // setup the active chains in the codec - _ad9361->set_active_chains(tx1, tx2, rx1, rx2); - } - - // 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 1R1T mode - _ad9361->set_timing_mode(this->get_default_timing_mode()); - 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 e3xx_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); -} - -double e3xx_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 e3xx_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)); - } - radio_ctrl_impl::set_tx_antenna(ant, chan); - // We can't actually set the TX antenna, so let's stop here. -} - -void e3xx_radio_ctrl_impl::set_rx_antenna(const std::string& ant, const size_t chan) -{ - UHD_ASSERT_THROW(chan <= E3XX_NUM_CHANS); - if (std::find(E3XX_RX_ANTENNAS.begin(), E3XX_RX_ANTENNAS.end(), ant) - == E3XX_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); - - radio_ctrl_impl::set_rx_antenna(ant, chan); - _set_atr_bits(chan); -} - -double e3xx_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 clipped_freq = uhd::clip(freq, AD9361_TX_MIN_FREQ, AD9361_TX_MAX_FREQ); - - double coerced_freq = - _ad9361->tune(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), clipped_freq); - radio_ctrl_impl::set_tx_frequency(coerced_freq, chan); - // Front-end switching - _set_atr_bits(chan); - - return coerced_freq; -} - -double e3xx_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 clipped_freq = uhd::clip(freq, AD9361_RX_MIN_FREQ, AD9361_RX_MAX_FREQ); - - double coerced_freq = - _ad9361->tune(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), clipped_freq); - radio_ctrl_impl::set_rx_frequency(coerced_freq, chan); - // Front-end switching - _set_atr_bits(chan); - - return coerced_freq; -} - -double e3xx_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, _fe_swap), bandwidth); - return radio_ctrl_impl::set_rx_bandwidth(clipped_bw, chan); -} - -double e3xx_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, _fe_swap), bandwidth); - return radio_ctrl_impl::set_tx_bandwidth(clipped_bw, chan); -} - -double e3xx_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, _fe_swap), clip_gain); - radio_ctrl_impl::set_tx_gain(clip_gain, chan); - return clip_gain; -} - -double e3xx_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, _fe_swap), clip_gain); - radio_ctrl_impl::set_rx_gain(clip_gain, chan); - return clip_gain; -} - -size_t e3xx_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 e3xx_radio_ctrl_impl::get_dboard_fe_from_chan( - const size_t chan, const direction_t /* dir */ -) -{ - return std::to_string(chan); -} - -void e3xx_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 < E3XX_NUM_CHANS; chan_idx++) { - _init_mpm_sensors(dir, chan_idx); - } - } -} - -bool e3xx_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 e3xx_radio_ctrl_impl::_set_atr_bits(const size_t chan) -{ - const auto rx_freq = radio_ctrl_impl::get_rx_frequency(chan); - const auto tx_freq = radio_ctrl_impl::get_tx_frequency(chan); - const auto rx_ant = radio_ctrl_impl::get_rx_antenna(chan); - const uint32_t rx_regs = this->get_rx_switches(chan, rx_freq, rx_ant); - const uint32_t tx_regs = this->get_tx_switches(chan, tx_freq); - const uint32_t idle_regs = this->get_idle_switches(); - - _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 bool is_txrx = rx_ant == "TX/RX"; - const int idle_led = 0; - const int rx_led = this->get_rx_led(); - const int tx_led = this->get_tx_led(); - const int txrx_led = this->get_txrx_led(); - - _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 e3xx_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)); - } -} diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.hpp deleted file mode 100644 index 41c2c4594..000000000 --- a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_impl.hpp +++ /dev/null @@ -1,215 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#ifndef INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP -# define INCLUDED_LIBUHD_RFNOC_E3XX_RADIO_CTRL_IMPL_HPP - -# include "e3xx_ad9361_iface.hpp" -# include <uhd/types/serial.hpp> -# include <uhd/usrp/dboard_manager.hpp> -# include <uhd/usrp/gpio_defs.hpp> -# include <uhdlib/rfnoc/radio_ctrl_impl.hpp> -# include <uhdlib/rfnoc/rpc_block_ctrl.hpp> -# include <uhdlib/usrp/cores/gpio_atr_3000.hpp> -# include <mutex> - -namespace uhd { namespace rfnoc { - -/*! \brief Provide access to an E3xx radio. - */ -class e3xx_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl -{ -public: - typedef boost::shared_ptr<e3xx_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 }; - - /************************************************************************ - * Structors - ***********************************************************************/ - e3xx_radio_ctrl_impl(); - virtual ~e3xx_radio_ctrl_impl(); - - /************************************************************************ - * API calls - ***********************************************************************/ - - // Note: We use the cached values in radio_ctrl_impl, so most getters are - // not reimplemented here - //! 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); - - //! Set which channel mode is used - void set_channel_mode(const std::string& channel_mode); - - 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); - -protected: - //! 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); - - virtual const std::string get_default_timing_mode() = 0; - - /*! 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. - */ - virtual void loopback_self_test(std::function<void(uint32_t)> poker_functor, - std::function<uint64_t()> peeker_functor) = 0; - - virtual uint32_t get_rx_switches( - const size_t chan, const double freq, const std::string& ant) = 0; - - virtual uint32_t get_tx_switches(const size_t chan, const double freq) = 0; - - virtual uint32_t get_idle_switches() = 0; - - virtual uint32_t get_tx_led() = 0; - virtual uint32_t get_rx_led() = 0; - virtual uint32_t get_txrx_led() = 0; - virtual uint32_t get_idle_led() = 0; - - //! Reference to the AD9361 controls - // e3xx_ad9361_iface::uptr _ad9361; - ad9361_ctrl::sptr _ad9361; - - //! Swap RFA and RFB for catalina - bool _fe_swap; - -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); - - /************************************************************************* - * 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); - - void _set_atr_bits(const size_t chan); - - /************************************************************************** - * 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; - - //! 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_E3XX_RADIO_CTRL_IMPL_HPP */ -// vim: sw=4 et: diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_init.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_init.cpp deleted file mode 100644 index 5b33b33e7..000000000 --- a/host/lib/usrp/dboard/e3xx/e3xx_radio_ctrl_init.cpp +++ /dev/null @@ -1,427 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "e3xx_constants.hpp" -#include "e3xx_radio_ctrl_impl.hpp" -#include <uhd/transport/chdr.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/types/sensors.hpp> -#include <uhd/utils/log.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/algorithm/string/case_conv.hpp> -#include <boost/algorithm/string/split.hpp> -#include <string> -#include <vector> - -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 e3xx_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(E3XX_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_rx_gain(E3XX_DEFAULT_GAIN, chan); - radio_ctrl_impl::set_rx_antenna(E3XX_DEFAULT_RX_ANTENNA, chan); - radio_ctrl_impl::set_rx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); - } - - for (size_t chan = 0; chan < num_tx_chans; chan++) { - radio_ctrl_impl::set_tx_frequency(E3XX_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_tx_gain(E3XX_DEFAULT_GAIN, chan); - radio_ctrl_impl::set_tx_antenna(E3XX_DEFAULT_TX_ANTENNA, chan); - radio_ctrl_impl::set_tx_bandwidth(E3XX_DEFAULT_BANDWIDTH, 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 e3xx_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_addr(regs::RB_FP_GPIO)); -} - -void e3xx_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("E3xx"))); - 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("E3xx"))); - 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({E3XX_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(E3XX_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_TX_MIN_FREQ, AD9361_TX_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_RX_MIN_FREQ, AD9361_RX_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 e3xx_radio_ctrl_impl::_init_prop_tree() -{ - const fs_path fe_base = fs_path("dboards") / _radio_slot; - for (size_t chan_idx = 0; chan_idx < E3XX_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 e3xx_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(E3XX_DEFAULT_GAIN, chan); - this->set_rx_frequency(E3XX_DEFAULT_FREQ, chan); - this->set_rx_antenna(E3XX_DEFAULT_RX_ANTENNA, chan); - this->set_rx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); - _ad9361->set_dc_offset_auto(rx_fe, E3XX_DEFAULT_AUTO_DC_OFFSET); - _ad9361->set_iq_balance_auto(rx_fe, E3XX_DEFAULT_AUTO_IQ_BALANCE); - _ad9361->set_agc(rx_fe, E3XX_DEFAULT_AGC_ENABLE); - std::string tx_fe = get_which_ad9361_chain(TX_DIRECTION, chan); - this->set_tx_gain(E3XX_DEFAULT_GAIN, chan); - this->set_tx_frequency(E3XX_DEFAULT_FREQ, chan); - this->set_tx_bandwidth(E3XX_DEFAULT_BANDWIDTH, chan); - } -} - -void e3xx_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/magnesium/CMakeLists.txt b/host/lib/usrp/dboard/magnesium/CMakeLists.txt index 1029bfacd..df5fe765a 100644 --- a/host/lib/usrp/dboard/magnesium/CMakeLists.txt +++ b/host/lib/usrp/dboard/magnesium/CMakeLists.txt @@ -8,10 +8,10 @@ # set to true. list(APPEND MAGNESIUM_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_init.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_cpld.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_gain.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_control.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_control_init.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_control_cpld.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_control_gain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_ad9371_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_bands.cpp ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_cpld_ctrl.cpp diff --git a/host/lib/usrp/dboard/magnesium/magnesium_bands.cpp b/host/lib/usrp/dboard/magnesium/magnesium_bands.cpp index 13cc52d49..ef72aee95 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_bands.cpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_bands.cpp @@ -7,7 +7,7 @@ // The band plan #include "magnesium_constants.hpp" -#include "magnesium_radio_ctrl_impl.hpp" +#include "magnesium_radio_control.hpp" #include <uhd/utils/math.hpp> /* @@ -93,11 +93,10 @@ namespace { } // namespace - -magnesium_radio_ctrl_impl::rx_band magnesium_radio_ctrl_impl::_map_freq_to_rx_band( +magnesium_radio_control_impl::rx_band magnesium_radio_control_impl::_map_freq_to_rx_band( const band_map_t band_map, const double freq) { - magnesium_radio_ctrl_impl::rx_band band; + magnesium_radio_control_impl::rx_band band; if (fp_compare_epsilon<double>(freq) < MAGNESIUM_MIN_FREQ) { band = rx_band::INVALID_BAND; @@ -124,10 +123,10 @@ magnesium_radio_ctrl_impl::rx_band magnesium_radio_ctrl_impl::_map_freq_to_rx_ba return band; } -magnesium_radio_ctrl_impl::tx_band magnesium_radio_ctrl_impl::_map_freq_to_tx_band( +magnesium_radio_control_impl::tx_band magnesium_radio_control_impl::_map_freq_to_tx_band( const band_map_t band_map, const double freq) { - magnesium_radio_ctrl_impl::tx_band band; + magnesium_radio_control_impl::tx_band band; if (fp_compare_epsilon<double>(freq) < MAGNESIUM_MIN_FREQ) { band = tx_band::INVALID_BAND; diff --git a/host/lib/usrp/dboard/magnesium/magnesium_constants.hpp b/host/lib/usrp/dboard/magnesium/magnesium_constants.hpp index 9b3bdf800..7d98bca91 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_constants.hpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_constants.hpp @@ -69,6 +69,13 @@ static constexpr char MAGNESIUM_GAIN2[] = "dsa"; //! Amplifier gain static constexpr char MAGNESIUM_AMP[] = "amp"; +static constexpr char MAGNESIUM_FE_NAME[] = "Magnesium"; + +static constexpr char MAGNESIUM_DEFAULT_RX_ANTENNA[] = "RX2"; +static constexpr char MAGNESIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; + +static constexpr char MAGNESIUM_FPGPIO_BANK[] = "FP0"; + // Note: MAGNESIUM_NUM_CHANS is independent of the number of chans per // RFNoC block. TODO: When we go to one radio per dboard, this comment can // be deleted. @@ -79,4 +86,20 @@ static constexpr double MAGNESIUM_TX_IF_FREQ = 1.95e9; //! Max time we allow for a call to set_freq() to take static constexpr size_t MAGNESIUM_TUNE_TIMEOUT = 15000; // milliseconds +//! Magnesium gain profile options +static const std::vector<std::string> MAGNESIUM_GP_OPTIONS = {"manual", + "default", + "default_rf_filter_bypass_always_on", + "default_rf_filter_bypass_always_off"}; + +namespace n310_regs { + +constexpr uint32_t DB_GPIO_BASE = 0x80000; // FIXME +constexpr uint32_t DB_GPIO_RB = 0x80000; // FIXME +constexpr uint32_t DB_GPIO_OFFSET = 0x100; // FIXME +constexpr uint32_t FP_GPIO = 0x80000; // FIXME +constexpr uint32_t RB_FP_GPIO = 0x80000; // FIXME + +} + #endif /* INCLUDED_LIBUHD_MAGNESIUM_CONSTANTS_HPP */ diff --git a/host/lib/usrp/dboard/magnesium/magnesium_gain_table.cpp b/host/lib/usrp/dboard/magnesium/magnesium_gain_table.cpp index 67b20f5fa..3e513218a 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_gain_table.cpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_gain_table.cpp @@ -15,8 +15,8 @@ using namespace uhd::rfnoc; using namespace magnesium; namespace { -typedef magnesium_radio_ctrl_impl::rx_band rx_band; -typedef magnesium_radio_ctrl_impl::tx_band tx_band; +typedef magnesium_radio_control_impl::rx_band rx_band; +typedef magnesium_radio_control_impl::tx_band tx_band; const size_t TX_LOWBAND = 0; const size_t TX_HIGHBAND = 1; @@ -454,7 +454,7 @@ gain_tuple_t fine_tune_ad9371_att(const gain_tuple_t gain_tuple, const double ga gain_tuple_t magnesium::get_rx_gain_tuple( - const double gain_index, const magnesium_radio_ctrl_impl::rx_band band) + const double gain_index, const magnesium_radio_control_impl::rx_band band) { UHD_ASSERT_THROW(gain_index <= ALL_RX_MAX_GAIN and gain_index >= ALL_RX_MIN_GAIN); auto& gain_table = rx_gain_tables.at(map_rx_band(band)); @@ -463,7 +463,7 @@ gain_tuple_t magnesium::get_rx_gain_tuple( } gain_tuple_t magnesium::get_tx_gain_tuple( - const double gain_index, const magnesium_radio_ctrl_impl::tx_band band) + const double gain_index, const magnesium_radio_control_impl::tx_band band) { UHD_ASSERT_THROW(gain_index <= ALL_TX_MAX_GAIN and gain_index >= ALL_TX_MIN_GAIN); auto& gain_table = tx_gain_tables.at(map_tx_band(band)); diff --git a/host/lib/usrp/dboard/magnesium/magnesium_gain_table.hpp b/host/lib/usrp/dboard/magnesium/magnesium_gain_table.hpp index 8769b44ac..6ba91a248 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_gain_table.hpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_gain_table.hpp @@ -7,7 +7,7 @@ #ifndef INCLUDED_LIBUHD_MAGNESIUM_GAIN_TABLE_HPP #define INCLUDED_LIBUHD_MAGNESIUM_GAIN_TABLE_HPP -#include "magnesium_radio_ctrl_impl.hpp" +#include "magnesium_radio_control.hpp" #include <uhd/types/direction.hpp> namespace magnesium { @@ -31,12 +31,12 @@ struct gain_tuple_t /*! Given a gain index, return a tuple of gain-related settings (Rx) */ gain_tuple_t get_rx_gain_tuple( - const double gain_index, const uhd::rfnoc::magnesium_radio_ctrl_impl::rx_band band_); + const double gain_index, const uhd::rfnoc::magnesium_radio_control_impl::rx_band band_); /*! Given a gain index, return a tuple of gain-related settings (Tx) */ gain_tuple_t get_tx_gain_tuple( - const double gain_index, const uhd::rfnoc::magnesium_radio_ctrl_impl::tx_band band_); + const double gain_index, const uhd::rfnoc::magnesium_radio_control_impl::tx_band band_); } /* namespace magnesium */ diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_control.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control.cpp new file mode 100644 index 000000000..dc78cee7d --- /dev/null +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control.cpp @@ -0,0 +1,1151 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "magnesium_radio_control.hpp" +#include "magnesium_constants.hpp" +#include "magnesium_gain_table.hpp" +#include <uhd/exception.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include <cmath> +#include <cstdlib> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +namespace { + +/************************************************************************** + * ADF4351 Controls + *************************************************************************/ +/*! + * \param lo_iface Reference to the LO object + * \param freq Frequency (in Hz) of the tone to be generated from the LO + * \param ref_clock_freq Frequency (in Hz) of the reference clock at the + * PLL input of the LO + * \param int_n_mode Integer-N mode on or off + */ +double _lo_set_frequency(adf435x_iface::sptr lo_iface, + const double freq, + const double ref_clock_freq, + const bool int_n_mode) +{ + UHD_LOG_TRACE("MG/ADF4351", + "Attempting to tune low band LO to " << freq << " Hz with ref clock freq " + << ref_clock_freq); + lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + lo_iface->set_reference_freq(ref_clock_freq); + lo_iface->set_prescaler(adf435x_iface::PRESCALER_4_5); + const double actual_freq = lo_iface->set_frequency(freq, int_n_mode); + lo_iface->set_output_power( + adf435x_iface::RF_OUTPUT_A, adf435x_iface::OUTPUT_POWER_2DBM); + lo_iface->set_output_power( + adf435x_iface::RF_OUTPUT_B, adf435x_iface::OUTPUT_POWER_2DBM); + lo_iface->set_charge_pump_current(adf435x_iface::CHARGE_PUMP_CURRENT_0_31MA); + return actual_freq; +} + +/*! Configure and enable LO + * + * Will tune it to requested frequency and enable outputs. + * + * \param lo_iface Reference to the LO object + * \param lo_freq Frequency (in Hz) of the tone to be generated from the LO + * \param ref_clock_freq Frequency (in Hz) of the reference clock at the + * PLL input of the LO + * \param int_n_mode Integer-N mode on or off + * \returns the actual frequency the LO is running at + */ +double _lo_enable(adf435x_iface::sptr lo_iface, + const double lo_freq, + const double ref_clock_freq, + const bool int_n_mode) +{ + const double actual_lo_freq = + _lo_set_frequency(lo_iface, lo_freq, ref_clock_freq, int_n_mode); + lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_A, true); + lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_B, true); + lo_iface->commit(); + return actual_lo_freq; +} + +/*! Disable LO + */ +void _lo_disable(adf435x_iface::sptr lo_iface) +{ + lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_A, false); + lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_B, false); + lo_iface->commit(); +} +} // namespace + + +/****************************************************************************** + * Structors + *****************************************************************************/ +magnesium_radio_control_impl::magnesium_radio_control_impl(make_args_ptr make_args) + : radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("Entering magnesium_radio_control_impl ctor..."); + UHD_ASSERT_THROW(get_block_id().get_block_count() < 2); + const char radio_slot_name[2] = {'A', 'B'}; + _radio_slot = radio_slot_name[get_block_id().get_block_count()]; + RFNOC_LOG_TRACE("Radio slot: " << _radio_slot); + _rpc_prefix = (_radio_slot == "A") ? "db_0_" : "db_1_"; + UHD_ASSERT_THROW(get_num_input_ports() == MAGNESIUM_NUM_CHANS); + UHD_ASSERT_THROW(get_num_output_ports() == MAGNESIUM_NUM_CHANS); + UHD_ASSERT_THROW(get_mb_controller()); + _n310_mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller()); + UHD_ASSERT_THROW(_n310_mb_control); + _n3xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>( + _n310_mb_control->get_timekeeper(0)); + UHD_ASSERT_THROW(_n3xx_timekeeper); + _rpcc = _n310_mb_control->get_rpc_client(); + UHD_ASSERT_THROW(_rpcc); + + _init_defaults(); + _init_mpm(); + _init_peripherals(); + _init_prop_tree(); +} + +magnesium_radio_control_impl::~magnesium_radio_control_impl() +{ + RFNOC_LOG_TRACE("magnesium_radio_control_impl::dtor() "); +} + + +/****************************************************************************** + * API Calls + *****************************************************************************/ +double magnesium_radio_control_impl::set_rate(double requested_rate) +{ + meta_range_t rates; + for (const double rate : MAGNESIUM_RADIO_RATES) { + rates.push_back(range_t(rate)); + } + + const double rate = rates.clip(requested_rate); + if (!math::frequencies_are_equal(requested_rate, rate)) { + RFNOC_LOG_WARNING("Coercing requested sample rate from " + << (requested_rate / 1e6) << " to " << (rate / 1e6)); + } + + const double current_rate = get_tick_rate(); + if (math::frequencies_are_equal(current_rate, rate)) { + RFNOC_LOG_DEBUG("Rate is already at " << rate << " MHz. Skipping set_rate()"); + return current_rate; + } + + std::lock_guard<std::mutex> l(_set_lock); + // Now commit to device. First, disable LOs. + _lo_disable(_tx_lo); + _lo_disable(_rx_lo); + _master_clock_rate = _ad9371->set_master_clock_rate(rate); + _n3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + // Frequency settings apply to both channels, no loop needed. Will also + // re-enable the lowband LOs if they were used. + set_rx_frequency(get_rx_frequency(0), 0); + set_tx_frequency(get_tx_frequency(0), 0); + // Gain and bandwidth need to be looped: + for (size_t radio_idx = 0; radio_idx < MAGNESIUM_NUM_CHANS; radio_idx++) { + set_rx_gain(radio_control_impl::get_rx_gain(radio_idx), radio_idx); + set_tx_gain(radio_control_impl::get_rx_gain(radio_idx), radio_idx); + set_rx_bandwidth(get_rx_bandwidth(radio_idx), radio_idx); + set_tx_bandwidth(get_tx_bandwidth(radio_idx), radio_idx); + } + set_tick_rate(_master_clock_rate); + return _master_clock_rate; +} + +void magnesium_radio_control_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") + % get_unique_id() % ant)); + } + // We can't actually set the TX antenna, so let's stop here. +} + +void magnesium_radio_control_impl::set_rx_antenna(const std::string& ant, const size_t chan) +{ + UHD_ASSERT_THROW(chan <= MAGNESIUM_NUM_CHANS); + if (std::find(MAGNESIUM_RX_ANTENNAS.begin(), MAGNESIUM_RX_ANTENNAS.end(), ant) + == MAGNESIUM_RX_ANTENNAS.end()) { + throw uhd::value_error( + str(boost::format("[%s] Requesting invalid RX antenna value: %s") + % get_unique_id() % ant)); + } + RFNOC_LOG_TRACE("Setting RX antenna to " << ant << " for chan " << chan); + magnesium_cpld_ctrl::chan_sel_t chan_sel = chan == 0 ? magnesium_cpld_ctrl::CHAN1 + : magnesium_cpld_ctrl::CHAN2; + _update_atr_switches(chan_sel, RX_DIRECTION, ant); + + radio_control_impl::set_rx_antenna(ant, chan); +} + +double magnesium_radio_control_impl::set_tx_frequency(const double req_freq, const size_t chan) +{ + const double freq = MAGNESIUM_FREQ_RANGE.clip(req_freq); + RFNOC_LOG_TRACE("set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); + _desired_rf_freq[TX_DIRECTION] = freq; + std::lock_guard<std::mutex> l(_set_lock); + // We need to set the switches on both channels, because they share an LO. + // This way, if we tune channel 0 it will not put channel 1 into a bad + // state. + _update_tx_freq_switches(freq, _tx_bypass_amp, magnesium_cpld_ctrl::BOTH); + const std::string ad9371_source = this->get_tx_lo_source(MAGNESIUM_LO1, chan); + const std::string adf4351_source = this->get_tx_lo_source(MAGNESIUM_LO2, chan); + UHD_ASSERT_THROW(adf4351_source == "internal"); + double coerced_if_freq = freq; + + if (_map_freq_to_tx_band(_tx_band_map, freq) == tx_band::LOWBAND) { + _is_low_band[TX_DIRECTION] = true; + const double desired_low_freq = MAGNESIUM_TX_IF_FREQ - freq; + coerced_if_freq = + this->_set_tx_lo_freq(adf4351_source, MAGNESIUM_LO2, desired_low_freq, chan) + + freq; + RFNOC_LOG_TRACE("coerced_if_freq = " << coerced_if_freq); + } else { + _is_low_band[TX_DIRECTION] = false; + _lo_disable(_tx_lo); + } + // external LO required to tune at 2xdesired_frequency. + const double desired_if_freq = (ad9371_source == "internal") ? coerced_if_freq + : 2 * coerced_if_freq; + + this->_set_tx_lo_freq(ad9371_source, MAGNESIUM_LO1, desired_if_freq, chan); + this->_update_freq(chan, TX_DIRECTION); + this->_update_gain(chan, TX_DIRECTION); + return radio_control_impl::get_tx_frequency(chan); +} + +void magnesium_radio_control_impl::_update_gain(const size_t chan, const uhd::direction_t dir) +{ + const std::string fe = (dir == TX_DIRECTION) ? "tx_frontends" : "rx_frontends"; + const double freq = (dir == TX_DIRECTION) ? this->get_tx_frequency(chan) + : this->get_rx_frequency(chan); + this->_set_all_gain(this->_get_all_gain(chan, dir), freq, chan, dir); +} + +void magnesium_radio_control_impl::_update_freq(const size_t chan, const uhd::direction_t dir) +{ + const std::string ad9371_source = dir == TX_DIRECTION + ? this->get_tx_lo_source(MAGNESIUM_LO1, chan) + : this->get_rx_lo_source(MAGNESIUM_LO1, chan); + + const double ad9371_freq = ad9371_source == "external" ? _ad9371_freq[dir] / 2 + : _ad9371_freq[dir]; + const double rf_freq = _is_low_band[dir] ? ad9371_freq - _adf4351_freq[dir] + : ad9371_freq; + + RFNOC_LOG_TRACE("RF freq = " << rf_freq); + UHD_ASSERT_THROW(fp_compare_epsilon<double>(rf_freq) >= 0); + UHD_ASSERT_THROW(fp_compare_epsilon<double>(std::abs(rf_freq - _desired_rf_freq[dir])) + <= _master_clock_rate / 2); + if (dir == RX_DIRECTION) { + radio_control_impl::set_rx_frequency(rf_freq, chan); + } else if (dir == TX_DIRECTION) { + radio_control_impl::set_tx_frequency(rf_freq, chan); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +double magnesium_radio_control_impl::set_rx_frequency(const double req_freq, const size_t chan) +{ + const double freq = MAGNESIUM_FREQ_RANGE.clip(req_freq); + RFNOC_LOG_TRACE("set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); + _desired_rf_freq[RX_DIRECTION] = freq; + std::lock_guard<std::mutex> l(_set_lock); + // We need to set the switches on both channels, because they share an LO. + // This way, if we tune channel 0 it will not put channel 1 into a bad + // state. + _update_rx_freq_switches(freq, _rx_bypass_lnas, magnesium_cpld_ctrl::BOTH); + const std::string ad9371_source = this->get_rx_lo_source(MAGNESIUM_LO1, chan); + const std::string adf4351_source = this->get_rx_lo_source(MAGNESIUM_LO2, chan); + UHD_ASSERT_THROW(adf4351_source == "internal"); + double coerced_if_freq = freq; + + if (_map_freq_to_rx_band(_rx_band_map, freq) == rx_band::LOWBAND) { + _is_low_band[RX_DIRECTION] = true; + const double desired_low_freq = MAGNESIUM_RX_IF_FREQ - freq; + coerced_if_freq = + this->_set_rx_lo_freq(adf4351_source, MAGNESIUM_LO2, desired_low_freq, chan) + + freq; + RFNOC_LOG_TRACE("coerced_if_freq = " << coerced_if_freq); + } else { + _is_low_band[RX_DIRECTION] = false; + _lo_disable(_rx_lo); + } + // external LO required to tune at 2xdesired_frequency. + const double desired_if_freq = ad9371_source == "internal" ? coerced_if_freq + : 2 * coerced_if_freq; + + this->_set_rx_lo_freq(ad9371_source, MAGNESIUM_LO1, desired_if_freq, chan); + + this->_update_freq(chan, RX_DIRECTION); + this->_update_gain(chan, RX_DIRECTION); + + return radio_control_impl::get_rx_frequency(chan); +} + +double magnesium_radio_control_impl::set_rx_bandwidth( + const double bandwidth, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + _ad9371->set_bandwidth(bandwidth, chan, RX_DIRECTION); + // FIXME: setting analog bandwidth on AD9371 take no effect. + // Remove this warning when ADI can confirm that it works. + RFNOC_LOG_WARNING("set_rx_bandwidth take no effect on AD9371. " + "Default analog bandwidth is 100MHz"); + return AD9371_RX_MAX_BANDWIDTH; +} + +double magnesium_radio_control_impl::set_tx_bandwidth( + const double bandwidth, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + _ad9371->set_bandwidth(bandwidth, chan, TX_DIRECTION); + // FIXME: setting analog bandwidth on AD9371 take no effect. + // Remove this warning when ADI can confirm that it works. + RFNOC_LOG_WARNING("set_tx_bandwidth take no effect on AD9371. " + "Default analog bandwidth is 100MHz"); + return AD9371_TX_MAX_BANDWIDTH; +} + +void magnesium_radio_control_impl::set_tx_gain_profile( + const std::string& profile, const size_t) +{ + if (std::find( + MAGNESIUM_GP_OPTIONS.begin(), MAGNESIUM_GP_OPTIONS.end(), profile) + == MAGNESIUM_GP_OPTIONS.end()) { + RFNOC_LOG_ERROR("Invalid TX gain profile: " << profile); + throw uhd::key_error("Invalid TX gain profile!"); + } + _gain_profile[TX_DIRECTION] = profile; +} + +void magnesium_radio_control_impl::set_rx_gain_profile( + const std::string& profile, const size_t) +{ + if (std::find( + MAGNESIUM_GP_OPTIONS.begin(), MAGNESIUM_GP_OPTIONS.end(), profile) + == MAGNESIUM_GP_OPTIONS.end()) { + RFNOC_LOG_ERROR("Invalid RX gain profile: " << profile); + throw uhd::key_error("Invalid RX gain profile!"); + } + _gain_profile[RX_DIRECTION] = profile; +} + +double magnesium_radio_control_impl::set_tx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); + const double coerced_gain = + _set_all_gain(gain, this->get_tx_frequency(chan), chan, TX_DIRECTION); + radio_control_impl::set_tx_gain(coerced_gain, chan); + return coerced_gain; +} + +double magnesium_radio_control_impl::_set_tx_gain( + const std::string& name, const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE( + "_set_tx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); + RFNOC_LOG_TRACE( + "_set_tx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); + double clip_gain = 0; + if (name == MAGNESIUM_GAIN1) { + clip_gain = uhd::clip(gain, AD9371_MIN_TX_GAIN, AD9371_MAX_TX_GAIN); + _ad9371_att[TX_DIRECTION] = clip_gain; + } else if (name == MAGNESIUM_GAIN2) { + clip_gain = uhd::clip(gain, DSA_MIN_GAIN, DSA_MAX_GAIN); + _dsa_att[TX_DIRECTION] = clip_gain; + } else if (name == MAGNESIUM_AMP) { + clip_gain = gain > 0.0 ? AMP_MAX_GAIN : AMP_MIN_GAIN; + _amp_bypass[TX_DIRECTION] = clip_gain == 0.0; + } else { + throw uhd::value_error("Could not find gain element " + name); + } + RFNOC_LOG_TRACE("_set_tx_gain calling update gain"); + this->_set_all_gain(this->_get_all_gain(chan, TX_DIRECTION), + this->get_tx_frequency(chan), + chan, + TX_DIRECTION); + return clip_gain; +} + +double magnesium_radio_control_impl::_get_tx_gain( + const std::string& name, const size_t /*chan*/ +) +{ + std::lock_guard<std::mutex> l(_set_lock); + if (name == MAGNESIUM_GAIN1) { + return _ad9371_att[TX_DIRECTION]; + } else if (name == MAGNESIUM_GAIN2) { + return _dsa_att[TX_DIRECTION]; + } else if (name == MAGNESIUM_AMP) { + return _amp_bypass[TX_DIRECTION] ? AMP_MIN_GAIN : AMP_MAX_GAIN; + } else { + throw uhd::value_error("Could not find gain element " + name); + } +} + +double magnesium_radio_control_impl::set_rx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); + const double coerced_gain = + _set_all_gain(gain, this->get_rx_frequency(chan), chan, RX_DIRECTION); + radio_control_impl::set_rx_gain(coerced_gain, chan); + return coerced_gain; +} + +double magnesium_radio_control_impl::_set_rx_gain( + const std::string& name, const double gain, const size_t chan) +{ + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE( + "_set_rx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); + double clip_gain = 0; + if (name == MAGNESIUM_GAIN1) { + clip_gain = uhd::clip(gain, AD9371_MIN_RX_GAIN, AD9371_MAX_RX_GAIN); + _ad9371_att[RX_DIRECTION] = clip_gain; + } else if (name == MAGNESIUM_GAIN2) { + clip_gain = uhd::clip(gain, DSA_MIN_GAIN, DSA_MAX_GAIN); + _dsa_att[RX_DIRECTION] = clip_gain; + } else if (name == MAGNESIUM_AMP) { + clip_gain = gain > 0.0 ? AMP_MAX_GAIN : AMP_MIN_GAIN; + _amp_bypass[RX_DIRECTION] = clip_gain == 0.0; + } else { + throw uhd::value_error("Could not find gain element " + name); + } + RFNOC_LOG_TRACE("_set_rx_gain calling update gain"); + this->_set_all_gain(this->_get_all_gain(chan, RX_DIRECTION), + this->get_rx_frequency(chan), + chan, + RX_DIRECTION); + return clip_gain; // not really any coerced here (only clip) for individual gain +} + +double magnesium_radio_control_impl::_get_rx_gain( + const std::string& name, const size_t /*chan*/ +) +{ + std::lock_guard<std::mutex> l(_set_lock); + + if (name == MAGNESIUM_GAIN1) { + return _ad9371_att[RX_DIRECTION]; + } else if (name == MAGNESIUM_GAIN2) { + return _dsa_att[RX_DIRECTION]; + } else if (name == MAGNESIUM_AMP) { + return _amp_bypass[RX_DIRECTION] ? AMP_MIN_GAIN : AMP_MAX_GAIN; + } else { + throw uhd::value_error("Could not find gain element " + name); + } +} + +double magnesium_radio_control_impl::set_tx_gain( + const double gain, const std::string& name, const size_t chan) +{ + if (get_tx_gain_profile(chan) == "manual") { + if (name == "all" || name == ALL_GAINS) { + RFNOC_LOG_ERROR("Setting overall gain is not supported in manual gain mode!"); + throw uhd::key_error( + "Setting overall gain is not supported in manual gain mode!"); + } + if (name != MAGNESIUM_GAIN1 && name != MAGNESIUM_GAIN2 && name != MAGNESIUM_AMP) { + RFNOC_LOG_ERROR("Invalid TX gain name: " << name); + throw uhd::key_error("Invalid TX gain name!"); + } + const double coerced_gain = get_tx_gain_range(name, chan).clip(gain, true); + if (name == MAGNESIUM_GAIN1) { + _ad9371_att[TX_DIRECTION] = AD9371_MAX_TX_GAIN - coerced_gain; + } else if (name == MAGNESIUM_GAIN2) { + _dsa_set_att(AD9371_MAX_TX_GAIN - coerced_gain, chan, TX_DIRECTION); + } else if (name == MAGNESIUM_AMP) { + _amp_bypass[TX_DIRECTION] = (coerced_gain == AMP_MIN_GAIN); + } else { + throw uhd::value_error("Could not find gain element " + name); + } + _set_all_gain(coerced_gain /* this value doesn't actuall matter */, + get_tx_frequency(chan), + chan, + TX_DIRECTION); + return coerced_gain; + } + + if (name == "all" || name == ALL_GAINS) { + return set_tx_gain(gain, chan); + } + RFNOC_LOG_ERROR("Setting individual TX gains is only supported in manual gain mode!"); + throw uhd::key_error( + "Setting individual TX gains is only supported in manual gain mode!"); +} + +double magnesium_radio_control_impl::set_rx_gain( + const double gain, const std::string& name, const size_t chan) +{ + if (get_rx_gain_profile(chan) == "manual") { + if (name == "all" || name == ALL_GAINS) { + RFNOC_LOG_ERROR("Setting overall gain is not supported in manual gain mode!"); + throw uhd::key_error( + "Setting overall gain is not supported in manual gain mode!"); + } + if (name != MAGNESIUM_GAIN1 && name != MAGNESIUM_GAIN2 && name != MAGNESIUM_AMP) { + RFNOC_LOG_ERROR("Invalid RX gain name: " << name); + throw uhd::key_error("Invalid RX gain name!"); + } + const double coerced_gain = get_rx_gain_range(name, chan).clip(gain, true); + if (name == MAGNESIUM_GAIN1) { + _ad9371_att[RX_DIRECTION] = AD9371_MAX_RX_GAIN - coerced_gain; + } else if (name == MAGNESIUM_GAIN2) { + _dsa_set_att(AD9371_MAX_RX_GAIN - coerced_gain, chan, RX_DIRECTION); + } else if (name == MAGNESIUM_AMP) { + _amp_bypass[RX_DIRECTION] = (coerced_gain == AMP_MIN_GAIN); + } else { + throw uhd::value_error("Could not find gain element " + name); + } + _set_all_gain(coerced_gain /* this value doesn't actuall matter */, + get_rx_frequency(chan), + chan, + RX_DIRECTION); + return coerced_gain; + } + + if (name == "all" || name == ALL_GAINS) { + return set_rx_gain(gain, chan); + } + RFNOC_LOG_ERROR("Setting individual RX gains is only supported in manual gain mode!"); + throw uhd::key_error( + "Setting individual RX gains is only supported in manual gain mode!"); +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_antennas(const size_t) const +{ + return {"TX/RX"}; +} + +std::vector<std::string> magnesium_radio_control_impl::get_rx_antennas(const size_t) const +{ + return MAGNESIUM_RX_ANTENNAS; +} + +uhd::freq_range_t magnesium_radio_control_impl::get_tx_frequency_range(const size_t) const +{ + return meta_range_t(MAGNESIUM_MIN_FREQ, MAGNESIUM_MAX_FREQ, 1.0); +} + +uhd::freq_range_t magnesium_radio_control_impl::get_rx_frequency_range(const size_t) const +{ + return meta_range_t(MAGNESIUM_MIN_FREQ, MAGNESIUM_MAX_FREQ, 1.0); +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_gain_names(const size_t) const +{ + return {MAGNESIUM_GAIN1, MAGNESIUM_GAIN2, MAGNESIUM_AMP}; +} + +std::vector<std::string> magnesium_radio_control_impl::get_rx_gain_names(const size_t) const +{ + return {MAGNESIUM_GAIN1, MAGNESIUM_GAIN2, MAGNESIUM_AMP}; +} + +double magnesium_radio_control_impl::get_tx_gain( + const std::string& name, const size_t chan) +{ + if (name == MAGNESIUM_GAIN1 || name == MAGNESIUM_GAIN2 || name == MAGNESIUM_AMP) { + return _get_tx_gain(name, chan); + } + if (name == "all" || name == ALL_GAINS) { + return radio_control_impl::get_tx_gain(chan); + } + RFNOC_LOG_ERROR("Invalid TX gain name: " << name); + throw uhd::key_error("Invalid TX gain name!"); +} + +double magnesium_radio_control_impl::get_rx_gain( + const std::string& name, const size_t chan) +{ + if (name == MAGNESIUM_GAIN1 || name == MAGNESIUM_GAIN2 || name == MAGNESIUM_AMP) { + return _get_rx_gain(name, chan); + } + if (name == "all" || name == ALL_GAINS) { + return radio_control_impl::get_rx_gain(chan); + } + RFNOC_LOG_ERROR("Invalid RX gain name: " << name); + throw uhd::key_error("Invalid RX gain name!"); +} + +uhd::gain_range_t magnesium_radio_control_impl::get_tx_gain_range(const size_t chan) const +{ + if (get_tx_gain_profile(chan) == "manual") { + return meta_range_t(0.0, 0.0, 0.0); + } + return meta_range_t(ALL_TX_MIN_GAIN, ALL_TX_MAX_GAIN, ALL_TX_GAIN_STEP); +} + +uhd::gain_range_t magnesium_radio_control_impl::get_tx_gain_range( + const std::string& name, const size_t chan) const +{ + if (get_tx_gain_profile(chan) == "manual") { + if (name == "all" || name == ALL_GAINS) { + return meta_range_t(0.0, 0.0, 0.0); + } + if (name == MAGNESIUM_GAIN1) { + return meta_range_t( + AD9371_MIN_TX_GAIN, AD9371_MAX_TX_GAIN, AD9371_TX_GAIN_STEP); + } + if (name == MAGNESIUM_GAIN2) { + return meta_range_t(DSA_MIN_GAIN, DSA_MAX_GAIN, DSA_GAIN_STEP); + } + if (name == MAGNESIUM_AMP) { + return meta_range_t(AMP_MIN_GAIN, AMP_MAX_GAIN, AMP_GAIN_STEP); + } + RFNOC_LOG_ERROR("Invalid TX gain name: " << name); + throw uhd::key_error("Invalid TX gain name!"); + } + if (name == "all" || name == ALL_GAINS) { + return get_tx_gain_range(chan); + } + if (name == MAGNESIUM_GAIN1 || name == MAGNESIUM_GAIN2 || name == MAGNESIUM_AMP) { + return meta_range_t(0.0, 0.0, 0.0); + } + RFNOC_LOG_ERROR("Invalid TX gain name: " << name); + throw uhd::key_error("Invalid TX gain name!"); +} + +uhd::gain_range_t magnesium_radio_control_impl::get_rx_gain_range(const size_t chan) const +{ + if (get_rx_gain_profile(chan) == "manual") { + return meta_range_t(0.0, 0.0, 0.0); + } + return meta_range_t(ALL_RX_MIN_GAIN, ALL_RX_MAX_GAIN, ALL_RX_GAIN_STEP); +} + +uhd::gain_range_t magnesium_radio_control_impl::get_rx_gain_range( + const std::string& name, const size_t chan) const +{ + if (get_rx_gain_profile(chan) == "manual") { + if (name == "all" || name == ALL_GAINS) { + return meta_range_t(0.0, 0.0, 0.0); + } + if (name == MAGNESIUM_GAIN1) { + return meta_range_t( + AD9371_MIN_RX_GAIN, AD9371_MAX_RX_GAIN, AD9371_RX_GAIN_STEP); + } + if (name == MAGNESIUM_GAIN2) { + return meta_range_t(DSA_MIN_GAIN, DSA_MAX_GAIN, DSA_GAIN_STEP); + } + if (name == MAGNESIUM_AMP) { + return meta_range_t(AMP_MIN_GAIN, AMP_MAX_GAIN, AMP_GAIN_STEP); + } + RFNOC_LOG_ERROR("Invalid RX gain name: " << name); + throw uhd::key_error("Invalid RX gain name!"); + } + if (name == "all" || name == ALL_GAINS) { + return get_rx_gain_range(chan); + } + if (name == MAGNESIUM_GAIN1 || name == MAGNESIUM_GAIN2 || name == MAGNESIUM_AMP) { + return meta_range_t(0.0, 0.0, 0.0); + } + RFNOC_LOG_ERROR("Invalid RX gain name: " << name); + throw uhd::key_error("Invalid RX gain name!"); +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_gain_profile_names( + const size_t) const +{ + return MAGNESIUM_GP_OPTIONS; +} + +std::vector<std::string> magnesium_radio_control_impl::get_rx_gain_profile_names(const size_t ) const +{ + return MAGNESIUM_GP_OPTIONS; +} + +std::string magnesium_radio_control_impl::get_tx_gain_profile(const size_t) const +{ + return _gain_profile.at(TX_DIRECTION); +} + +std::string magnesium_radio_control_impl::get_rx_gain_profile(const size_t) const +{ + return _gain_profile.at(RX_DIRECTION); +} + +meta_range_t magnesium_radio_control_impl::get_tx_bandwidth_range(size_t) const +{ + return meta_range_t(AD9371_TX_MIN_BANDWIDTH, AD9371_TX_MAX_BANDWIDTH); +} + +meta_range_t magnesium_radio_control_impl::get_rx_bandwidth_range(size_t) const +{ + return meta_range_t(AD9371_TX_MIN_BANDWIDTH, AD9371_TX_MAX_BANDWIDTH); +} + + +/****************************************************************************** + * LO Controls + *****************************************************************************/ +std::vector<std::string> magnesium_radio_control_impl::get_rx_lo_names( + const size_t /*chan*/ + ) const +{ + return std::vector<std::string>{MAGNESIUM_LO1, MAGNESIUM_LO2}; +} + +std::vector<std::string> magnesium_radio_control_impl::get_rx_lo_sources( + const std::string& name, const size_t /*chan*/ + ) const +{ + if (name == MAGNESIUM_LO2) { + return std::vector<std::string>{"internal"}; + } else if (name == MAGNESIUM_LO1) { + return std::vector<std::string>{"internal", "external"}; + } else { + throw uhd::value_error("Could not find LO stage " + name); + } +} + +freq_range_t magnesium_radio_control_impl::get_rx_lo_freq_range( + const std::string& name, const size_t /*chan*/ +) const +{ + if (name == MAGNESIUM_LO1) { + return freq_range_t{ADF4351_MIN_FREQ, ADF4351_MAX_FREQ}; + } else if (name == MAGNESIUM_LO2) { + return freq_range_t{AD9371_MIN_FREQ, AD9371_MAX_FREQ}; + } else { + throw uhd::value_error("Could not find LO stage " + name); + } +} + +void magnesium_radio_control_impl::set_rx_lo_source( + const std::string& src, const std::string& name, const size_t /*chan*/ +) +{ + // TODO: checking what options are there + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("Setting RX LO " << name << " to " << src); + + if (name == MAGNESIUM_LO1) { + _ad9371->set_lo_source(src, RX_DIRECTION); + } else { + RFNOC_LOG_ERROR( + "RX LO " << name << " does not support setting source to " << src); + } +} + +const std::string magnesium_radio_control_impl::get_rx_lo_source( + const std::string& name, const size_t /*chan*/ +) const +{ + if (name == MAGNESIUM_LO1) { + // TODO: should we use this from cache? + return _ad9371->get_lo_source(RX_DIRECTION); + } + return "internal"; +} + +double magnesium_radio_control_impl::_set_rx_lo_freq(const std::string source, + const std::string name, + const double freq, + const size_t chan) +{ + double coerced_lo_freq = freq; + if (source != "internal") { + RFNOC_LOG_WARNING( + "LO source is not internal. This set frequency will be ignored"); + if (name == MAGNESIUM_LO1) { + // handle ad9371 external LO case + coerced_lo_freq = freq; + _ad9371_freq[RX_DIRECTION] = coerced_lo_freq; + } + } else { + if (name == MAGNESIUM_LO1) { + coerced_lo_freq = _ad9371->set_frequency(freq, chan, RX_DIRECTION); + _ad9371_freq[RX_DIRECTION] = coerced_lo_freq; + } else if (name == MAGNESIUM_LO2) { + // TODO: no hardcode the init_n_mode + coerced_lo_freq = _lo_enable(_rx_lo, freq, _master_clock_rate, false); + _adf4351_freq[RX_DIRECTION] = coerced_lo_freq; + } else { + RFNOC_LOG_WARNING("There's no LO with this name of " + << name + << " in the system. This set rx lo freq will be ignored"); + }; + } + return coerced_lo_freq; +} + +double magnesium_radio_control_impl::set_rx_lo_freq( + double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_freq(freq=" << freq << ", name=" << name << ")"); + std::lock_guard<std::mutex> l(_set_lock); + std::string source = this->get_rx_lo_source(name, chan); + const double coerced_lo_freq = this->_set_rx_lo_freq(source, name, freq, chan); + this->_update_freq(chan, RX_DIRECTION); + this->_update_gain(chan, RX_DIRECTION); + return coerced_lo_freq; +} + +double magnesium_radio_control_impl::get_rx_lo_freq( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_freq(name=" << name << ")"); + std::string source = this->get_rx_lo_source(name, chan); + if (name == MAGNESIUM_LO1) { + return _ad9371_freq.at(RX_DIRECTION); + } else if (name == "adf4531") { + return _adf4351_freq.at(RX_DIRECTION); + } else { + RFNOC_LOG_ERROR("get_rx_lo_freq(): No such LO: " << name); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +// TX LO +std::vector<std::string> magnesium_radio_control_impl::get_tx_lo_names( + const size_t /*chan*/ +) const +{ + return std::vector<std::string>{MAGNESIUM_LO1, MAGNESIUM_LO2}; +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_lo_sources( + const std::string& name, const size_t /*chan*/ +) const +{ + if (name == MAGNESIUM_LO2) { + return std::vector<std::string>{"internal"}; + } else if (name == MAGNESIUM_LO1) { + return std::vector<std::string>{"internal", "external"}; + } else { + throw uhd::value_error("Could not find LO stage " + name); + } +} + +freq_range_t magnesium_radio_control_impl::get_tx_lo_freq_range( + const std::string& name, const size_t /*chan*/ +) +{ + if (name == MAGNESIUM_LO2) { + return freq_range_t{ADF4351_MIN_FREQ, ADF4351_MAX_FREQ}; + } else if (name == MAGNESIUM_LO1) { + return freq_range_t{AD9371_MIN_FREQ, AD9371_MAX_FREQ}; + } else { + throw uhd::value_error("Could not find LO stage " + name); + } +} + +void magnesium_radio_control_impl::set_tx_lo_source( + const std::string& src, const std::string& name, const size_t /*chan*/ +) +{ + // TODO: checking what options are there + std::lock_guard<std::mutex> l(_set_lock); + RFNOC_LOG_TRACE("set_tx_lo_source(name=" << name << ", src=" << src << ")"); + if (name == MAGNESIUM_LO1) { + _ad9371->set_lo_source(src, TX_DIRECTION); + } else { + RFNOC_LOG_ERROR( + "TX LO " << name << " does not support setting source to " << src); + } +} + +const std::string magnesium_radio_control_impl::get_tx_lo_source( + const std::string& name, const size_t /*chan*/ +) +{ + if (name == MAGNESIUM_LO1) { + // TODO: should we use this from cache? + return _ad9371->get_lo_source(TX_DIRECTION); + } + return "internal"; +} + +double magnesium_radio_control_impl::_set_tx_lo_freq(const std::string source, + const std::string name, + const double freq, + const size_t chan) +{ + double coerced_lo_freq = freq; + if (source != "internal") { + RFNOC_LOG_WARNING( + "LO source is not internal. This set frequency will be ignored"); + if (name == MAGNESIUM_LO1) { + // handle ad9371 external LO case + coerced_lo_freq = freq; + _ad9371_freq[TX_DIRECTION] = coerced_lo_freq; + } + } else { + if (name == MAGNESIUM_LO1) { + coerced_lo_freq = _ad9371->set_frequency(freq, chan, TX_DIRECTION); + _ad9371_freq[TX_DIRECTION] = coerced_lo_freq; + } else if (name == MAGNESIUM_LO2) { + // TODO: no hardcode the int_n_mode + const bool int_n_mode = false; + coerced_lo_freq = _lo_enable(_tx_lo, freq, _master_clock_rate, int_n_mode); + _adf4351_freq[TX_DIRECTION] = coerced_lo_freq; + } else { + RFNOC_LOG_WARNING("There's no LO with this name of " + << name + << " in the system. This set tx lo freq will be ignored"); + }; + } + return coerced_lo_freq; +} + +double magnesium_radio_control_impl::set_tx_lo_freq( + double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_freq(freq=" << freq << ", name=" << name << ")"); + std::string source = this->get_tx_lo_source(name, chan); + const double return_freq = this->_set_tx_lo_freq(source, name, freq, chan); + this->_update_freq(chan, TX_DIRECTION); + this->_update_gain(chan, TX_DIRECTION); + return return_freq; +} + +double magnesium_radio_control_impl::get_tx_lo_freq(const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_freq(name=" << name << ")"); + std::string source = this->get_tx_lo_source(name, chan); + if (name == MAGNESIUM_LO1) { + return _ad9371_freq[TX_DIRECTION]; + } else if (name == MAGNESIUM_LO2) { + return _adf4351_freq[TX_DIRECTION]; + } else { + RFNOC_LOG_ERROR("get_tx_lo_freq(): No such LO: " << name); + }; + + UHD_THROW_INVALID_CODE_PATH(); +} + +void magnesium_radio_control_impl::_remap_band_limits( + const std::string band_map, const uhd::direction_t dir) +{ + const size_t dflt_band_size = (dir == RX_DIRECTION) ? _rx_band_map.size() + : _tx_band_map.size(); + + std::vector<std::string> band_map_split; + double band_lim; + + RFNOC_LOG_DEBUG("Using user specified frequency band limits"); + boost::split(band_map_split, band_map, boost::is_any_of(";")); + if (band_map_split.size() != dflt_band_size) { + throw uhd::runtime_error(( + boost::format( + "size %s of given frequency band map doesn't match the required size: %s") + % band_map_split.size() % dflt_band_size) + .str()); + } + RFNOC_LOG_DEBUG("newly used band limits: "); + for (size_t i = 0; i < band_map_split.size(); i++) { + try { + band_lim = std::stod(band_map_split.at(i)); + } catch (...) { + throw uhd::value_error( + (boost::format("error while converting given frequency string %s " + "to a double value") + % band_map_split.at(i)) + .str()); + } + RFNOC_LOG_DEBUG("band " << i << " limit: " << band_lim << "Hz"); + if (dir == RX_DIRECTION) + _rx_band_map.at(i) = band_lim; + else + _tx_band_map.at(i) = band_lim; + } +} + + +bool magnesium_radio_control_impl::get_lo_lock_status(const direction_t dir) +{ + if (not(bool(_rpcc))) { + RFNOC_LOG_WARNING("Reported no LO lock due to lack of RPC connection."); + return false; + } + + const std::string trx = (dir == RX_DIRECTION) ? "rx" : "tx"; + const size_t chan = 0; // They're the same after all + const double freq = (dir == RX_DIRECTION) ? get_rx_frequency(chan) + : get_tx_frequency(chan); + + bool lo_lock = + _rpcc->request_with_token<bool>(_rpc_prefix + "get_ad9371_lo_lock", trx); + RFNOC_LOG_TRACE("AD9371 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); + if (lo_lock and _map_freq_to_rx_band(_rx_band_map, freq) == rx_band::LOWBAND) { + lo_lock = + lo_lock + && _rpcc->request_with_token<bool>(_rpc_prefix + "get_lowband_lo_lock", trx); + RFNOC_LOG_TRACE( + "ADF4351 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); + } + + return lo_lock; +} + +/************************************************************************** + * GPIO Controls + *************************************************************************/ +std::vector<std::string> magnesium_radio_control_impl::get_gpio_banks() const +{ + return {MAGNESIUM_FPGPIO_BANK}; +} + +void magnesium_radio_control_impl::set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value) +{ + if (bank != MAGNESIUM_FPGPIO_BANK) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + if (!gpio_atr::gpio_attr_rev_map.count(attr)) { + RFNOC_LOG_ERROR("Invalid GPIO attr: " << attr); + throw uhd::key_error("Invalid GPIO attr!"); + } + + const gpio_atr::gpio_attr_t gpio_attr = gpio_atr::gpio_attr_rev_map.at(attr); + + if (gpio_attr == gpio_atr::GPIO_READBACK) { + RFNOC_LOG_WARNING("Cannot set READBACK attr."); + return; + } + + _fp_gpio->set_gpio_attr(gpio_attr, value); +} + +uint32_t magnesium_radio_control_impl::get_gpio_attr( + const std::string& bank, const std::string& attr) +{ + if (bank != MAGNESIUM_FPGPIO_BANK) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + + return _fp_gpio->get_attr_reg(usrp::gpio_atr::gpio_attr_rev_map.at(attr)); +} + +/****************************************************************************** + * EEPROM API + *****************************************************************************/ +void magnesium_radio_control_impl::set_db_eeprom(const eeprom_map_t& db_eeprom) +{ + const size_t db_idx = get_block_id().get_block_count(); + _rpcc->notify_with_token("set_db_eeprom", db_idx, db_eeprom); +} + +eeprom_map_t magnesium_radio_control_impl::get_db_eeprom() +{ + const size_t db_idx = get_block_id().get_block_count(); + return this->_rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", db_idx); +} + +/************************************************************************** + * Sensor API + *************************************************************************/ +std::vector<std::string> magnesium_radio_control_impl::get_rx_sensor_names(size_t) +{ + auto sensor_names = _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", "RX"); + sensor_names.push_back("lo_locked"); + return sensor_names; +} + +sensor_value_t magnesium_radio_control_impl::get_rx_sensor(const std::string& name, size_t chan) +{ + if (name == "lo_locked") { + return sensor_value_t( + "all_los", this->get_lo_lock_status(RX_DIRECTION), "locked", "unlocked"); + } + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "RX", name, chan)); +} + +std::vector<std::string> magnesium_radio_control_impl::get_tx_sensor_names(size_t) +{ + auto sensor_names = _rpcc->request_with_token<std::vector<std::string>>( + this->_rpc_prefix + "get_sensors", "TX"); + sensor_names.push_back("lo_locked"); + return sensor_names; +} + +sensor_value_t magnesium_radio_control_impl::get_tx_sensor(const std::string& name, size_t chan) +{ + if (name == "lo_locked") { + return sensor_value_t( + "all_los", this->get_lo_lock_status(TX_DIRECTION), "locked", "unlocked"); + } + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "TX", name, chan)); +} + +/************************************************************************** + * node_t API Calls + *************************************************************************/ +void magnesium_radio_control_impl::set_command_time(uhd::time_spec_t time, const size_t chan) +{ + node_t::set_command_time(time, chan); + _wb_ifaces.at(chan)->set_time(time); +} + +/************************************************************************** + * Radio Identification API Calls + *************************************************************************/ +size_t magnesium_radio_control_impl::get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t) const +{ + if (fe == "0") { + return 0; + } + if (fe == "1") { + return 1; + } + throw uhd::key_error(std::string("[N300] Invalid frontend: ") + fe); +} + +std::string magnesium_radio_control_impl::get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t) const +{ + if (chan == 0) { + return "0"; + } + if (chan == 1) { + return "1"; + } + throw uhd::lookup_error( + std::string("[N300] Invalid channel: ") + std::to_string(chan)); +} + +std::string magnesium_radio_control_impl::get_fe_name( + const size_t, const uhd::direction_t) const +{ + return MAGNESIUM_FE_NAME; +} + +// Register the block +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + magnesium_radio_control, RADIO_BLOCK, N300, "Radio", true, "radio_clk", "bus_clk"); diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control.hpp index 165e3c996..d7c721c3b 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control.hpp @@ -14,12 +14,14 @@ #include "magnesium_ad9371_iface.hpp" #include "magnesium_cpld_ctrl.hpp" #include "magnesium_cpld_regs.hpp" +#include <iostream> #include <uhd/types/serial.hpp> #include <uhd/usrp/dboard_manager.hpp> #include <uhd/usrp/gpio_defs.hpp> -#include <uhdlib/rfnoc/radio_ctrl_impl.hpp> -#include <uhdlib/rfnoc/rpc_block_ctrl.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhdlib/rfnoc/radio_control_impl.hpp> #include <uhdlib/usrp/common/adf435x.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> #include <uhdlib/usrp/cores/gpio_atr_3000.hpp> #include <mutex> @@ -29,13 +31,9 @@ namespace uhd { namespace rfnoc { * * This daughterboard is used on the USRP N310 and N300. */ - - -class magnesium_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl +class magnesium_radio_control_impl : public radio_control_impl { public: - typedef boost::shared_ptr<magnesium_radio_ctrl_impl> sptr; - //! Frequency bands for RX. Bands are a function of the analog filter banks enum class rx_band { INVALID_BAND, @@ -68,66 +66,109 @@ public: /************************************************************************ * Structors ***********************************************************************/ - UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(magnesium_radio_ctrl) - virtual ~magnesium_radio_ctrl_impl(); + magnesium_radio_control_impl(make_args_ptr make_args); + virtual ~magnesium_radio_control_impl(); /************************************************************************ - * API calls + * RF API calls ***********************************************************************/ // Note: We use the cached values in radio_ctrl_impl, so most getters are // not reimplemented here double set_rate(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); - + // Setters + 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 get_rx_frequency(const size_t chan); - double get_tx_frequency(const size_t chan); + double set_tx_gain(const double gain, const size_t chan); + double set_tx_gain(const double gain, const std::string& name, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const std::string& name, 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); + void set_tx_gain_profile(const std::string& profile, const size_t chan); + void set_rx_gain_profile(const std::string& profile, const size_t chan); + + // Getters + std::vector<std::string> get_tx_antennas(const size_t chan) const; + std::vector<std::string> get_rx_antennas(const size_t chan) const; + uhd::freq_range_t get_tx_frequency_range(const size_t chan) const; + uhd::freq_range_t get_rx_frequency_range(const size_t chan) const; + std::vector<std::string> get_tx_gain_names(const size_t) const; + std::vector<std::string> get_rx_gain_names(const size_t) const; + double get_tx_gain(const std::string&, size_t); + double get_rx_gain(const std::string&, size_t); + uhd::gain_range_t get_tx_gain_range(const size_t) const; + uhd::gain_range_t get_tx_gain_range(const std::string&, const size_t) const; + uhd::gain_range_t get_rx_gain_range(const size_t) const; + uhd::gain_range_t get_rx_gain_range(const std::string&, const size_t) const; + std::vector<std::string> get_tx_gain_profile_names(const size_t chan) const; + std::vector<std::string> get_rx_gain_profile_names(const size_t chan) const; + std::string get_tx_gain_profile(const size_t chan) const; + std::string get_rx_gain_profile(const size_t chan) const; + uhd::meta_range_t get_tx_bandwidth_range(size_t chan) const; + uhd::meta_range_t get_rx_bandwidth_range(size_t chan) const; - // RX LO - std::vector<std::string> get_rx_lo_names(const size_t chan); + /************************************************************************** + * LO Controls + *************************************************************************/ + std::vector<std::string> get_rx_lo_names(const size_t chan) const; std::vector<std::string> get_rx_lo_sources( - const std::string& name, const size_t chan); - freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan); - + const std::string& name, const size_t chan) const; + freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan) const; void set_rx_lo_source( const std::string& src, const std::string& name, const size_t chan); - const std::string get_rx_lo_source(const std::string& name, const size_t chan); - + const std::string get_rx_lo_source(const std::string& name, const size_t chan) const; double set_rx_lo_freq(double freq, const std::string& name, const size_t chan); double get_rx_lo_freq(const std::string& name, const size_t chan); - - // TX LO - std::vector<std::string> get_tx_lo_names(const size_t chan); + std::vector<std::string> get_tx_lo_names(const size_t chan) const; std::vector<std::string> get_tx_lo_sources( - const std::string& name, const size_t chan); + const std::string& name, const size_t chan) const; freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan); - void set_tx_lo_source( const std::string& src, const std::string& name, const size_t chan); const std::string get_tx_lo_source(const std::string& name, const size_t chan); - - double set_tx_lo_freq(double freq, const std::string& name, const size_t chan); + double set_tx_lo_freq(const double freq, const std::string& name, const size_t chan); double get_tx_lo_freq(const std::string& name, 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); - void set_tx_gain_source( - const std::string& src, const std::string& name, const size_t chan); - std::string get_tx_gain_source(const std::string& name, const size_t chan); - void set_rx_gain_source( - const std::string& src, const std::string& name, const size_t chan); - std::string get_rx_gain_source(const std::string& name, const size_t chan); + /************************************************************************** + * GPIO Controls + *************************************************************************/ + std::vector<std::string> get_gpio_banks() const; + void set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value); + uint32_t get_gpio_attr(const std::string& bank, const std::string& attr); + + /************************************************************************** + * EEPROM API + *************************************************************************/ + void set_db_eeprom(const uhd::eeprom_map_t& db_eeprom); + uhd::eeprom_map_t get_db_eeprom(); + + /************************************************************************** + * Sensor API + *************************************************************************/ + std::vector<std::string> get_rx_sensor_names(size_t chan); + uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan); + std::vector<std::string> get_tx_sensor_names(size_t chan); + uhd::sensor_value_t get_tx_sensor(const std::string& name, 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); + /************************************************************************** + * Radio Identification API Calls + *************************************************************************/ + std::string get_slot_name() const { return _radio_slot; } + size_t get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t direction) const; + std::string get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t direction) const; + std::string get_fe_name( + const size_t chan, const uhd::direction_t direction) const; - void set_rpc_client(uhd::rpc_client::sptr rpcc, const uhd::device_addr_t& block_args); + /************************************************************************** + * node_t API Calls + *************************************************************************/ + void set_command_time(uhd::time_spec_t time, const size_t chan); private: /************************************************************************** @@ -157,6 +198,10 @@ private: //! Initialize property tree void _init_prop_tree(); + //! Init RPC interaction + void _init_mpm(); + + //! Set up sensor property nodes 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 @@ -240,12 +285,21 @@ private: //! Additional block args; gets set during set_rpc_client() uhd::device_addr_t _block_args; + //! Reference to the MB controller + mpmd_mb_controller::sptr _n310_mb_control; + + //! Reference to the MB timekeeper + uhd::rfnoc::mpmd_mb_controller::mpmd_timekeeper::sptr _n3xx_timekeeper; + //! Reference to the RPC client uhd::rpc_client::sptr _rpcc; //! Reference to the SPI core uhd::spi_iface::sptr _spi; + //! Reference to wb_iface compat adapters (one per channel) + std::vector<uhd::timed_wb_iface::sptr> _wb_ifaces; + //! Reference to the TX LO adf435x_iface::sptr _tx_lo; diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_cpld.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_cpld.cpp index 679816af8..41f99cd68 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_cpld.cpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_cpld.cpp @@ -6,14 +6,14 @@ #include "magnesium_constants.hpp" #include "magnesium_cpld_ctrl.hpp" -#include "magnesium_radio_ctrl_impl.hpp" +#include "magnesium_radio_control.hpp" #include <uhd/utils/log.hpp> using namespace uhd; using namespace uhd::usrp; using namespace uhd::rfnoc; -void magnesium_radio_ctrl_impl::_identify_with_leds(const int identify_duration) +void magnesium_radio_control_impl::_identify_with_leds(const int identify_duration) { auto end_time = std::chrono::steady_clock::now() + std::chrono::seconds(identify_duration); @@ -36,7 +36,7 @@ void magnesium_radio_ctrl_impl::_identify_with_leds(const int identify_duration) _cpld->reset(); } -void magnesium_radio_ctrl_impl::_update_atr_switches( +void magnesium_radio_control_impl::_update_atr_switches( const magnesium_cpld_ctrl::chan_sel_t chan, const direction_t dir, const std::string& ant) @@ -51,8 +51,7 @@ void magnesium_radio_ctrl_impl::_update_atr_switches( // RX SW1. In all other cases, a TX state toggle (on to idle or vice // versa) won't trigger a change of the TRX switch. auto sw_trx = _sw_trx[chan]; - UHD_LOG_TRACE( - unique_id(), "Updating all RX-ATR related switches for antenna==" << ant); + RFNOC_LOG_TRACE("Updating all RX-ATR related switches for antenna==" << ant); if (ant == "TX/RX") { rx_sw1 = magnesium_cpld_ctrl::RX_SW1_TRXSWITCHOUTPUT; sw_trx = magnesium_cpld_ctrl::SW_TRX_RXCHANNELPATH; @@ -96,7 +95,7 @@ void magnesium_radio_ctrl_impl::_update_atr_switches( ); } if (dir == TX_DIRECTION or dir == DX_DIRECTION) { - UHD_LOG_TRACE(unique_id(), "Updating all TX-ATR related switches..."); + RFNOC_LOG_TRACE("Updating all TX-ATR related switches..."); _cpld->set_tx_atr_bits(chan, magnesium_cpld_ctrl::ON, true, /* LED on */ @@ -117,11 +116,11 @@ void magnesium_radio_ctrl_impl::_update_atr_switches( }; } -void magnesium_radio_ctrl_impl::_update_rx_freq_switches(const double freq, +void magnesium_radio_control_impl::_update_rx_freq_switches(const double freq, const bool bypass_lnas, const magnesium_cpld_ctrl::chan_sel_t chan_sel) { - UHD_LOG_TRACE(unique_id(), + RFNOC_LOG_TRACE( "Update all RX freq related switches. f=" << freq << " Hz, " "bypass LNAS: " @@ -142,7 +141,7 @@ void magnesium_radio_ctrl_impl::_update_rx_freq_switches(const double freq, not bypass_lnas and (band == rx_band::BAND4 or band == rx_band::BAND5 or band == rx_band::BAND6); const bool rx_lna2_enable = not bypass_lnas and not rx_lna1_enable; - UHD_LOG_TRACE(unique_id(), + RFNOC_LOG_TRACE( " Enabling LNA1: " << (rx_lna1_enable ? "Yes" : "No") << " Enabling LNA2: " << (rx_lna2_enable ? "Yes" : "No")); // All the defaults are OK when using the bypass path. @@ -199,7 +198,7 @@ void magnesium_radio_ctrl_impl::_update_rx_freq_switches(const double freq, rx_sw6 = magnesium_cpld_ctrl::RX_SW6_UPPERFILTERBANKFROMSWITCH4; break; case rx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), "Cannot map RX frequency to band: " << freq); + RFNOC_LOG_ERROR("Cannot map RX frequency to band: " << freq); break; default: UHD_THROW_INVALID_CODE_PATH(); @@ -222,16 +221,15 @@ void magnesium_radio_ctrl_impl::_update_rx_freq_switches(const double freq, enable_lowband_mixer); } -void magnesium_radio_ctrl_impl::_update_tx_freq_switches(const double freq, +void magnesium_radio_control_impl::_update_tx_freq_switches(const double freq, const bool bypass_amp, const magnesium_cpld_ctrl::chan_sel_t chan_sel) { - UHD_LOG_TRACE(unique_id(), - "Update all TX freq related switches. f=" << freq - << " Hz, " - "bypass amp: " - << (bypass_amp ? "Yes" : "No") - << ", chan=" << chan_sel); + RFNOC_LOG_TRACE("Update all TX freq related switches. f=" + << freq + << " Hz, " + "bypass amp: " + << (bypass_amp ? "Yes" : "No") << ", chan=" << chan_sel); auto tx_sw1 = magnesium_cpld_ctrl::TX_SW1_SHUTDOWNTXSW1; auto tx_sw2 = magnesium_cpld_ctrl::TX_SW2_TOTXFILTERLP6400MHZ; auto tx_sw3 = magnesium_cpld_ctrl::TX_SW3_BYPASSPATHTOTRXSW; @@ -279,7 +277,7 @@ void magnesium_radio_ctrl_impl::_update_tx_freq_switches(const double freq, tx_sw3 = magnesium_cpld_ctrl::TX_SW3_TOTXFILTERBANKS; break; case tx_band::INVALID_BAND: - UHD_LOG_ERROR(unique_id(), "Cannot map TX frequency to band: " << freq); + RFNOC_LOG_ERROR("Cannot map TX frequency to band: " << freq); break; default: UHD_THROW_INVALID_CODE_PATH(); diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_gain.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_gain.cpp index b66bd2efd..f515b2e33 100644 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_gain.cpp +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_gain.cpp @@ -6,63 +6,62 @@ #include "magnesium_constants.hpp" #include "magnesium_gain_table.hpp" -#include "magnesium_radio_ctrl_impl.hpp" -#include <uhd/exception.hpp> +#include "magnesium_radio_control.hpp" #include <uhd/utils/log.hpp> +#include <uhd/exception.hpp> using namespace uhd; using namespace uhd::usrp; using namespace uhd::rfnoc; using namespace magnesium; -double magnesium_radio_ctrl_impl::_set_all_gain( +double magnesium_radio_control_impl::_set_all_gain( const double gain, const double freq, const size_t chan, const direction_t dir) { - UHD_LOG_TRACE(unique_id(), - __func__ << "(gain=" << gain - << "dB, " - "freq=" - << freq - << " Hz, " - "chan=" - << chan - << ", " - "dir=" - << dir << ")"); + RFNOC_LOG_TRACE(__func__ << "(gain=" << gain + << "dB, " + "freq=" + << freq + << " Hz, " + "chan=" + << chan + << ", " + "dir=" + << dir); const size_t ad9371_chan = chan; auto chan_sel = static_cast<magnesium_cpld_ctrl::chan_sel_t>(chan); gain_tuple_t gain_tuple; std::string gp = _gain_profile[dir]; - UHD_LOG_TRACE(unique_id(), "Gain profile: " << gp); + RFNOC_LOG_TRACE("Gain profile: " << gp); if (gp == "manual") { - UHD_LOG_TRACE(unique_id(), "Manual gain mode. Getting gain from property tree."); + RFNOC_LOG_TRACE("Manual gain mode. Getting gain from property tree."); gain_tuple = {DSA_MAX_GAIN - _dsa_att[dir], ((dir == RX_DIRECTION) ? AD9371_MAX_RX_GAIN : AD9371_MAX_TX_GAIN) - _ad9371_att[dir], _amp_bypass[dir]}; } else if (gp.find("default") != gp.npos) { - UHD_LOG_TRACE(unique_id(), "Getting gain from gain table."); + RFNOC_LOG_TRACE("Getting gain from gain table."); gain_tuple = (dir == RX_DIRECTION) ? get_rx_gain_tuple(gain, _map_freq_to_rx_band(_rx_band_map, freq)) : get_tx_gain_tuple(gain, _map_freq_to_tx_band(_tx_band_map, freq)); if (gp == "default_rf_filter_bypass_always_on") { - UHD_LOG_TRACE(unique_id(), "Enable filter bypass for all gains"); + RFNOC_LOG_TRACE("Enable filter bypass for all gains"); gain_tuple.bypass = true; } else if (gp == "default_rf_filter_bypass_always_off") { - UHD_LOG_TRACE(unique_id(), "Disable filter bypass for all gains"); + RFNOC_LOG_TRACE("Disable filter bypass for all gains"); gain_tuple.bypass = false; } } else { - UHD_LOG_ERROR(unique_id(), "Unsupported gain mode: " << gp); + RFNOC_LOG_ERROR("Unsupported gain mode: " << gp); throw uhd::value_error( - str(boost::format("[%s] Unsupported gain mode: %s") % unique_id() % gp)); + str(boost::format("[%s] Unsupported gain mode: %s") % get_unique_id() % gp)); } const double ad9371_gain = ((dir == RX_DIRECTION) ? AD9371_MAX_RX_GAIN : AD9371_MAX_TX_GAIN) - gain_tuple.ad9371_att; - UHD_LOG_TRACE(unique_id(), + RFNOC_LOG_TRACE( "AD9371 attenuation==" << gain_tuple.ad9371_att << " dB, " "AD9371 gain==" @@ -86,10 +85,10 @@ double magnesium_radio_ctrl_impl::_set_all_gain( return gain; } -double magnesium_radio_ctrl_impl::_get_all_gain( +double magnesium_radio_control_impl::_get_all_gain( const size_t /* chan */, const direction_t dir) { - UHD_LOG_TRACE(unique_id(), "Getting all gain "); + RFNOC_LOG_TRACE("_get_all_gain()"); if (dir == RX_DIRECTION) { return _all_rx_gain; } @@ -99,11 +98,12 @@ double magnesium_radio_ctrl_impl::_get_all_gain( /****************************************************************************** * DSA Controls *****************************************************************************/ -double magnesium_radio_ctrl_impl::_dsa_set_att( +double magnesium_radio_control_impl::_dsa_set_att( const double att, const size_t chan, const direction_t dir) { - UHD_LOG_TRACE(unique_id(), - __func__ << "(att=" << att << "dB, chan=" << chan << ", dir=" << dir << ")") + RFNOC_LOG_TRACE( + __func__ << "(att=" + << "att dB, chan=" << chan << ", dir=" << dir << ")") const uint32_t dsa_val = 2 * att; _set_dsa_val(chan, dir, dsa_val); @@ -116,7 +116,7 @@ double magnesium_radio_ctrl_impl::_dsa_set_att( return att; } -double magnesium_radio_ctrl_impl::_dsa_get_att( +double magnesium_radio_control_impl::_dsa_get_att( const size_t /*chan*/, const direction_t dir) { if (dir == RX_DIRECTION) { @@ -125,21 +125,19 @@ double magnesium_radio_ctrl_impl::_dsa_get_att( return _dsa_tx_att; } -void magnesium_radio_ctrl_impl::_set_dsa_val( +void magnesium_radio_control_impl::_set_dsa_val( const size_t chan, const direction_t dir, const uint32_t dsa_val) { // The DSA register holds 12 bits. The lower 6 bits are for RX, the upper // 6 bits are for TX. if (dir == RX_DIRECTION or dir == DX_DIRECTION) { - UHD_LOG_TRACE(unique_id(), - __func__ << "(chan=" << chan << ", dir=RX" - << ", dsa_val=" << dsa_val << ")") + RFNOC_LOG_TRACE(__func__ << "(chan=" << chan << ", dir=RX" + << ", dsa_val=" << dsa_val << ")") _gpio[chan]->set_gpio_out(dsa_val, 0x003F); } if (dir == TX_DIRECTION or dir == DX_DIRECTION) { - UHD_LOG_TRACE(unique_id(), - __func__ << "(chan=" << chan << ", dir=TX" - << ", dsa_val=" << dsa_val << ")") + RFNOC_LOG_TRACE(__func__ << "(chan=" << chan << ", dir=TX" + << ", dsa_val=" << dsa_val << ")") _gpio[chan]->set_gpio_out(dsa_val << 6, 0x0FC0); } } diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_control_init.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_init.cpp new file mode 100644 index 000000000..db2ec9494 --- /dev/null +++ b/host/lib/usrp/dboard/magnesium/magnesium_radio_control_init.cpp @@ -0,0 +1,446 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "magnesium_constants.hpp" +#include "magnesium_radio_control.hpp" +#include <uhd/transport/chdr.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/usrp/cores/spi_core_3000.hpp> +#include <uhdlib/rfnoc/reg_iface_adapter.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/make_shared.hpp> +#include <string> +#include <vector> + +using namespace uhd; +using namespace uhd::rfnoc; + +namespace { + +enum slave_select_t { SEN_CPLD = 1, SEN_TX_LO = 2, SEN_RX_LO = 4, SEN_PHASE_DAC = 8 }; + +constexpr double MAGNESIUM_DEFAULT_FREQ = 2.5e9; // Hz +constexpr double MAGNESIUM_DEFAULT_BANDWIDTH = 100e6; // Hz + +} // namespace + +void magnesium_radio_control_impl::_init_defaults() +{ + RFNOC_LOG_TRACE("_init_defaults()"); + for (size_t chan = 0; chan < get_num_output_ports(); chan++) { + radio_control_impl::set_rx_frequency(MAGNESIUM_DEFAULT_FREQ, chan); + radio_control_impl::set_rx_gain(0, chan); + radio_control_impl::set_rx_antenna(MAGNESIUM_DEFAULT_RX_ANTENNA, chan); + radio_control_impl::set_rx_bandwidth(MAGNESIUM_DEFAULT_BANDWIDTH, chan); + } + + for (size_t chan = 0; chan < get_num_input_ports(); chan++) { + radio_control_impl::set_tx_frequency(MAGNESIUM_DEFAULT_FREQ, chan); + radio_control_impl::set_tx_gain(0, chan); + radio_control_impl::set_tx_antenna(MAGNESIUM_DEFAULT_TX_ANTENNA, chan); + radio_control_impl::set_tx_bandwidth(MAGNESIUM_DEFAULT_BANDWIDTH, chan); + } + + const auto block_args = get_block_args(); + if (block_args.has_key("tx_gain_profile")) { + RFNOC_LOG_INFO("Using user specified TX gain profile: " << block_args.get( + "tx_gain_profile")); + _gain_profile[TX_DIRECTION] = block_args.get("tx_gain_profile"); + } + + if (block_args.has_key("rx_gain_profile")) { + RFNOC_LOG_INFO("Using user specified RX gain profile: " << block_args.get( + "rx_gain_profile")); + _gain_profile[RX_DIRECTION] = block_args.get("rx_gain_profile"); + } + + if (block_args.has_key("rx_band_map")) { + RFNOC_LOG_INFO("Using user specified RX band limits"); + _remap_band_limits(block_args.get("rx_band_map"), RX_DIRECTION); + } + + if (block_args.has_key("tx_band_map")) { + RFNOC_LOG_INFO("Using user specified TX band limits"); + _remap_band_limits(block_args.get("tx_band_map"), TX_DIRECTION); + } +} + +void magnesium_radio_control_impl::_init_peripherals() +{ + RFNOC_LOG_TRACE("Initializing peripherals..."); + RFNOC_LOG_TRACE("Initializing SPI core..."); + _spi = spi_core_3000::make( + [this](uint32_t addr, uint32_t data){ regs().poke32(addr, data, get_command_time(0)); }, + [this](uint32_t addr){ return regs().peek32(addr, get_command_time(0)); }, + regmap::REG_SPI_W, + 8, + regmap::REG_SPI_R); + RFNOC_LOG_TRACE("Initializing CPLD..."); + RFNOC_LOG_TRACE("Creating new CPLD object..."); + spi_config_t spi_config; + spi_config.use_custom_divider = true; + spi_config.divider = 125; + spi_config.mosi_edge = spi_config_t::EDGE_RISE; + spi_config.miso_edge = spi_config_t::EDGE_FALL; + RFNOC_LOG_TRACE("Making CPLD object..."); + _cpld = std::make_shared<magnesium_cpld_ctrl>( + [this, spi_config](const uint32_t transaction) { // Write functor + this->_spi->write_spi(SEN_CPLD, spi_config, transaction, 24); + }, + [this, spi_config](const uint32_t transaction) { // Read functor + return this->_spi->read_spi(SEN_CPLD, spi_config, transaction, 24); + }); + _update_atr_switches( + magnesium_cpld_ctrl::BOTH, DX_DIRECTION, radio_control_impl::get_rx_antenna(0)); + RFNOC_LOG_TRACE("Initializing TX LO..."); + _tx_lo = adf435x_iface::make_adf4351([this]( + const std::vector<uint32_t> transactions) { + for (const uint32_t transaction : transactions) { + this->_spi->write_spi(SEN_TX_LO, spi_config_t::EDGE_RISE, transaction, 32); + } + }); + RFNOC_LOG_TRACE("Initializing RX LO..."); + _rx_lo = adf435x_iface::make_adf4351([this]( + const std::vector<uint32_t> transactions) { + for (const uint32_t transaction : transactions) { + this->_spi->write_spi(SEN_RX_LO, spi_config_t::EDGE_RISE, transaction, 32); + } + }); + + _gpio.clear(); // Following the as-if rule, this can get optimized out + for (size_t radio_idx = 0; radio_idx < get_num_input_ports(); radio_idx++) { + _wb_ifaces.push_back(RFNOC_MAKE_WB_IFACE(0, radio_idx)); + RFNOC_LOG_TRACE("Initializing GPIOs for channel " << radio_idx); + _gpio.emplace_back(usrp::gpio_atr::gpio_atr_3000::make( + _wb_ifaces.back(), + n310_regs::DB_GPIO_BASE + radio_idx * n310_regs::DB_GPIO_OFFSET, + n310_regs::DB_GPIO_RB + radio_idx * n310_regs::DB_GPIO_OFFSET)); + // DSA and AD9371 gain bits do *not* toggle on ATR modes. If we ever + // connect anything else to this core, we might need to set_atr_mode() + // to MODE_ATR on those bits. For now, all bits simply do what they're + // told, and don't toggle on RX/TX state changes. + _gpio.back()->set_atr_mode(usrp::gpio_atr::MODE_GPIO, // Disable ATR mode + usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _gpio.back()->set_gpio_ddr(usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs + usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + } + RFNOC_LOG_TRACE("Initializing front-panel GPIO control...") + _fp_gpio = usrp::gpio_atr::gpio_atr_3000::make(_wb_ifaces.front(), + n310_regs::FP_GPIO, n310_regs::RB_FP_GPIO); +} + +void magnesium_radio_control_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; + RFNOC_LOG_TRACE("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(get_fe_name(chan_idx, TX_DIRECTION)); + subtree->create<std::string>(tx_fe_path / "connection").set("IQ"); + // RX Standard attributes + subtree->create<std::string>(rx_fe_path / "name").set(get_fe_name(chan_idx, RX_DIRECTION)); + 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_publisher([this](){ return get_tx_antennas(0); }) + .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_publisher([this](){ return get_rx_antennas(0); }) + .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_publisher([this, chan_idx](){ return get_tx_frequency_range(chan_idx); }) + .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_publisher([this, chan_idx](){ return get_rx_frequency_range(chan_idx); }) + .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(AD9371_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_publisher([this, chan_idx](){ return get_tx_bandwidth_range(chan_idx); }) + .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(AD9371_RX_MAX_BANDWIDTH) + .set_coercer([this, chan_idx](const double bw) { + return this->set_rx_bandwidth(bw, chan_idx); + }) + .set_publisher([this, chan_idx]() { return this->get_rx_bandwidth(chan_idx); }); + subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") + .set_publisher([this, chan_idx](){ return get_rx_bandwidth_range(chan_idx); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }); + + // TX gains + std::vector<std::string> tx_gain_names = get_tx_gain_names(chan_idx); + tx_gain_names.push_back("all"); + for (const auto gain_name : tx_gain_names) { + subtree->create<double>(tx_fe_path / "gains" / gain_name / "value") + .set_coercer([this, chan_idx, gain_name](const double gain) { + return this->set_tx_gain(gain, gain_name, chan_idx); + }) + .set_publisher( + [this, chan_idx, gain_name]() { return get_tx_gain(gain_name, chan_idx); }); + subtree->create<meta_range_t>(tx_fe_path / "gains" / gain_name / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this, gain_name, chan_idx]() { return get_tx_gain_range(gain_name, chan_idx); }); + } + subtree->create<std::vector<std::string>>(tx_fe_path / "gains/all/profile/options") + .set_publisher( + [this, chan_idx]() { return get_tx_gain_profile_names(chan_idx); }); + subtree->create<std::string>(tx_fe_path / "gains/all/profile/value") + .set_coercer([this, chan_idx](const std::string& profile) { + set_tx_gain_profile(profile, chan_idx); + return profile; + }) + .set_publisher([this, chan_idx]() { return get_tx_gain_profile(chan_idx); }); + + // RX gains + std::vector<std::string> rx_gain_names = get_rx_gain_names(chan_idx); + rx_gain_names.push_back("all"); + for (const auto gain_name : rx_gain_names) { + subtree->create<double>(rx_fe_path / "gains" / gain_name / "value") + .set_coercer([this, chan_idx, gain_name](const double gain) { + return this->set_rx_gain(gain, gain_name, chan_idx); + }) + .set_publisher( + [this, chan_idx, gain_name]() { return get_rx_gain(gain_name, chan_idx); }); + subtree->create<meta_range_t>(rx_fe_path / "gains" / gain_name / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this, gain_name, chan_idx]() { return get_rx_gain_range(gain_name, chan_idx); }); + } + subtree->create<std::vector<std::string>>(rx_fe_path / "gains/all/profile/options") + .set_publisher( + [this, chan_idx]() { return get_rx_gain_profile_names(chan_idx); }); + subtree->create<std::string>(rx_fe_path / "gains/all/profile/value") + .set_coercer([this, chan_idx](const std::string& profile) { + set_rx_gain_profile(profile, chan_idx); + return profile; + }) + .set_publisher([this, chan_idx]() { return get_rx_gain_profile(chan_idx); }); + + // LO Specific + // RX LO + subtree->create<meta_range_t>(rx_fe_path / "los" / MAGNESIUM_LO1 / "freq/range") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_freq_range(MAGNESIUM_LO1, chan_idx); + }); + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / MAGNESIUM_LO1 / "source/options") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_sources(MAGNESIUM_LO1, chan_idx); + }); + subtree->create<std::string>(rx_fe_path / "los" / MAGNESIUM_LO1 / "source/value") + .add_coerced_subscriber([this, chan_idx](std::string src) { + this->set_rx_lo_source(src, MAGNESIUM_LO1, chan_idx); + }) + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_source(MAGNESIUM_LO1, chan_idx); + }); + subtree->create<double>(rx_fe_path / "los" / MAGNESIUM_LO1 / "freq/value") + .set_publisher( + [this, chan_idx]() { return this->get_rx_lo_freq(MAGNESIUM_LO1, chan_idx); }) + .set_coercer([this, chan_idx](const double freq) { + return this->set_rx_lo_freq(freq, MAGNESIUM_LO1, chan_idx); + }); + subtree->create<meta_range_t>(rx_fe_path / "los" / MAGNESIUM_LO2 / "freq/range") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_freq_range(MAGNESIUM_LO2, chan_idx); + }); + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / MAGNESIUM_LO2 / "source/options") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_sources(MAGNESIUM_LO2, chan_idx); + }); + + subtree->create<std::string>(rx_fe_path / "los" / MAGNESIUM_LO2 / "source/value") + .add_coerced_subscriber([this, chan_idx](std::string src) { + this->set_rx_lo_source(src, MAGNESIUM_LO2, chan_idx); + }) + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_source(MAGNESIUM_LO2, chan_idx); + }); + subtree->create<double>(rx_fe_path / "los" / MAGNESIUM_LO2 / "freq/value") + .set_publisher( + [this, chan_idx]() { return this->get_rx_lo_freq(MAGNESIUM_LO2, chan_idx); }) + .set_coercer([this, chan_idx](double freq) { + return this->set_rx_lo_freq(freq, MAGNESIUM_LO2, chan_idx); + }); + // TX LO + subtree->create<meta_range_t>(tx_fe_path / "los" / MAGNESIUM_LO1 / "freq/range") + .set_publisher([this, chan_idx]() { + return this->get_rx_lo_freq_range(MAGNESIUM_LO1, chan_idx); + }); + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / MAGNESIUM_LO1 / "source/options") + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_sources(MAGNESIUM_LO1, chan_idx); + }); + subtree->create<std::string>(tx_fe_path / "los" / MAGNESIUM_LO1 / "source/value") + .add_coerced_subscriber([this, chan_idx](std::string src) { + this->set_tx_lo_source(src, MAGNESIUM_LO1, chan_idx); + }) + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_source(MAGNESIUM_LO1, chan_idx); + }); + subtree->create<double>(tx_fe_path / "los" / MAGNESIUM_LO1 / "freq/value ") + .set_publisher( + [this, chan_idx]() { return this->get_tx_lo_freq(MAGNESIUM_LO1, chan_idx); }) + .set_coercer([this, chan_idx](double freq) { + return this->set_tx_lo_freq(freq, MAGNESIUM_LO1, chan_idx); + }); + subtree->create<meta_range_t>(tx_fe_path / "los" / MAGNESIUM_LO2 / "freq/range") + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_freq_range(MAGNESIUM_LO2, chan_idx); + }); + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / MAGNESIUM_LO2 / "source/options") + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_sources(MAGNESIUM_LO2, chan_idx); + }); + subtree->create<std::string>(tx_fe_path / "los" / MAGNESIUM_LO2 / "source/value") + .add_coerced_subscriber([this, chan_idx](std::string src) { + this->set_tx_lo_source(src, MAGNESIUM_LO2, chan_idx); + }) + .set_publisher([this, chan_idx]() { + return this->get_tx_lo_source(MAGNESIUM_LO2, chan_idx); + }); + subtree->create<double>(tx_fe_path / "los" / MAGNESIUM_LO2 / "freq/value") + .set_publisher( + [this, chan_idx]() { return this->get_tx_lo_freq(MAGNESIUM_LO2, chan_idx); }) + .set_coercer([this, chan_idx](double freq) { + return this->set_tx_lo_freq(freq, MAGNESIUM_LO2, chan_idx); + }); + + // Sensors + auto rx_sensor_names = get_rx_sensor_names(chan_idx); + for (const auto& sensor_name : rx_sensor_names) { + RFNOC_LOG_TRACE("Adding RX sensor " << sensor_name); + get_tree()->create<sensor_value_t>(rx_fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, sensor_name, chan_idx]() { + return get_rx_sensor(sensor_name, chan_idx); + }); + } + auto tx_sensor_names = get_tx_sensor_names(chan_idx); + for (const auto& sensor_name : tx_sensor_names) { + RFNOC_LOG_TRACE("Adding TX sensor " << sensor_name); + get_tree()->create<sensor_value_t>(tx_fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, sensor_name, chan_idx]() { + return get_tx_sensor(sensor_name, chan_idx); + }); + } +} + +void magnesium_radio_control_impl::_init_prop_tree() +{ + for (size_t chan_idx = 0; chan_idx < MAGNESIUM_NUM_CHANS; chan_idx++) { + this->_init_frontend_subtree(get_tree()->subtree(DB_PATH), chan_idx); + } + + // DB EEPROM + get_tree()->create<eeprom_map_t>("eeprom") + .add_coerced_subscriber( + [this](const eeprom_map_t& db_eeprom) { set_db_eeprom(db_eeprom); }) + .set_publisher([this]() { return get_db_eeprom(); }); +} + +void magnesium_radio_control_impl::_init_mpm() +{ + auto block_args = get_block_args(); + RFNOC_LOG_TRACE("Instantiating AD9371 control object..."); + _ad9371 = magnesium_ad9371_iface::uptr( + new magnesium_ad9371_iface(_rpcc, (_radio_slot == "A") ? 0 : 1)); + + 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; + } + RFNOC_LOG_INFO("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))); + } + RFNOC_LOG_DEBUG("Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); + set_tick_rate(_master_clock_rate); + _n3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); +} + diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.cpp deleted file mode 100644 index 405d5955e..000000000 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_impl.cpp +++ /dev/null @@ -1,847 +0,0 @@ -// -// Copyright 2017 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "magnesium_radio_ctrl_impl.hpp" -#include "magnesium_constants.hpp" -#include "magnesium_gain_table.hpp" -#include <uhd/exception.hpp> -#include <uhd/rfnoc/node_ctrl_base.hpp> -#include <uhd/transport/chdr.hpp> -#include <uhd/types/direction.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/utils/math.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/format.hpp> -#include <boost/make_shared.hpp> -#include <cmath> -#include <cstdlib> -#include <sstream> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; -using namespace uhd::math::fp_compare; - -namespace { -/************************************************************************** - * ADF4351 Controls - *************************************************************************/ -/*! - * \param lo_iface Reference to the LO object - * \param freq Frequency (in Hz) of the tone to be generated from the LO - * \param ref_clock_freq Frequency (in Hz) of the reference clock at the - * PLL input of the LO - * \param int_n_mode Integer-N mode on or off - */ -double _lo_set_frequency(adf435x_iface::sptr lo_iface, - const double freq, - const double ref_clock_freq, - const bool int_n_mode) -{ - UHD_LOG_TRACE("MG/ADF4351", - "Attempting to tune low band LO to " << freq << " Hz with ref clock freq " - << ref_clock_freq); - lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); - lo_iface->set_reference_freq(ref_clock_freq); - lo_iface->set_prescaler(adf435x_iface::PRESCALER_4_5); - const double actual_freq = lo_iface->set_frequency(freq, int_n_mode); - lo_iface->set_output_power( - adf435x_iface::RF_OUTPUT_A, adf435x_iface::OUTPUT_POWER_2DBM); - lo_iface->set_output_power( - adf435x_iface::RF_OUTPUT_B, adf435x_iface::OUTPUT_POWER_2DBM); - lo_iface->set_charge_pump_current(adf435x_iface::CHARGE_PUMP_CURRENT_0_31MA); - return actual_freq; -} - -/*! Configure and enable LO - * - * Will tune it to requested frequency and enable outputs. - * - * \param lo_iface Reference to the LO object - * \param lo_freq Frequency (in Hz) of the tone to be generated from the LO - * \param ref_clock_freq Frequency (in Hz) of the reference clock at the - * PLL input of the LO - * \param int_n_mode Integer-N mode on or off - * \returns the actual frequency the LO is running at - */ -double _lo_enable(adf435x_iface::sptr lo_iface, - const double lo_freq, - const double ref_clock_freq, - const bool int_n_mode) -{ - const double actual_lo_freq = - _lo_set_frequency(lo_iface, lo_freq, ref_clock_freq, int_n_mode); - lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_A, true); - lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_B, true); - lo_iface->commit(); - return actual_lo_freq; -} - -/*! Disable LO - */ -void _lo_disable(adf435x_iface::sptr lo_iface) -{ - lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_A, false); - lo_iface->set_output_enable(adf435x_iface::RF_OUTPUT_B, false); - lo_iface->commit(); -} -} // namespace - - -/****************************************************************************** - * Structors - *****************************************************************************/ -UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(magnesium_radio_ctrl) -{ - UHD_LOG_TRACE(unique_id(), "Entering magnesium_radio_ctrl_impl ctor..."); - const char radio_slot_name[2] = {'A', 'B'}; - _radio_slot = radio_slot_name[get_block_id().get_block_count()]; - UHD_LOG_TRACE(unique_id(), "Radio slot: " << _radio_slot); - _rpc_prefix = (_radio_slot == "A") ? "db_0_" : "db_1_"; - - _init_defaults(); - _init_peripherals(); - _init_prop_tree(); -} - -magnesium_radio_ctrl_impl::~magnesium_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "magnesium_radio_ctrl_impl::dtor() "); -} - - -/****************************************************************************** - * API Calls - *****************************************************************************/ -double magnesium_radio_ctrl_impl::set_rate(double requested_rate) -{ - meta_range_t rates; - for (const double rate : MAGNESIUM_RADIO_RATES) { - rates.push_back(range_t(rate)); - } - - const double rate = rates.clip(requested_rate); - if (!math::frequencies_are_equal(requested_rate, rate)) { - UHD_LOG_WARNING(unique_id(), - "Coercing requested sample rate from " << (requested_rate / 1e6) << " to " - << (rate / 1e6)); - } - - const double current_rate = get_rate(); - if (math::frequencies_are_equal(current_rate, rate)) { - UHD_LOG_DEBUG( - unique_id(), "Rate is already at " << rate << ". Skipping set_rate()"); - return current_rate; - } - - std::lock_guard<std::mutex> l(_set_lock); - // Now commit to device. First, disable LOs. - _lo_disable(_tx_lo); - _lo_disable(_rx_lo); - const double new_rate = _ad9371->set_master_clock_rate(rate); - // Frequency settings apply to both channels, no loop needed. Will also - // re-enable the lowband LOs if they were used. - set_rx_frequency(get_rx_frequency(0), 0); - set_tx_frequency(get_tx_frequency(0), 0); - // Gain and bandwidth need to be looped: - for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { - set_rx_gain(get_rx_gain(radio_idx), radio_idx); - set_tx_gain(get_rx_gain(radio_idx), radio_idx); - set_rx_bandwidth(get_rx_bandwidth(radio_idx), radio_idx); - set_tx_bandwidth(get_tx_bandwidth(radio_idx), radio_idx); - } - radio_ctrl_impl::set_rate(new_rate); - return new_rate; -} - -void magnesium_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 magnesium_radio_ctrl_impl::set_rx_antenna(const std::string& ant, const size_t chan) -{ - UHD_ASSERT_THROW(chan <= MAGNESIUM_NUM_CHANS); - if (std::find(MAGNESIUM_RX_ANTENNAS.begin(), MAGNESIUM_RX_ANTENNAS.end(), ant) - == MAGNESIUM_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); - magnesium_cpld_ctrl::chan_sel_t chan_sel = chan == 0 ? magnesium_cpld_ctrl::CHAN1 - : magnesium_cpld_ctrl::CHAN2; - _update_atr_switches(chan_sel, RX_DIRECTION, ant); - - radio_ctrl_impl::set_rx_antenna(ant, chan); -} - -double magnesium_radio_ctrl_impl::set_tx_frequency( - const double req_freq, const size_t chan) -{ - const double freq = MAGNESIUM_FREQ_RANGE.clip(req_freq); - UHD_LOG_TRACE(unique_id(), "set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); - _desired_rf_freq[TX_DIRECTION] = freq; - std::lock_guard<std::mutex> l(_set_lock); - // We need to set the switches on both channels, because they share an LO. - // This way, if we tune channel 0 it will not put channel 1 into a bad - // state. - _update_tx_freq_switches(freq, _tx_bypass_amp, magnesium_cpld_ctrl::BOTH); - const std::string ad9371_source = this->get_tx_lo_source(MAGNESIUM_LO1, chan); - const std::string adf4351_source = this->get_tx_lo_source(MAGNESIUM_LO2, chan); - UHD_ASSERT_THROW(adf4351_source == "internal"); - double coerced_if_freq = freq; - - if (_map_freq_to_tx_band(_tx_band_map, freq) == tx_band::LOWBAND) { - _is_low_band[TX_DIRECTION] = true; - const double desired_low_freq = MAGNESIUM_TX_IF_FREQ - freq; - coerced_if_freq = - this->_set_tx_lo_freq(adf4351_source, MAGNESIUM_LO2, desired_low_freq, chan) - + freq; - UHD_LOG_TRACE(unique_id(), "coerced_if_freq = " << coerced_if_freq); - } else { - _is_low_band[TX_DIRECTION] = false; - _lo_disable(_tx_lo); - } - // external LO required to tune at 2xdesired_frequency. - const double desired_if_freq = (ad9371_source == "internal") ? coerced_if_freq - : 2 * coerced_if_freq; - - this->_set_tx_lo_freq(ad9371_source, MAGNESIUM_LO1, desired_if_freq, chan); - this->_update_freq(chan, TX_DIRECTION); - this->_update_gain(chan, TX_DIRECTION); - return radio_ctrl_impl::get_tx_frequency(chan); -} - -void magnesium_radio_ctrl_impl::_update_gain( - const size_t chan, const uhd::direction_t dir) -{ - const std::string fe = (dir == TX_DIRECTION) ? "tx_frontends" : "rx_frontends"; - const double freq = (dir == TX_DIRECTION) ? this->get_tx_frequency(chan) - : this->get_rx_frequency(chan); - this->_set_all_gain(this->_get_all_gain(chan, dir), freq, chan, dir); -} - -void magnesium_radio_ctrl_impl::_update_freq( - const size_t chan, const uhd::direction_t dir) -{ - const std::string ad9371_source = dir == TX_DIRECTION - ? this->get_tx_lo_source(MAGNESIUM_LO1, chan) - : this->get_rx_lo_source(MAGNESIUM_LO1, chan); - - const double ad9371_freq = ad9371_source == "external" ? _ad9371_freq[dir] / 2 - : _ad9371_freq[dir]; - const double rf_freq = _is_low_band[dir] ? ad9371_freq - _adf4351_freq[dir] - : ad9371_freq; - - UHD_LOG_TRACE(unique_id(), "RF freq = " << rf_freq); - UHD_ASSERT_THROW(fp_compare_epsilon<double>(rf_freq) >= 0); - UHD_ASSERT_THROW(fp_compare_epsilon<double>(std::abs(rf_freq - _desired_rf_freq[dir])) - <= _master_clock_rate / 2); - if (dir == RX_DIRECTION) { - radio_ctrl_impl::set_rx_frequency(rf_freq, chan); - } else if (dir == TX_DIRECTION) { - radio_ctrl_impl::set_tx_frequency(rf_freq, chan); - } else { - UHD_THROW_INVALID_CODE_PATH(); - } -} - -double magnesium_radio_ctrl_impl::set_rx_frequency( - const double req_freq, const size_t chan) -{ - const double freq = MAGNESIUM_FREQ_RANGE.clip(req_freq); - UHD_LOG_TRACE(unique_id(), "set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); - _desired_rf_freq[RX_DIRECTION] = freq; - std::lock_guard<std::mutex> l(_set_lock); - // We need to set the switches on both channels, because they share an LO. - // This way, if we tune channel 0 it will not put channel 1 into a bad - // state. - _update_rx_freq_switches(freq, _rx_bypass_lnas, magnesium_cpld_ctrl::BOTH); - const std::string ad9371_source = this->get_rx_lo_source(MAGNESIUM_LO1, chan); - const std::string adf4351_source = this->get_rx_lo_source(MAGNESIUM_LO2, chan); - UHD_ASSERT_THROW(adf4351_source == "internal"); - double coerced_if_freq = freq; - - if (_map_freq_to_rx_band(_rx_band_map, freq) == rx_band::LOWBAND) { - _is_low_band[RX_DIRECTION] = true; - const double desired_low_freq = MAGNESIUM_RX_IF_FREQ - freq; - coerced_if_freq = - this->_set_rx_lo_freq(adf4351_source, MAGNESIUM_LO2, desired_low_freq, chan) - + freq; - UHD_LOG_TRACE(unique_id(), "coerced_if_freq = " << coerced_if_freq); - } else { - _is_low_band[RX_DIRECTION] = false; - _lo_disable(_rx_lo); - } - // external LO required to tune at 2xdesired_frequency. - const double desired_if_freq = ad9371_source == "internal" ? coerced_if_freq - : 2 * coerced_if_freq; - - this->_set_rx_lo_freq(ad9371_source, MAGNESIUM_LO1, desired_if_freq, chan); - - this->_update_freq(chan, RX_DIRECTION); - this->_update_gain(chan, RX_DIRECTION); - - return radio_ctrl_impl::get_rx_frequency(chan); -} - -double magnesium_radio_ctrl_impl::get_tx_frequency(const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "get_tx_frequency(chan=" << chan << ")"); - return radio_ctrl_impl::get_tx_frequency(chan); -} - -double magnesium_radio_ctrl_impl::get_rx_frequency(const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "get_rx_frequency(chan=" << chan << ")"); - return radio_ctrl_impl::get_rx_frequency(chan); -} -double magnesium_radio_ctrl_impl::set_rx_bandwidth( - const double bandwidth, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - _ad9371->set_bandwidth(bandwidth, chan, RX_DIRECTION); - // FIXME: setting analog bandwidth on AD9371 take no effect. - // Remove this warning when ADI can confirm that it works. - UHD_LOG_WARNING(unique_id(), - "set_rx_bandwidth take no effect on AD9371. " - "Default analog bandwidth is 100MHz"); - return AD9371_RX_MAX_BANDWIDTH; -} - -double magnesium_radio_ctrl_impl::set_tx_bandwidth( - const double bandwidth, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - _ad9371->set_bandwidth(bandwidth, chan, TX_DIRECTION); - // FIXME: setting analog bandwidth on AD9371 take no effect. - // Remove this warning when ADI can confirm that it works. - UHD_LOG_WARNING(unique_id(), - "set_tx_bandwidth take no effect on AD9371. " - "Default analog bandwidth is 100MHz"); - return AD9371_TX_MAX_BANDWIDTH; -} - -double magnesium_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 << ")"); - const double coerced_gain = - _set_all_gain(gain, this->get_tx_frequency(chan), chan, TX_DIRECTION); - radio_ctrl_impl::set_tx_gain(coerced_gain, chan); - return coerced_gain; -} - -double magnesium_radio_ctrl_impl::_set_tx_gain( - const std::string& name, const double gain, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), - "_set_tx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); - UHD_LOG_TRACE(unique_id(), - "_set_tx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); - double clip_gain = 0; - if (name == MAGNESIUM_GAIN1) { - clip_gain = uhd::clip(gain, AD9371_MIN_TX_GAIN, AD9371_MAX_TX_GAIN); - _ad9371_att[TX_DIRECTION] = clip_gain; - } else if (name == MAGNESIUM_GAIN2) { - clip_gain = uhd::clip(gain, DSA_MIN_GAIN, DSA_MAX_GAIN); - _dsa_att[TX_DIRECTION] = clip_gain; - } else if (name == MAGNESIUM_AMP) { - clip_gain = gain > 0.0 ? AMP_MAX_GAIN : AMP_MIN_GAIN; - _amp_bypass[TX_DIRECTION] = clip_gain == 0.0; - } else { - throw uhd::value_error("Could not find gain element " + name); - } - UHD_LOG_TRACE(unique_id(), "_set_tx_gain calling update gain"); - this->_set_all_gain(this->_get_all_gain(chan, TX_DIRECTION), - this->get_tx_frequency(chan), - chan, - TX_DIRECTION); - return clip_gain; -} - -double magnesium_radio_ctrl_impl::_get_tx_gain( - const std::string& name, const size_t /*chan*/ -) -{ - std::lock_guard<std::mutex> l(_set_lock); - if (name == MAGNESIUM_GAIN1) { - return _ad9371_att[TX_DIRECTION]; - } else if (name == MAGNESIUM_GAIN2) { - return _dsa_att[TX_DIRECTION]; - } else if (name == MAGNESIUM_AMP) { - return _amp_bypass[TX_DIRECTION] ? AMP_MIN_GAIN : AMP_MAX_GAIN; - } else { - throw uhd::value_error("Could not find gain element " + name); - } -} - -double magnesium_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 << ")"); - const double coerced_gain = - _set_all_gain(gain, this->get_rx_frequency(chan), chan, RX_DIRECTION); - radio_ctrl_impl::set_rx_gain(coerced_gain, chan); - return coerced_gain; -} - -double magnesium_radio_ctrl_impl::_set_rx_gain( - const std::string& name, const double gain, const size_t chan) -{ - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), - "_set_rx_gain(name=" << name << ", gain=" << gain << ", chan=" << chan << ")"); - double clip_gain = 0; - if (name == MAGNESIUM_GAIN1) { - clip_gain = uhd::clip(gain, AD9371_MIN_RX_GAIN, AD9371_MAX_RX_GAIN); - _ad9371_att[RX_DIRECTION] = clip_gain; - } else if (name == MAGNESIUM_GAIN2) { - clip_gain = uhd::clip(gain, DSA_MIN_GAIN, DSA_MAX_GAIN); - _dsa_att[RX_DIRECTION] = clip_gain; - } else if (name == MAGNESIUM_AMP) { - clip_gain = gain > 0.0 ? AMP_MAX_GAIN : AMP_MIN_GAIN; - _amp_bypass[RX_DIRECTION] = clip_gain == 0.0; - } else { - throw uhd::value_error("Could not find gain element " + name); - } - UHD_LOG_TRACE(unique_id(), "_set_rx_gain calling update gain"); - this->_set_all_gain(this->_get_all_gain(chan, RX_DIRECTION), - this->get_rx_frequency(chan), - chan, - RX_DIRECTION); - return clip_gain; // not really any coerced here (only clip) for individual gain -} - -double magnesium_radio_ctrl_impl::_get_rx_gain( - const std::string& name, const size_t /*chan*/ -) -{ - std::lock_guard<std::mutex> l(_set_lock); - - if (name == MAGNESIUM_GAIN1) { - return _ad9371_att[RX_DIRECTION]; - } else if (name == MAGNESIUM_GAIN2) { - return _dsa_att[RX_DIRECTION]; - } else if (name == MAGNESIUM_AMP) { - return _amp_bypass[RX_DIRECTION] ? AMP_MIN_GAIN : AMP_MAX_GAIN; - } else { - throw uhd::value_error("Could not find gain element " + name); - } -} - -std::vector<std::string> magnesium_radio_ctrl_impl::get_rx_lo_names(const size_t /*chan*/ -) -{ - return std::vector<std::string>{MAGNESIUM_LO1, MAGNESIUM_LO2}; -} - -std::vector<std::string> magnesium_radio_ctrl_impl::get_rx_lo_sources( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO2) { - return std::vector<std::string>{"internal"}; - } else if (name == MAGNESIUM_LO1) { - return std::vector<std::string>{"internal", "external"}; - } else { - throw uhd::value_error("Could not find LO stage " + name); - } -} - -freq_range_t magnesium_radio_ctrl_impl::get_rx_lo_freq_range( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO1) { - return freq_range_t{ADF4351_MIN_FREQ, ADF4351_MAX_FREQ}; - } else if (name == MAGNESIUM_LO2) { - return freq_range_t{AD9371_MIN_FREQ, AD9371_MAX_FREQ}; - } else { - throw uhd::value_error("Could not find LO stage " + name); - } -} - -void magnesium_radio_ctrl_impl::set_rx_lo_source( - const std::string& src, const std::string& name, const size_t /*chan*/ -) -{ - // TODO: checking what options are there - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), "Setting RX LO " << name << " to " << src); - - if (name == MAGNESIUM_LO1) { - _ad9371->set_lo_source(src, RX_DIRECTION); - } else { - UHD_LOG_ERROR(unique_id(), - "RX LO " << name << " does not support setting source to " << src); - } -} - -const std::string magnesium_radio_ctrl_impl::get_rx_lo_source( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO1) { - // TODO: should we use this from cache? - return _ad9371->get_lo_source(RX_DIRECTION); - } - return "internal"; -} - -double magnesium_radio_ctrl_impl::_set_rx_lo_freq(const std::string source, - const std::string name, - const double freq, - const size_t chan) -{ - double coerced_lo_freq = freq; - if (source != "internal") { - UHD_LOG_WARNING( - unique_id(), "LO source is not internal. This set frequency will be ignored"); - if (name == MAGNESIUM_LO1) { - // handle ad9371 external LO case - coerced_lo_freq = freq; - _ad9371_freq[RX_DIRECTION] = coerced_lo_freq; - } - } else { - if (name == MAGNESIUM_LO1) { - coerced_lo_freq = _ad9371->set_frequency(freq, chan, RX_DIRECTION); - _ad9371_freq[RX_DIRECTION] = coerced_lo_freq; - } else if (name == MAGNESIUM_LO2) { - // TODO: no hardcode the init_n_mode - coerced_lo_freq = _lo_enable(_rx_lo, freq, _master_clock_rate, false); - _adf4351_freq[RX_DIRECTION] = coerced_lo_freq; - } else { - UHD_LOG_WARNING(unique_id(), - "There's no LO with this name of " - << name << " in the system. This set rx lo freq will be ignored"); - }; - } - return coerced_lo_freq; -} - -double magnesium_radio_ctrl_impl::set_rx_lo_freq( - double freq, const std::string& name, const size_t chan) -{ - UHD_LOG_TRACE( - unique_id(), "Setting rx lo frequency for " << name << " with freq = " << freq); - std::lock_guard<std::mutex> l(_set_lock); - std::string source = this->get_rx_lo_source(name, chan); - const double coerced_lo_freq = this->_set_rx_lo_freq(source, name, freq, chan); - this->_update_freq(chan, RX_DIRECTION); - this->_update_gain(chan, RX_DIRECTION); - return coerced_lo_freq; -} - -double magnesium_radio_ctrl_impl::get_rx_lo_freq( - const std::string& name, const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "Getting rx lo frequency for " << name); - std::string source = this->get_rx_lo_source(name, chan); - if (name == MAGNESIUM_LO1) { - return _ad9371_freq[RX_DIRECTION]; - } else if (name == "adf4531") { - return _adf4351_freq[RX_DIRECTION]; - } else { - UHD_LOG_ERROR(unique_id(), - "There's no LO with this name of " - << name << " in the system. This set rx lo freq will be ignored"); - } - UHD_THROW_INVALID_CODE_PATH(); -} - -// TX LO -std::vector<std::string> magnesium_radio_ctrl_impl::get_tx_lo_names(const size_t /*chan*/ -) -{ - return std::vector<std::string>{MAGNESIUM_LO1, MAGNESIUM_LO2}; -} - -std::vector<std::string> magnesium_radio_ctrl_impl::get_tx_lo_sources( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO2) { - return std::vector<std::string>{"internal"}; - } else if (name == MAGNESIUM_LO1) { - return std::vector<std::string>{"internal", "external"}; - } else { - throw uhd::value_error("Could not find LO stage " + name); - } -} - -freq_range_t magnesium_radio_ctrl_impl::get_tx_lo_freq_range( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO2) { - return freq_range_t{ADF4351_MIN_FREQ, ADF4351_MAX_FREQ}; - } else if (name == MAGNESIUM_LO1) { - return freq_range_t{AD9371_MIN_FREQ, AD9371_MAX_FREQ}; - } else { - throw uhd::value_error("Could not find LO stage " + name); - } -} - -void magnesium_radio_ctrl_impl::set_tx_lo_source( - const std::string& src, const std::string& name, const size_t /*chan*/ -) -{ - // TODO: checking what options are there - std::lock_guard<std::mutex> l(_set_lock); - UHD_LOG_TRACE(unique_id(), "Setting TX LO " << name << " to " << src); - if (name == MAGNESIUM_LO1) { - _ad9371->set_lo_source(src, TX_DIRECTION); - } else { - UHD_LOG_ERROR(unique_id(), - "TX LO " << name << " does not support setting source to " << src); - } -} - -const std::string magnesium_radio_ctrl_impl::get_tx_lo_source( - const std::string& name, const size_t /*chan*/ -) -{ - if (name == MAGNESIUM_LO1) { - // TODO: should we use this from cache? - return _ad9371->get_lo_source(TX_DIRECTION); - } - return "internal"; -} - -double magnesium_radio_ctrl_impl::_set_tx_lo_freq(const std::string source, - const std::string name, - const double freq, - const size_t chan) -{ - double coerced_lo_freq = freq; - if (source != "internal") { - UHD_LOG_WARNING( - unique_id(), "LO source is not internal. This set frequency will be ignored"); - if (name == MAGNESIUM_LO1) { - // handle ad9371 external LO case - coerced_lo_freq = freq; - _ad9371_freq[TX_DIRECTION] = coerced_lo_freq; - } - } else { - if (name == MAGNESIUM_LO1) { - coerced_lo_freq = _ad9371->set_frequency(freq, chan, TX_DIRECTION); - _ad9371_freq[TX_DIRECTION] = coerced_lo_freq; - } else if (name == MAGNESIUM_LO2) { - // TODO: no hardcode the int_n_mode - const bool int_n_mode = false; - coerced_lo_freq = _lo_enable(_tx_lo, freq, _master_clock_rate, int_n_mode); - _adf4351_freq[TX_DIRECTION] = coerced_lo_freq; - } else { - UHD_LOG_WARNING(unique_id(), - "There's no LO with this name of " - << name << " in the system. This set tx lo freq will be ignored"); - }; - } - return coerced_lo_freq; -} - -double magnesium_radio_ctrl_impl::set_tx_lo_freq( - double freq, const std::string& name, const size_t chan) -{ - UHD_LOG_TRACE( - unique_id(), "Setting tx lo frequency for " << name << " with freq = " << freq); - std::string source = this->get_tx_lo_source(name, chan); - const double return_freq = this->_set_tx_lo_freq(source, name, freq, chan); - this->_update_freq(chan, TX_DIRECTION); - this->_update_gain(chan, TX_DIRECTION); - return return_freq; -} - -double magnesium_radio_ctrl_impl::get_tx_lo_freq( - const std::string& name, const size_t chan) -{ - UHD_LOG_TRACE(unique_id(), "Getting tx lo frequency for " << name); - std::string source = this->get_tx_lo_source(name, chan); - if (name == MAGNESIUM_LO1) { - return _ad9371_freq[TX_DIRECTION]; - } else if (name == MAGNESIUM_LO2) { - return _adf4351_freq[TX_DIRECTION]; - } else { - UHD_LOG_ERROR( - unique_id(), "There's no LO with this name of " << name << " in the system."); - }; - - UHD_THROW_INVALID_CODE_PATH(); -} - - -size_t magnesium_radio_ctrl_impl::get_chan_from_dboard_fe( - const std::string& fe, const direction_t /* dir */ -) -{ - return boost::lexical_cast<size_t>(fe); -} - -std::string magnesium_radio_ctrl_impl::get_dboard_fe_from_chan( - const size_t chan, const direction_t /* dir */ -) -{ - return std::to_string(chan); -} - - -void magnesium_radio_ctrl_impl::_remap_band_limits( - const std::string band_map, const uhd::direction_t dir) -{ - const size_t dflt_band_size = (dir == RX_DIRECTION) ? _rx_band_map.size() - : _tx_band_map.size(); - - std::vector<std::string> band_map_split; - double band_lim; - - UHD_LOG_DEBUG(unique_id(), "Using user specified frequency band limits"); - boost::split(band_map_split, band_map, boost::is_any_of(";")); - if (band_map_split.size() != dflt_band_size) { - throw uhd::runtime_error(( - boost::format( - "size %s of given frequency band map doesn't match the required size: %s") - % band_map_split.size() % dflt_band_size) - .str()); - } - UHD_LOG_DEBUG(unique_id(), "newly used band limits: "); - for (size_t i = 0; i < band_map_split.size(); i++) { - try { - band_lim = std::stod(band_map_split.at(i)); - } catch (...) { - throw uhd::value_error( - (boost::format("error while converting given frequency string %s " - "to a double value") - % band_map_split.at(i)) - .str()); - } - UHD_LOG_DEBUG(unique_id(), "band " << i << " limit: " << band_lim << "Hz"); - if (dir == RX_DIRECTION) - _rx_band_map.at(i) = band_lim; - else - _tx_band_map.at(i) = band_lim; - } -} - - -void magnesium_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 AD9371 control object..."); - _ad9371 = magnesium_ad9371_iface::uptr( - new magnesium_ad9371_iface(_rpcc, (_radio_slot == "A") ? 0 : 1)); - - 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); - } - - if (block_args.has_key("tx_gain_profile")) { - UHD_LOG_INFO(unique_id(), - "Using user specified TX gain profile: " << block_args.get( - "tx_gain_profile")); - _gain_profile[TX_DIRECTION] = block_args.get("tx_gain_profile"); - } - - if (block_args.has_key("rx_gain_profile")) { - UHD_LOG_INFO(unique_id(), - "Using user specified RX gain profile: " << block_args.get( - "rx_gain_profile")); - _gain_profile[RX_DIRECTION] = block_args.get("rx_gain_profile"); - } - - if (block_args.has_key("rx_band_map")) { - UHD_LOG_INFO(unique_id(), "Using user specified RX band limits"); - _remap_band_limits(block_args.get("rx_band_map"), RX_DIRECTION); - } - - if (block_args.has_key("tx_band_map")) { - UHD_LOG_INFO(unique_id(), "Using user specified TX band limits"); - _remap_band_limits(block_args.get("tx_band_map"), TX_DIRECTION); - } - - // 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."); - radio_ctrl_impl::set_rate(_master_clock_rate); - - // EEPROM paths subject to change FIXME - 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 < MAGNESIUM_NUM_CHANS; chan_idx++) { - _init_mpm_sensors(dir, chan_idx); - } - } -} - -bool magnesium_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"; - const size_t chan = 0; // They're the same after all - const double freq = (dir == RX_DIRECTION) ? get_rx_frequency(chan) - : get_tx_frequency(chan); - - bool lo_lock = - _rpcc->request_with_token<bool>(_rpc_prefix + "get_ad9371_lo_lock", trx); - UHD_LOG_TRACE(unique_id(), - "AD9371 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); - if (lo_lock and _map_freq_to_rx_band(_rx_band_map, freq) == rx_band::LOWBAND) { - lo_lock = - lo_lock - && _rpcc->request_with_token<bool>(_rpc_prefix + "get_lowband_lo_lock", trx); - UHD_LOG_TRACE(unique_id(), - "ADF4351 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); - } - - return lo_lock; -} - -UHD_RFNOC_BLOCK_REGISTER(magnesium_radio_ctrl, "MagnesiumRadio"); diff --git a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_init.cpp b/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_init.cpp deleted file mode 100644 index 89db61428..000000000 --- a/host/lib/usrp/dboard/magnesium/magnesium_radio_ctrl_init.cpp +++ /dev/null @@ -1,713 +0,0 @@ -// -// Copyright 2017 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "magnesium_constants.hpp" -#include "magnesium_radio_ctrl_impl.hpp" -#include <uhd/transport/chdr.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/types/sensors.hpp> -#include <uhd/utils/log.hpp> -#include <uhdlib/usrp/cores/spi_core_3000.hpp> -#include <boost/algorithm/string.hpp> -#include <boost/algorithm/string/case_conv.hpp> -#include <boost/algorithm/string/split.hpp> -#include <string> -#include <vector> -using namespace uhd; -using namespace uhd::rfnoc; - -namespace { -enum slave_select_t { SEN_CPLD = 1, SEN_TX_LO = 2, SEN_RX_LO = 4, SEN_PHASE_DAC = 8 }; - -constexpr double MAGNESIUM_DEFAULT_FREQ = 2.5e9; // Hz -constexpr double MAGNESIUM_DEFAULT_BANDWIDTH = 100e6; // Hz -constexpr char MAGNESIUM_DEFAULT_RX_ANTENNA[] = "RX2"; -constexpr char MAGNESIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; - -//! Magnesium gain profile options -const std::vector<std::string> MAGNESIUM_GP_OPTIONS = {"manual", - "default", - "default_rf_filter_bypass_always_on", - "default_rf_filter_bypass_always_off"}; -} // namespace - -//! 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 magnesium_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(MAGNESIUM_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_rx_gain(0, chan); - radio_ctrl_impl::set_rx_antenna(MAGNESIUM_DEFAULT_RX_ANTENNA, chan); - radio_ctrl_impl::set_rx_bandwidth(MAGNESIUM_DEFAULT_BANDWIDTH, chan); - } - - for (size_t chan = 0; chan < num_tx_chans; chan++) { - radio_ctrl_impl::set_tx_frequency(MAGNESIUM_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_tx_gain(0, chan); - radio_ctrl_impl::set_tx_antenna(MAGNESIUM_DEFAULT_TX_ANTENNA, chan); - radio_ctrl_impl::set_tx_bandwidth(MAGNESIUM_DEFAULT_BANDWIDTH, 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 magnesium_radio_ctrl_impl::_init_peripherals() -{ - UHD_LOG_TRACE(unique_id(), "Initializing peripherals..."); - UHD_LOG_TRACE(unique_id(), "Initializing SPI core..."); - _spi = spi_core_3000::make( - _get_ctrl(0), regs::sr_addr(regs::SPI), regs::rb_addr(regs::RB_SPI)); - UHD_LOG_TRACE(unique_id(), "Initializing CPLD..."); - UHD_LOG_TRACE(unique_id(), "Creating new CPLD object..."); - spi_config_t spi_config; - spi_config.use_custom_divider = true; - spi_config.divider = 125; - spi_config.mosi_edge = spi_config_t::EDGE_RISE; - spi_config.miso_edge = spi_config_t::EDGE_FALL; - UHD_LOG_TRACE(unique_id(), "Making CPLD object..."); - _cpld = std::make_shared<magnesium_cpld_ctrl>( - [this, spi_config](const uint32_t transaction) { // Write functor - this->_spi->write_spi(SEN_CPLD, spi_config, transaction, 24); - }, - [this, spi_config](const uint32_t transaction) { // Read functor - return this->_spi->read_spi(SEN_CPLD, spi_config, transaction, 24); - }); - _update_atr_switches( - magnesium_cpld_ctrl::BOTH, DX_DIRECTION, radio_ctrl_impl::get_rx_antenna(0)); - UHD_LOG_TRACE(unique_id(), "Initializing TX LO..."); - _tx_lo = adf435x_iface::make_adf4351([this]( - const std::vector<uint32_t> transactions) { - for (const uint32_t transaction : transactions) { - this->_spi->write_spi(SEN_TX_LO, spi_config_t::EDGE_RISE, transaction, 32); - } - }); - UHD_LOG_TRACE(unique_id(), "Initializing RX LO..."); - _rx_lo = adf435x_iface::make_adf4351([this]( - const std::vector<uint32_t> transactions) { - for (const uint32_t transaction : transactions) { - this->_spi->write_spi(SEN_RX_LO, spi_config_t::EDGE_RISE, transaction, 32); - } - }); - - _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); - _gpio.emplace_back(usrp::gpio_atr::gpio_atr_3000::make(_get_ctrl(radio_idx), - regs::sr_addr(regs::GPIO), - regs::rb_addr(regs::RB_DB_GPIO))); - // DSA and AD9371 gain bits do *not* toggle on ATR modes. If we ever - // connect anything else to this core, we might need to set_atr_mode() - // to MODE_ATR on those bits. For now, all bits simply do what they're - // told, and don't toggle on RX/TX state changes. - _gpio.back()->set_atr_mode(usrp::gpio_atr::MODE_GPIO, // Disable ATR mode - usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); - _gpio.back()->set_gpio_ddr(usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs - 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_addr(regs::RB_FP_GPIO)); -} - -void magnesium_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("Magnesium"))); - 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("Magnesium"))); - 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({MAGNESIUM_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(MAGNESIUM_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(MAGNESIUM_MIN_FREQ, MAGNESIUM_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(MAGNESIUM_MIN_FREQ, MAGNESIUM_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(AD9371_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(AD9371_TX_MIN_BANDWIDTH, AD9371_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(AD9371_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(AD9371_RX_MIN_BANDWIDTH, AD9371_RX_MAX_BANDWIDTH)) - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update bandwidth range!"); - }); - // TX gains - subtree->create<double>(tx_fe_path / "gains" / "all" / "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" / "all" / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[TX_DIRECTION] == "manual") { - return meta_range_t(0.0, 0.0, 0.0); - } else { - return meta_range_t(ALL_TX_MIN_GAIN, ALL_TX_MAX_GAIN, ALL_TX_GAIN_STEP); - } - }); - - subtree->create<std::vector<std::string>>(tx_fe_path / "gains/all/profile/options") - .set(MAGNESIUM_GP_OPTIONS); - - subtree->create<std::string>(tx_fe_path / "gains/all/profile/value") - .set_coercer([this](const std::string& profile) { - // check if given profile is valid, otherwise use default profile - std::string return_profile = profile; - if (std::find( - MAGNESIUM_GP_OPTIONS.begin(), MAGNESIUM_GP_OPTIONS.end(), profile) - == MAGNESIUM_GP_OPTIONS.end()) { - return_profile = "default"; - } - _gain_profile[TX_DIRECTION] = return_profile; - return return_profile; - }) - .set_publisher([this]() { return _gain_profile[TX_DIRECTION]; }); - - // RX gains - subtree->create<double>(rx_fe_path / "gains" / "all" / "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" / "all" / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[RX_DIRECTION] == "manual") { - return meta_range_t(0.0, 0.0, 0.0); - } else { - return meta_range_t(ALL_RX_MIN_GAIN, ALL_RX_MAX_GAIN, ALL_RX_GAIN_STEP); - } - }); - - subtree->create<std::vector<std::string>>(rx_fe_path / "gains/all/profile/options") - .set(MAGNESIUM_GP_OPTIONS); - - subtree->create<std::string>(rx_fe_path / "gains/all/profile/value") - .set_coercer([this](const std::string& profile) { - // check if given profile is valid, otherwise use default profile - std::string return_profile = profile; - if (std::find( - MAGNESIUM_GP_OPTIONS.begin(), MAGNESIUM_GP_OPTIONS.end(), profile) - == MAGNESIUM_GP_OPTIONS.end()) { - return_profile = "default"; - } - _gain_profile[RX_DIRECTION] = return_profile; - return return_profile; - }) - .set_publisher([this]() { return _gain_profile[RX_DIRECTION]; }); - - // TX mykonos attenuation - subtree->create<double>(tx_fe_path / "gains" / MAGNESIUM_GAIN1 / "value") - .set_coercer([this, chan_idx](const double gain) { - return _set_tx_gain(MAGNESIUM_GAIN1, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_tx_gain(MAGNESIUM_GAIN1, chan_idx); }); - - subtree->create<meta_range_t>(tx_fe_path / "gains" / MAGNESIUM_GAIN1 / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[TX_DIRECTION] == "manual") { - return meta_range_t( - AD9371_MIN_TX_GAIN, AD9371_MAX_TX_GAIN, AD9371_TX_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - // TX DSA - subtree->create<double>(tx_fe_path / "gains" / MAGNESIUM_GAIN2 / "value") - .set_coercer([this, chan_idx](const double gain) { - return this->_set_tx_gain(MAGNESIUM_GAIN2, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_tx_gain(MAGNESIUM_GAIN2, chan_idx); }); - - subtree->create<meta_range_t>(tx_fe_path / "gains" / MAGNESIUM_GAIN2 / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[TX_DIRECTION] == "manual") { - return meta_range_t(DSA_MIN_GAIN, DSA_MAX_GAIN, DSA_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - // TX amp - subtree->create<double>(tx_fe_path / "gains" / MAGNESIUM_AMP / "value") - .set_coercer([this, chan_idx](const double gain) { - return this->_set_tx_gain(MAGNESIUM_AMP, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_tx_gain(MAGNESIUM_AMP, chan_idx); }); - - subtree->create<meta_range_t>(tx_fe_path / "gains" / MAGNESIUM_AMP / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[TX_DIRECTION] == "manual") { - return meta_range_t(AMP_MIN_GAIN, AMP_MAX_GAIN, AMP_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - - // RX mykonos attenuation - subtree->create<double>(rx_fe_path / "gains" / MAGNESIUM_GAIN1 / "value") - .set_coercer([this, chan_idx](const double gain) { - UHD_VAR(gain); - return this->_set_rx_gain(MAGNESIUM_GAIN1, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_rx_gain(MAGNESIUM_GAIN1, chan_idx); }); - - subtree->create<meta_range_t>(rx_fe_path / "gains" / MAGNESIUM_GAIN1 / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[RX_DIRECTION] == "manual") { - return meta_range_t( - AD9371_MIN_RX_GAIN, AD9371_MAX_RX_GAIN, AD9371_RX_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - // RX DSA - subtree->create<double>(rx_fe_path / "gains" / MAGNESIUM_GAIN2 / "value") - .set_coercer([this, chan_idx](const double gain) { - UHD_VAR(gain); - return this->_set_rx_gain(MAGNESIUM_GAIN2, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_rx_gain(MAGNESIUM_GAIN2, chan_idx); }); - - subtree->create<meta_range_t>(rx_fe_path / "gains" / MAGNESIUM_GAIN2 / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[RX_DIRECTION] == "manual") { - return meta_range_t(DSA_MIN_GAIN, DSA_MAX_GAIN, DSA_MAX_GAIN); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - - // RX amp - subtree->create<double>(rx_fe_path / "gains" / MAGNESIUM_AMP / "value") - .set_coercer([this, chan_idx](const double gain) { - return this->_set_rx_gain(MAGNESIUM_AMP, gain, chan_idx); - }) - .set_publisher( - [this, chan_idx]() { return this->_get_rx_gain(MAGNESIUM_AMP, chan_idx); }); - - subtree->create<meta_range_t>(rx_fe_path / "gains" / MAGNESIUM_AMP / "range") - .add_coerced_subscriber([](const meta_range_t&) { - throw uhd::runtime_error("Attempting to update gain range!"); - }) - .set_publisher([this]() { - if (_gain_profile[RX_DIRECTION] == "manual") { - return meta_range_t(AMP_MIN_GAIN, AMP_MAX_GAIN, AMP_GAIN_STEP); - } else { - return meta_range_t(0.0, 0.0, 0.0); - } - }); - - // TX LO lock sensor ////////////////////////////////////////////////////// - // Note: The lowband and AD9371 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"); - }); - // LO Specific - // RX LO - subtree->create<meta_range_t>(rx_fe_path / "los" / MAGNESIUM_LO1 / "freq/range") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_freq_range(MAGNESIUM_LO1, chan_idx); - }); - subtree - ->create<std::vector<std::string>>( - rx_fe_path / "los" / MAGNESIUM_LO1 / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_sources(MAGNESIUM_LO1, chan_idx); - }); - subtree->create<std::string>(rx_fe_path / "los" / MAGNESIUM_LO1 / "source/value") - .add_coerced_subscriber([this, chan_idx](std::string src) { - this->set_rx_lo_source(src, MAGNESIUM_LO1, chan_idx); - }) - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_source(MAGNESIUM_LO1, chan_idx); - }); - subtree->create<double>(rx_fe_path / "los" / MAGNESIUM_LO1 / "freq/value") - .set_publisher( - [this, chan_idx]() { return this->get_rx_lo_freq(MAGNESIUM_LO1, chan_idx); }) - .set_coercer([this, chan_idx](const double freq) { - return this->set_rx_lo_freq(freq, MAGNESIUM_LO1, chan_idx); - }); - - subtree->create<meta_range_t>(rx_fe_path / "los" / MAGNESIUM_LO2 / "freq/range") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_freq_range(MAGNESIUM_LO2, chan_idx); - }); - subtree - ->create<std::vector<std::string>>( - rx_fe_path / "los" / MAGNESIUM_LO2 / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_sources(MAGNESIUM_LO2, chan_idx); - }); - - subtree->create<std::string>(rx_fe_path / "los" / MAGNESIUM_LO2 / "source/value") - .add_coerced_subscriber([this, chan_idx](std::string src) { - this->set_rx_lo_source(src, MAGNESIUM_LO2, chan_idx); - }) - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_source(MAGNESIUM_LO2, chan_idx); - }); - subtree->create<double>(rx_fe_path / "los" / MAGNESIUM_LO2 / "freq/value") - .set_publisher( - [this, chan_idx]() { return this->get_rx_lo_freq(MAGNESIUM_LO2, chan_idx); }) - .set_coercer([this, chan_idx](double freq) { - return this->set_rx_lo_freq(freq, MAGNESIUM_LO2, chan_idx); - }); - // TX LO - subtree->create<meta_range_t>(tx_fe_path / "los" / MAGNESIUM_LO1 / "freq/range") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_freq_range(MAGNESIUM_LO1, chan_idx); - }); - subtree - ->create<std::vector<std::string>>( - tx_fe_path / "los" / MAGNESIUM_LO1 / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_sources(MAGNESIUM_LO1, chan_idx); - }); - subtree->create<std::string>(tx_fe_path / "los" / MAGNESIUM_LO1 / "source/value") - .add_coerced_subscriber([this, chan_idx](std::string src) { - this->set_tx_lo_source(src, MAGNESIUM_LO1, chan_idx); - }) - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_source(MAGNESIUM_LO1, chan_idx); - }); - subtree->create<double>(tx_fe_path / "los" / MAGNESIUM_LO1 / "freq/value ") - .set_publisher( - [this, chan_idx]() { return this->get_tx_lo_freq(MAGNESIUM_LO1, chan_idx); }) - .set_coercer([this, chan_idx](double freq) { - return this->set_tx_lo_freq(freq, MAGNESIUM_LO1, chan_idx); - }); - - subtree->create<meta_range_t>(tx_fe_path / "los" / MAGNESIUM_LO2 / "freq/range") - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_freq_range(MAGNESIUM_LO2, chan_idx); - }); - subtree - ->create<std::vector<std::string>>( - tx_fe_path / "los" / MAGNESIUM_LO2 / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_sources(MAGNESIUM_LO2, chan_idx); - }); - - subtree->create<std::string>(tx_fe_path / "los" / MAGNESIUM_LO2 / "source/value") - .add_coerced_subscriber([this, chan_idx](std::string src) { - this->set_tx_lo_source(src, MAGNESIUM_LO2, chan_idx); - }) - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_source(MAGNESIUM_LO2, chan_idx); - }); - subtree->create<double>(tx_fe_path / "los" / MAGNESIUM_LO2 / "freq/value") - .set_publisher( - [this, chan_idx]() { return this->get_tx_lo_freq(MAGNESIUM_LO2, chan_idx); }) - .set_coercer([this, chan_idx](double freq) { - return this->set_tx_lo_freq(freq, MAGNESIUM_LO2, chan_idx); - }); -} - -void magnesium_radio_ctrl_impl::_init_prop_tree() -{ - const fs_path fe_base = fs_path("dboards") / _radio_slot; - for (size_t chan_idx = 0; chan_idx < MAGNESIUM_NUM_CHANS; chan_idx++) { - this->_init_frontend_subtree(_tree->subtree(fe_base), chan_idx); - } - - // EEPROM paths subject to change FIXME - _tree->create<eeprom_map_t>(_root_path / "eeprom").set(eeprom_map_t()); - - // TODO change codec names - _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("AD9371 Dual ADC"); - _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("AD9371 Dual DAC"); - - // TODO remove this dirty hack - if (not _tree->exists("tick_rate")) { - _tree->create<double>("tick_rate").set_publisher([this]() { - return this->get_rate(); - }); - } - - // *****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 magnesium_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/rhodium/CMakeLists.txt b/host/lib/usrp/dboard/rhodium/CMakeLists.txt index 2b2e9744c..3159037c4 100644 --- a/host/lib/usrp/dboard/rhodium/CMakeLists.txt +++ b/host/lib/usrp/dboard/rhodium/CMakeLists.txt @@ -6,10 +6,10 @@ if(ENABLE_MPMD) list(APPEND RHODIUM_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_ctrl_impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_ctrl_init.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_ctrl_cpld.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_ctrl_lo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_control.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_control_init.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_control_cpld.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_radio_control_lo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_bands.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_cpld_ctrl.cpp ) diff --git a/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp b/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp index ffa206195..9e0a1d3d3 100644 --- a/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp +++ b/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp @@ -4,8 +4,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later // -#include "rhodium_radio_ctrl_impl.hpp" #include "rhodium_constants.hpp" +#include "rhodium_radio_control.hpp" #include <uhd/utils/math.hpp> using namespace uhd; @@ -30,7 +30,7 @@ namespace { * chosen to allow as much of the full bandwidth through unattenuated. * * Switch selection logic for these bands can be found in - * rhodium_radio_ctrl_impl::_update_rx_freq_switches() + * rhodium_radio_control_impl::_update_rx_freq_switches() */ constexpr double RHODIUM_RX_BAND0_MIN_FREQ = RHODIUM_MIN_FREQ; constexpr double RHODIUM_RX_BAND1_MIN_FREQ = 450e6; @@ -55,7 +55,7 @@ namespace { * bandwidth through unattenuated. * * Switch selection logic for these bands can be found in - * rhodium_radio_ctrl_impl::_update_tx_freq_switches() + * rhodium_radio_control_impl::_update_tx_freq_switches() */ constexpr double RHODIUM_TX_BAND0_MIN_FREQ = RHODIUM_MIN_FREQ; constexpr double RHODIUM_TX_BAND1_MIN_FREQ = 450e6; @@ -67,9 +67,9 @@ namespace { constexpr double RHODIUM_TX_BAND7_MIN_FREQ = 4100e6; } -rhodium_radio_ctrl_impl::rx_band -rhodium_radio_ctrl_impl::_map_freq_to_rx_band(const double freq) { - +rhodium_radio_control_impl::rx_band rhodium_radio_control_impl::_map_freq_to_rx_band( + const double freq) +{ auto freq_compare = fp_compare_epsilon<double>(freq, RHODIUM_FREQ_COMPARE_EPSILON); if (freq_compare < RHODIUM_RX_BAND0_MIN_FREQ) { @@ -95,9 +95,9 @@ rhodium_radio_ctrl_impl::_map_freq_to_rx_band(const double freq) { } } -rhodium_radio_ctrl_impl::tx_band -rhodium_radio_ctrl_impl::_map_freq_to_tx_band(const double freq) { - +rhodium_radio_control_impl::tx_band rhodium_radio_control_impl::_map_freq_to_tx_band( + const double freq) +{ auto freq_compare = fp_compare_epsilon<double>(freq, RHODIUM_FREQ_COMPARE_EPSILON); if (freq_compare < RHODIUM_TX_BAND0_MIN_FREQ) { @@ -123,12 +123,12 @@ rhodium_radio_ctrl_impl::_map_freq_to_tx_band(const double freq) { } } -bool rhodium_radio_ctrl_impl::_is_rx_lowband(const double freq) +bool rhodium_radio_control_impl::_is_rx_lowband(const double freq) { return _map_freq_to_rx_band(freq) == rx_band::RX_BAND_0; } -bool rhodium_radio_ctrl_impl::_is_tx_lowband(const double freq) +bool rhodium_radio_control_impl::_is_tx_lowband(const double freq) { return _map_freq_to_tx_band(freq) == tx_band::TX_BAND_0; } diff --git a/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp b/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp index c52a73bca..69e6bf676 100644 --- a/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp +++ b/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp @@ -48,6 +48,8 @@ static constexpr double LO_MIN_POWER = 0.0; static constexpr double LO_MAX_POWER = 63.0; static constexpr double LO_POWER_STEP = 1.0; +static constexpr double RHODIUM_DEFAULT_BANDWIDTH = 250e6; // Hz + static const std::vector<std::string> RHODIUM_RX_ANTENNAS = { "TX/RX", "RX2", "CAL", "TERM" }; @@ -57,10 +59,15 @@ static const std::vector<std::string> RHODIUM_TX_ANTENNAS = { }; // These names are taken from radio_rhodium.xml -static constexpr char SPUR_DODGING_ARG_NAME[] = "spur_dodging"; -static constexpr char SPUR_DODGING_THRESHOLD_ARG_NAME[] = "spur_dodging_threshold"; -static constexpr char HIGHBAND_SPUR_REDUCTION_ARG_NAME[] = "highband_spur_reduction"; +static constexpr char SPUR_DODGING_PROP_NAME[] = "spur_dodging"; +static constexpr char SPUR_DODGING_THRESHOLD_PROP_NAME[] = "spur_dodging_threshold"; +static constexpr char HIGHBAND_SPUR_REDUCTION_PROP_NAME[] = "highband_spur_reduction"; +static constexpr char RHODIUM_DEFAULT_SPUR_DOGING_MODE[] = "disabled"; +static constexpr double RHODIUM_DEFAULT_SPUR_DOGING_THRESHOLD = 2e6; +static constexpr char RHODIUM_DEFAULT_HB_SPUR_REDUCTION_MODE[] = "disabled"; + +static constexpr char RHODIUM_FPGPIO_BANK[] = "FP0"; static constexpr uint32_t RHODIUM_GPIO_MASK = 0x1F; static constexpr uint32_t SW10_GPIO_MASK = 0x3; static constexpr uint32_t LED_GPIO_MASK = 0x1C; @@ -85,8 +92,9 @@ static constexpr char RHODIUM_LO_GAIN[] = "dsa"; //! LO output power static constexpr char RHODIUM_LO_POWER[] = "lo"; -static constexpr int NUM_LO_OUTPUT_PORT_NAMES = 4; +static constexpr char RHODIUM_FE_NAME[] = "Rhodium"; +static constexpr int NUM_LO_OUTPUT_PORT_NAMES = 4; static constexpr std::array<const char*, NUM_LO_OUTPUT_PORT_NAMES> LO_OUTPUT_PORT_NAMES = { "LO_OUT_0", "LO_OUT_1", @@ -96,4 +104,28 @@ static constexpr std::array<const char*, NUM_LO_OUTPUT_PORT_NAMES> LO_OUTPUT_POR static constexpr size_t RHODIUM_NUM_CHANS = 1; +namespace n320_regs { + +static constexpr uint32_t PERIPH_BASE = 0x80000; +static constexpr uint32_t PERIPH_REG_OFFSET = 8; + +// db_control registers +static constexpr uint32_t SR_MISC_OUTS = PERIPH_BASE + 160 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_SPI = PERIPH_BASE + 168 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_LEDS = PERIPH_BASE + 176 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_FP_GPIO = PERIPH_BASE + 184 * PERIPH_REG_OFFSET; +static constexpr uint32_t SR_DB_GPIO = PERIPH_BASE + 192 * PERIPH_REG_OFFSET; + +static constexpr uint32_t RB_MISC_IO = PERIPH_BASE + 16 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_SPI = PERIPH_BASE + 17 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_LEDS = PERIPH_BASE + 18 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_DB_GPIO = PERIPH_BASE + 19 * PERIPH_REG_OFFSET; +static constexpr uint32_t RB_FP_GPIO = PERIPH_BASE + 20 * PERIPH_REG_OFFSET; + +//! Delta between frontend offsets for channel 0 and 1 +constexpr uint32_t SR_TX_FE_BASE = PERIPH_BASE + 208 * PERIPH_REG_OFFSET; +constexpr uint32_t SR_RX_FE_BASE = PERIPH_BASE + 224 * PERIPH_REG_OFFSET; + +} // namespace n320_regs + #endif /* INCLUDED_LIBUHD_RHODIUM_CONSTANTS_HPP */ diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp new file mode 100644 index 000000000..a3b072e74 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp @@ -0,0 +1,723 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_radio_control.hpp" +#include "rhodium_constants.hpp" +#include <uhd/exception.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhdlib/usrp/common/apply_corrections.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include <cmath> +#include <cstdlib> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +namespace { + constexpr char RX_FE_CONNECTION_LOWBAND[] = "QI"; + constexpr char RX_FE_CONNECTION_HIGHBAND[] = "IQ"; + constexpr char TX_FE_CONNECTION_LOWBAND[] = "QI"; + constexpr char TX_FE_CONNECTION_HIGHBAND[] = "IQ"; + + constexpr double DEFAULT_IDENTIFY_DURATION = 5.0; // seconds + + constexpr uint64_t SET_RATE_RPC_TIMEOUT_MS = 10000; + +} + + +/****************************************************************************** + * Structors + *****************************************************************************/ +rhodium_radio_control_impl::rhodium_radio_control_impl(make_args_ptr make_args) + : radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("Entering rhodium_radio_control_impl ctor..."); + UHD_ASSERT_THROW(get_block_id().get_block_count() < 2); + const char radio_slot_name[] = {'A', 'B'}; + _radio_slot = radio_slot_name[get_block_id().get_block_count()]; + _rpc_prefix = + (_radio_slot == "A") ? "db_0_" : "db_1_"; + RFNOC_LOG_TRACE("Radio slot: " << _radio_slot); + UHD_ASSERT_THROW(get_num_input_ports() == RHODIUM_NUM_CHANS); + UHD_ASSERT_THROW(get_num_output_ports() == RHODIUM_NUM_CHANS); + UHD_ASSERT_THROW(get_mb_controller()); + _n320_mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller()); + UHD_ASSERT_THROW(_n320_mb_control); + _n3xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>( + _n320_mb_control->get_timekeeper(0)); + UHD_ASSERT_THROW(_n3xx_timekeeper); + _rpcc = _n320_mb_control->get_rpc_client(); + UHD_ASSERT_THROW(_rpcc); + + const auto all_dboard_info = + _rpcc->request<std::vector<std::map<std::string, std::string>>>( + "get_dboard_info"); + RFNOC_LOG_TRACE("Hardware detected " << all_dboard_info.size() << " daughterboards."); + + // If we two radio blocks, but there is only one dboard plugged in, we skip + // initialization. The board needs to be in slot A + if (all_dboard_info.size() > get_block_id().get_block_count()) { + _init_defaults(); + _init_mpm(); + _init_peripherals(); + _init_prop_tree(); + } + + // Properties + for (auto& samp_rate_prop : _samp_rate_in) { + samp_rate_prop.set(_master_clock_rate); + } + for (auto& samp_rate_prop : _samp_rate_out) { + samp_rate_prop.set(_master_clock_rate); + } +} + +rhodium_radio_control_impl::~rhodium_radio_control_impl() +{ + RFNOC_LOG_TRACE("rhodium_radio_control_impl::dtor() "); +} + + +/****************************************************************************** + * RF API Calls + *****************************************************************************/ +double rhodium_radio_control_impl::set_rate(double requested_rate) +{ + meta_range_t rates; + for (const double rate : RHODIUM_RADIO_RATES) { + rates.push_back(range_t(rate)); + } + + const double rate = rates.clip(requested_rate); + if (!math::frequencies_are_equal(requested_rate, rate)) { + RFNOC_LOG_WARNING("Coercing requested sample rate from " + << (requested_rate / 1e6) << " MHz to " << (rate / 1e6) + << " MHz, the closest possible rate."); + } + + const double current_rate = get_rate(); + if (math::frequencies_are_equal(current_rate, rate)) { + RFNOC_LOG_DEBUG( + "Rate is already at " << (rate / 1e6) << " MHz. Skipping set_rate()"); + return current_rate; + } + + RFNOC_LOG_TRACE("Updating master clock rate to " << rate); + _master_clock_rate = _rpcc->request_with_token<double>( + SET_RATE_RPC_TIMEOUT_MS, "db_0_set_master_clock_rate", rate); + _n3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + // The lowband LO frequency will change with the master clock rate, so + // update the tuning of the device. + set_tx_frequency(get_tx_frequency(0), 0); + set_rx_frequency(get_rx_frequency(0), 0); + + set_tick_rate(_master_clock_rate); + return _master_clock_rate; +} + +void rhodium_radio_control_impl::set_tx_antenna(const std::string& ant, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_antenna(ant=" << ant << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + if (!uhd::has(RHODIUM_TX_ANTENNAS, ant)) { + RFNOC_LOG_ERROR("Invalid TX antenna value: " << ant); + throw uhd::value_error("Requesting invalid TX antenna value!"); + } + + _update_tx_output_switches(ant); + // _update_atr will set the cached antenna value, so no need to do + // it here. See comments in _update_antenna for more info. + _update_atr(ant, TX_DIRECTION); +} + +void rhodium_radio_control_impl::set_rx_antenna(const std::string& ant, const size_t chan) +{ + RFNOC_LOG_TRACE("Setting RX antenna to " << ant); + UHD_ASSERT_THROW(chan == 0); + + if (!uhd::has(RHODIUM_RX_ANTENNAS, ant)) { + RFNOC_LOG_ERROR("Invalid RX antenna value: " << ant); + throw uhd::value_error("Requesting invalid RX antenna value!"); + } + + _update_rx_input_switches(ant); + // _update_atr will set the cached antenna value, so no need to do + // it here. See comments in _update_antenna for more info. + _update_atr(ant, RX_DIRECTION); +} + +void rhodium_radio_control_impl::_set_tx_fe_connection(const std::string& conn) +{ + RFNOC_LOG_TRACE("set_tx_fe_connection(conn=" << conn << ")"); + if (conn != _tx_fe_connection) { + _tx_fe_core->set_mux(conn); + _tx_fe_connection = conn; + } +} + +void rhodium_radio_control_impl::_set_rx_fe_connection(const std::string& conn) +{ + RFNOC_LOG_TRACE("set_rx_fe_connection(conn=" << conn << ")"); + if (conn != _rx_fe_connection) { + _rx_fe_core->set_fe_connection(conn); + _rx_fe_connection = conn; + } +} + +std::string rhodium_radio_control_impl::_get_tx_fe_connection() const +{ + return _tx_fe_connection; +} + +std::string rhodium_radio_control_impl::_get_rx_fe_connection() const +{ + return _rx_fe_connection; +} + +double rhodium_radio_control_impl::set_tx_frequency(const double freq, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + const auto old_freq = get_tx_frequency(0); + double coerced_target_freq = uhd::clip(freq, RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ); + + if (freq != coerced_target_freq) { + RFNOC_LOG_DEBUG("Requested frequency is outside supported range. Coercing to " + << coerced_target_freq); + } + + const bool is_highband = !_is_tx_lowband(coerced_target_freq); + + const double target_lo_freq = is_highband ? + coerced_target_freq : _get_lowband_lo_freq() - coerced_target_freq; + const double actual_lo_freq = + set_tx_lo_freq(target_lo_freq, RHODIUM_LO1, chan); + const double coerced_freq = is_highband ? + actual_lo_freq : _get_lowband_lo_freq() - actual_lo_freq; + const auto conn = is_highband ? + TX_FE_CONNECTION_HIGHBAND : TX_FE_CONNECTION_LOWBAND; + + // update the cached frequency value now so calls to set gain and update + // switches will read the new frequency + radio_control_impl::set_tx_frequency(coerced_freq, chan); + + _set_tx_fe_connection(conn); + set_tx_gain(radio_control_impl::get_tx_gain(chan), 0); + + if (_get_highband_spur_reduction_enabled(TX_DIRECTION)) { + if (_get_timed_command_enabled() and _is_tx_lowband(old_freq) != not is_highband) { + RFNOC_LOG_WARNING( + "Timed tuning commands that transition between lowband and highband, 450 " + "MHz, do not function correctly when highband_spur_reduction is enabled! " + "Disable highband_spur_reduction or avoid using timed tuning commands."); + } + RFNOC_LOG_TRACE("TX Lowband LO is " << (is_highband ? "disabled" : "enabled")); + _rpcc->notify_with_token(_rpc_prefix + "enable_tx_lowband_lo", (!is_highband)); + } + _update_tx_freq_switches(coerced_freq); + const bool enable_corrections = is_highband + and (get_tx_lo_source(RHODIUM_LO1, 0) == "internal"); + _update_corrections(actual_lo_freq, TX_DIRECTION, enable_corrections); + // if TX lowband/highband changed and antenna is TX/RX, + // the ATR and SW1 need to be updated + _update_tx_output_switches(get_tx_antenna(0)); + _update_atr(get_tx_antenna(0), TX_DIRECTION); + + return coerced_freq; +} + +double rhodium_radio_control_impl::set_rx_frequency(const double freq, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + const auto old_freq = get_rx_frequency(0); + double coerced_target_freq = uhd::clip(freq, RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ); + + if (freq != coerced_target_freq) { + RFNOC_LOG_DEBUG("Requested frequency is outside supported range. Coercing to " + << coerced_target_freq); + } + + const bool is_highband = !_is_rx_lowband(coerced_target_freq); + + const double target_lo_freq = is_highband ? + coerced_target_freq : _get_lowband_lo_freq() - coerced_target_freq; + const double actual_lo_freq = + set_rx_lo_freq(target_lo_freq, RHODIUM_LO1, chan); + const double coerced_freq = is_highband ? + actual_lo_freq : _get_lowband_lo_freq() - actual_lo_freq; + const auto conn = is_highband ? + RX_FE_CONNECTION_HIGHBAND : RX_FE_CONNECTION_LOWBAND; + + // update the cached frequency value now so calls to set gain and update + // switches will read the new frequency + radio_control_impl::set_rx_frequency(coerced_freq, chan); + + _set_rx_fe_connection(conn); + set_rx_gain(radio_control_impl::get_rx_gain(chan), 0); + + if (_get_highband_spur_reduction_enabled(RX_DIRECTION)) { + if (_get_timed_command_enabled() and _is_rx_lowband(old_freq) != not is_highband) { + RFNOC_LOG_WARNING( + "Timed tuning commands that transition between lowband and highband, 450 " + "MHz, do not function correctly when highband_spur_reduction is enabled! " + "Disable highband_spur_reduction or avoid using timed tuning commands."); + } + RFNOC_LOG_TRACE("RX Lowband LO is " << (is_highband ? "disabled" : "enabled")); + _rpcc->notify_with_token(_rpc_prefix + "enable_rx_lowband_lo", (!is_highband)); + } + _update_rx_freq_switches(coerced_freq); + const bool enable_corrections = is_highband + and (get_rx_lo_source(RHODIUM_LO1, 0) == "internal"); + _update_corrections(actual_lo_freq, RX_DIRECTION, enable_corrections); + + return coerced_freq; +} + +void rhodium_radio_control_impl::set_tx_tune_args( + const uhd::device_addr_t& args, const size_t chan) +{ + UHD_ASSERT_THROW(chan == 0); + _tune_args[uhd::TX_DIRECTION] = args; +} + +void rhodium_radio_control_impl::set_rx_tune_args( + const uhd::device_addr_t& args, const size_t chan) +{ + UHD_ASSERT_THROW(chan == 0); + _tune_args[uhd::RX_DIRECTION] = args; +} + +double rhodium_radio_control_impl::set_tx_gain(const double gain, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + auto freq = this->get_tx_frequency(chan); + auto index = get_tx_gain_range(chan).clip(gain); + + auto old_band = _is_tx_lowband(_tx_frequency_at_last_gain_write) ? + rhodium_cpld_ctrl::gain_band_t::LOW : + rhodium_cpld_ctrl::gain_band_t::HIGH; + auto new_band = _is_tx_lowband(freq) ? + rhodium_cpld_ctrl::gain_band_t::LOW : + rhodium_cpld_ctrl::gain_band_t::HIGH; + + // The CPLD requires a rewrite of the gain control command on a change of lowband or highband + if (radio_control_impl::get_tx_gain(chan) != index or old_band != new_band) { + RFNOC_LOG_TRACE("Writing new TX gain index: " << index); + _cpld->set_gain_index(index, new_band, TX_DIRECTION); + _tx_frequency_at_last_gain_write = freq; + radio_control_impl::set_tx_gain(index, chan); + } else { + RFNOC_LOG_TRACE( + "No change in index or band, skipped writing TX gain index: " << index); + } + + return index; +} + +double rhodium_radio_control_impl::set_rx_gain(const double gain, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + + auto freq = this->get_rx_frequency(chan); + auto index = get_rx_gain_range(chan).clip(gain); + + auto old_band = _is_rx_lowband(_rx_frequency_at_last_gain_write) ? + rhodium_cpld_ctrl::gain_band_t::LOW : + rhodium_cpld_ctrl::gain_band_t::HIGH; + auto new_band = _is_rx_lowband(freq) ? + rhodium_cpld_ctrl::gain_band_t::LOW : + rhodium_cpld_ctrl::gain_band_t::HIGH; + + // The CPLD requires a rewrite of the gain control command on a change of lowband or highband + if (radio_control_impl::get_rx_gain(chan) != index or old_band != new_band) { + RFNOC_LOG_TRACE("Writing new RX gain index: " << index); + _cpld->set_gain_index(index, new_band, RX_DIRECTION); + _rx_frequency_at_last_gain_write = freq; + radio_control_impl::set_rx_gain(index, chan); + } else { + RFNOC_LOG_TRACE( + "No change in index or band, skipped writing RX gain index: " << index); + } + + return index; +} + +void rhodium_radio_control_impl::_identify_with_leds(double identify_duration) +{ + auto duration_ms = static_cast<uint64_t>(identify_duration * 1000); + auto end_time = + std::chrono::steady_clock::now() + std::chrono::milliseconds(duration_ms); + bool led_state = true; + { + std::lock_guard<std::mutex> lock(_ant_mutex); + while (std::chrono::steady_clock::now() < end_time) { + auto atr = led_state ? (LED_RX | LED_RX2 | LED_TX) : 0; + _gpio->set_atr_reg(gpio_atr::ATR_REG_IDLE, atr, RHODIUM_GPIO_MASK); + led_state = !led_state; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } + _update_atr(get_tx_antenna(0), TX_DIRECTION); + _update_atr(get_rx_antenna(0), RX_DIRECTION); +} + +void rhodium_radio_control_impl::_update_atr( + const std::string& ant, const direction_t dir) +{ + // This function updates sw10 based on the value of both antennas, so we + // use a mutex to prevent other calls in this class instance from running + // at the same time. + std::lock_guard<std::mutex> lock(_ant_mutex); + + RFNOC_LOG_TRACE( + "Updating ATRs for " << ((dir == RX_DIRECTION) ? "RX" : "TX") << " to " << ant); + + const auto rx_ant = (dir == RX_DIRECTION) ? ant : get_rx_antenna(0); + const auto tx_ant = (dir == TX_DIRECTION) ? ant : get_tx_antenna(0); + const auto sw10_tx = _is_tx_lowband(get_tx_frequency(0)) ? + SW10_FROMTXLOWBAND : SW10_FROMTXHIGHBAND; + + + const uint32_t atr_idle = SW10_ISOLATION; + + const uint32_t atr_rx = [rx_ant]{ + if (rx_ant == "TX/RX") { + return SW10_TORX | LED_RX; + } else if (rx_ant == "RX2") { + return SW10_ISOLATION | LED_RX2; + } else { + return SW10_ISOLATION; + } + }(); + + const uint32_t atr_tx = (tx_ant == "TX/RX") ? + (sw10_tx | LED_TX) : SW10_ISOLATION; + + const uint32_t atr_dx = [tx_ant, rx_ant, sw10_tx] { + uint32_t sw10_return; + if (tx_ant == "TX/RX") { + // if both channels are set to TX/RX, TX will override + sw10_return = sw10_tx | LED_TX; + } else if (rx_ant == "TX/RX") { + sw10_return = SW10_TORX | LED_RX; + } else { + sw10_return = SW10_ISOLATION; + } + sw10_return |= (rx_ant == "RX2") ? LED_RX2 : 0; + return sw10_return; + }(); + + _gpio->set_atr_reg(gpio_atr::ATR_REG_IDLE, atr_idle, RHODIUM_GPIO_MASK); + _gpio->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, atr_rx, RHODIUM_GPIO_MASK); + _gpio->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, atr_tx, RHODIUM_GPIO_MASK); + _gpio->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, atr_dx, RHODIUM_GPIO_MASK); + + RFNOC_LOG_TRACE( + str(boost::format("Wrote ATR registers i:0x%02X, r:0x%02X, t:0x%02X, d:0x%02X") + % atr_idle % atr_rx % atr_tx % atr_dx)); + + if (dir == RX_DIRECTION) { + radio_control_impl::set_rx_antenna(ant, 0); + } else { + radio_control_impl::set_tx_antenna(ant, 0); + } +} + +void rhodium_radio_control_impl::_update_corrections( + const double freq, const direction_t dir, const bool enable) +{ + const std::string fe_path_part = dir == RX_DIRECTION ? "rx_fe_corrections" + : "tx_fe_corrections"; + const fs_path fe_corr_path = FE_PATH / fe_path_part / 0; + + if (enable) { + const std::vector<uint8_t> db_serial_u8 = get_db_eeprom().count("serial") + ? std::vector<uint8_t>() + : get_db_eeprom().at("serial"); + const std::string db_serial = + db_serial_u8.empty() ? "unknown" + : std::string(db_serial_u8.begin(), db_serial_u8.end()); + RFNOC_LOG_DEBUG("Loading any available frontend corrections for " + << ((dir == RX_DIRECTION) ? "RX" : "TX") << " at " << freq); + if (dir == RX_DIRECTION) { + apply_rx_fe_corrections(get_tree(), db_serial, fe_corr_path, freq); + } else { + apply_tx_fe_corrections(get_tree(), db_serial, fe_corr_path, freq); + } + } else { + RFNOC_LOG_DEBUG("Disabling frontend corrections for " + << ((dir == RX_DIRECTION) ? "RX" : "TX")); + if (dir == RX_DIRECTION) { + _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); + } else { + _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); + _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); + } + } +} + +bool rhodium_radio_control_impl::_get_spur_dodging_enabled(uhd::direction_t dir) const +{ + // get the current tune_arg for spur_dodging + // if the tune_arg doesn't exist, use the radio block argument instead + const std::string spur_dodging_arg = _tune_args.at(dir).cast<std::string>( + SPUR_DODGING_PROP_NAME, _spur_dodging_mode.get()); + + RFNOC_LOG_TRACE("_get_spur_dodging_enabled returning " << spur_dodging_arg); + if (spur_dodging_arg == "enabled") { + return true; + } else if (spur_dodging_arg == "disabled") { + return false; + } else { + const std::string err_msg = str( + boost::format( + "Invalid spur_dodging argument: %s Valid options are [enabled, disabled]") + % spur_dodging_arg); + RFNOC_LOG_ERROR(err_msg); + throw uhd::value_error(err_msg); + } +} + +double rhodium_radio_control_impl::_get_spur_dodging_threshold(uhd::direction_t dir) const +{ + // get the current tune_arg for spur_dodging_threshold + // if the tune_arg doesn't exist, use the radio block argument instead + const double threshold = _tune_args.at(dir).cast<double>( + SPUR_DODGING_THRESHOLD_PROP_NAME, _spur_dodging_threshold.get()); + RFNOC_LOG_TRACE("_get_spur_dodging_threshold returning " << threshold); + return threshold; +} + +bool rhodium_radio_control_impl::_get_highband_spur_reduction_enabled( + uhd::direction_t dir) const +{ + const std::string highband_spur_reduction_arg = _tune_args.at(dir).cast<std::string>( + HIGHBAND_SPUR_REDUCTION_PROP_NAME, _highband_spur_reduction_mode.get()); + + RFNOC_LOG_TRACE(__func__ << " returning " << highband_spur_reduction_arg); + if (highband_spur_reduction_arg == "enabled") { + return true; + } else if (highband_spur_reduction_arg == "disabled") { + return false; + } else { + throw uhd::value_error( + str(boost::format("Invalid highband_spur_reduction argument: %s Valid " + "options are [enabled, disabled]") + % highband_spur_reduction_arg)); + } +} + +bool rhodium_radio_control_impl::_get_timed_command_enabled() const +{ + return get_command_time(0) != time_spec_t::ASAP; +} + +std::vector<std::string> rhodium_radio_control_impl::get_tx_antennas(const size_t) const +{ + return RHODIUM_RX_ANTENNAS; +} + +std::vector<std::string> rhodium_radio_control_impl::get_rx_antennas(const size_t) const +{ + return RHODIUM_TX_ANTENNAS; +} + +uhd::freq_range_t rhodium_radio_control_impl::get_tx_frequency_range(const size_t) const +{ + return meta_range_t(RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ, 1.0); +} + +uhd::freq_range_t rhodium_radio_control_impl::get_rx_frequency_range(const size_t) const +{ + return meta_range_t(RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ, 1.0); +} + +uhd::gain_range_t rhodium_radio_control_impl::get_tx_gain_range(const size_t) const +{ + return gain_range_t(TX_MIN_GAIN, TX_MAX_GAIN, TX_GAIN_STEP); +} + +uhd::gain_range_t rhodium_radio_control_impl::get_rx_gain_range(const size_t) const +{ + return gain_range_t(RX_MIN_GAIN, RX_MAX_GAIN, RX_GAIN_STEP); +} + +uhd::meta_range_t rhodium_radio_control_impl::get_tx_bandwidth_range(size_t) const +{ + return meta_range_t(RHODIUM_DEFAULT_BANDWIDTH, RHODIUM_DEFAULT_BANDWIDTH); +} + +uhd::meta_range_t rhodium_radio_control_impl::get_rx_bandwidth_range(size_t) const +{ + return meta_range_t(RHODIUM_DEFAULT_BANDWIDTH, RHODIUM_DEFAULT_BANDWIDTH); +} + + +/************************************************************************** + * Radio Identification API Calls + *************************************************************************/ +size_t rhodium_radio_control_impl::get_chan_from_dboard_fe( + const std::string& fe, const direction_t /* dir */ + ) const +{ + UHD_ASSERT_THROW(boost::lexical_cast<size_t>(fe) == 0); + return 0; +} + +std::string rhodium_radio_control_impl::get_dboard_fe_from_chan( + const size_t chan, const direction_t /* dir */ + ) const +{ + UHD_ASSERT_THROW(chan == 0); + return "0"; +} + +std::string rhodium_radio_control_impl::get_fe_name( + const size_t, const uhd::direction_t) const +{ + return RHODIUM_FE_NAME; +} + +/************************************************************************** + * GPIO Controls + *************************************************************************/ +std::vector<std::string> rhodium_radio_control_impl::get_gpio_banks() const +{ + return {RHODIUM_FPGPIO_BANK}; +} + +void rhodium_radio_control_impl::set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value) +{ + if (bank != RHODIUM_FPGPIO_BANK) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + if (!gpio_atr::gpio_attr_rev_map.count(attr)) { + RFNOC_LOG_ERROR("Invalid GPIO attr: " << attr); + throw uhd::key_error("Invalid GPIO attr!"); + } + + const gpio_atr::gpio_attr_t gpio_attr = gpio_atr::gpio_attr_rev_map.at(attr); + + if (gpio_attr == gpio_atr::GPIO_READBACK) { + RFNOC_LOG_WARNING("Cannot set READBACK attr."); + return; + } + + _fp_gpio->set_gpio_attr(gpio_attr, value); +} + +uint32_t rhodium_radio_control_impl::get_gpio_attr( + const std::string& bank, const std::string& attr) +{ + if (bank != RHODIUM_FPGPIO_BANK) { + RFNOC_LOG_ERROR("Invalid GPIO bank: " << bank); + throw uhd::key_error("Invalid GPIO bank!"); + } + + return _fp_gpio->get_attr_reg(usrp::gpio_atr::gpio_attr_rev_map.at(attr)); +} + +/****************************************************************************** + * EEPROM API + *****************************************************************************/ +void rhodium_radio_control_impl::set_db_eeprom(const eeprom_map_t& db_eeprom) +{ + const size_t db_idx = get_block_id().get_block_count(); + _rpcc->notify_with_token("set_db_eeprom", db_idx, db_eeprom); + _db_eeprom = this->_rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", db_idx); +} + +eeprom_map_t rhodium_radio_control_impl::get_db_eeprom() +{ + return _db_eeprom; +} + +/************************************************************************** + * Sensor API + *************************************************************************/ +std::vector<std::string> rhodium_radio_control_impl::get_rx_sensor_names(size_t) const +{ + return _rx_sensor_names; +} + +sensor_value_t rhodium_radio_control_impl::get_rx_sensor( + const std::string& name, size_t chan) +{ + if (!uhd::has(_rx_sensor_names, name)) { + RFNOC_LOG_ERROR("Invalid RX sensor name: " << name); + throw uhd::key_error("Invalid RX sensor name!"); + } + if (name == "lo_locked") { + return sensor_value_t( + "all_los", this->get_lo_lock_status(RX_DIRECTION), "locked", "unlocked"); + } + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "RX", name, chan)); +} + +std::vector<std::string> rhodium_radio_control_impl::get_tx_sensor_names(size_t) const +{ + return _tx_sensor_names; +} + +sensor_value_t rhodium_radio_control_impl::get_tx_sensor( + const std::string& name, size_t chan) +{ + if (!uhd::has(_rx_sensor_names, name)) { + RFNOC_LOG_ERROR("Invalid RX sensor name: " << name); + throw uhd::key_error("Invalid RX sensor name!"); + } + if (name == "lo_locked") { + return sensor_value_t( + "all_los", this->get_lo_lock_status(TX_DIRECTION), "locked", "unlocked"); + } + return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( + _rpc_prefix + "get_sensor", "TX", name, chan)); +} + +bool rhodium_radio_control_impl::get_lo_lock_status(const direction_t dir) const +{ + return (dir == RX_DIRECTION) ? _rx_lo->get_lock_status() : _tx_lo->get_lock_status(); +} + +/************************************************************************** + * node_t API Calls + *************************************************************************/ +void rhodium_radio_control_impl::set_command_time( + uhd::time_spec_t time, const size_t chan) +{ + UHD_ASSERT_THROW(chan == 0); + node_t::set_command_time(time, chan); + _wb_iface->set_time(time); +} + +// Register the block +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + rhodium_radio_control, RADIO_BLOCK, N320, "Radio", true, "radio_clk", "bus_clk"); diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control.hpp index fad987b98..a70db79cc 100644 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control.hpp @@ -1,5 +1,6 @@ // // Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -7,28 +8,28 @@ #ifndef INCLUDED_LIBUHD_RFNOC_RHODIUM_RADIO_CTRL_IMPL_HPP #define INCLUDED_LIBUHD_RFNOC_RHODIUM_RADIO_CTRL_IMPL_HPP +#include "rhodium_constants.hpp" #include "rhodium_cpld_ctrl.hpp" #include "rhodium_cpld_regs.hpp" +#include <uhd/types/serial.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <uhdlib/rfnoc/radio_control_impl.hpp> +#include <uhdlib/rfnoc/rpc_block_ctrl.hpp> #include <uhdlib/usrp/common/lmx2592.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> #include <uhdlib/usrp/cores/gpio_atr_3000.hpp> -#include <uhdlib/rfnoc/rpc_block_ctrl.hpp> -#include <uhdlib/rfnoc/radio_ctrl_impl.hpp> #include <uhdlib/usrp/cores/rx_frontend_core_3000.hpp> #include <uhdlib/usrp/cores/tx_frontend_core_200.hpp> -#include <uhd/types/serial.hpp> -#include <uhd/usrp/dboard_manager.hpp> -#include <uhd/usrp/gpio_defs.hpp> #include <mutex> -namespace uhd { - namespace rfnoc { +namespace uhd { namespace rfnoc { /*! \brief Provide access to an Rhodium radio. */ -class rhodium_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl +class rhodium_radio_control_impl : public radio_control_impl { public: - typedef boost::shared_ptr<rhodium_radio_ctrl_impl> sptr; + typedef boost::shared_ptr<rhodium_radio_control_impl> sptr; //! Frequency bands for RX. Bands are a function of the analog filter banks enum class rx_band { @@ -59,54 +60,108 @@ public: /************************************************************************ * Structors ***********************************************************************/ - UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(rhodium_radio_ctrl) - virtual ~rhodium_radio_ctrl_impl(); + rhodium_radio_control_impl(make_args_ptr make_args); + virtual ~rhodium_radio_control_impl(); /************************************************************************ - * API calls + * RF API calls ***********************************************************************/ // Note: We use the cached values in radio_ctrl_impl, so most getters are // not reimplemented here - double set_rate(const double rate); + double set_rate(double rate); + // Setters 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); - + void set_tx_tune_args(const uhd::device_addr_t&, const size_t chan); + void set_rx_tune_args(const uhd::device_addr_t&, const size_t chan); double set_tx_gain(const double gain, const size_t chan); double set_rx_gain(const double gain, const size_t chan); - // LO Property Getters - std::vector<std::string> get_tx_lo_names(const size_t chan); - std::vector<std::string> get_rx_lo_names(const size_t chan); - std::vector<std::string> get_tx_lo_sources(const std::string& name, const size_t chan); - std::vector<std::string> get_rx_lo_sources(const std::string& name, const size_t chan); - freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan); - freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan); + // Getters + std::vector<std::string> get_tx_antennas(const size_t) const; + std::vector<std::string> get_rx_antennas(const size_t) const; + uhd::freq_range_t get_tx_frequency_range(const size_t) const; + uhd::freq_range_t get_rx_frequency_range(const size_t) const; + uhd::gain_range_t get_tx_gain_range(const size_t) const; + uhd::gain_range_t get_rx_gain_range(const size_t) const; + uhd::meta_range_t get_tx_bandwidth_range(size_t) const; + uhd::meta_range_t get_rx_bandwidth_range(size_t) const; - // LO Frequency Control - double set_tx_lo_freq(const double freq, const std::string& name, const size_t chan); - double set_rx_lo_freq(const double freq, const std::string& name, const size_t chan); - double get_tx_lo_freq(const std::string& name, const size_t chan); + /************************************************************************** + * LO Controls + *************************************************************************/ + std::vector<std::string> get_rx_lo_names(const size_t chan) const; + std::vector<std::string> get_rx_lo_sources( + const std::string& name, const size_t chan) const; + freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan) const; + void set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan); + const std::string get_rx_lo_source(const std::string& name, const size_t chan); + double set_rx_lo_freq(double freq, const std::string& name, const size_t chan); double get_rx_lo_freq(const std::string& name, const size_t chan); - - // LO Source Control - void set_tx_lo_source(const std::string& src, const std::string& name, const size_t chan); - void set_rx_lo_source(const std::string& src, const std::string& name, const size_t chan); + std::vector<std::string> get_tx_lo_names(const size_t chan) const; + std::vector<std::string> get_tx_lo_sources( + const std::string& name, const size_t chan) const; + freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan); + void set_tx_lo_source( + const std::string& src, const std::string& name, const size_t chan); const std::string get_tx_lo_source(const std::string& name, const size_t chan); - const std::string get_rx_lo_source(const std::string& name, const size_t chan); - + double set_tx_lo_freq(const double freq, const std::string& name, const size_t chan); + double get_tx_lo_freq(const std::string& name, const size_t chan); // LO Export Control - void set_tx_lo_export_enabled(const bool enabled, const std::string& name, const size_t chan); - void set_rx_lo_export_enabled(const bool enabled, const std::string& name, const size_t chan); + void set_tx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan); + void set_rx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan); bool get_tx_lo_export_enabled(const std::string& name, const size_t chan); bool get_rx_lo_export_enabled(const std::string& name, const size_t chan); + /************************************************************************** + * GPIO Controls + *************************************************************************/ + std::vector<std::string> get_gpio_banks() const; + void set_gpio_attr( + const std::string& bank, const std::string& attr, const uint32_t value); + uint32_t get_gpio_attr(const std::string& bank, const std::string& attr); + + /************************************************************************** + * EEPROM API + *************************************************************************/ + void set_db_eeprom(const uhd::eeprom_map_t& db_eeprom); + uhd::eeprom_map_t get_db_eeprom(); + + /************************************************************************** + * Sensor API + *************************************************************************/ + std::vector<std::string> get_rx_sensor_names(size_t chan) const; + uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan); + std::vector<std::string> get_tx_sensor_names(size_t chan) const; + uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan); + + /************************************************************************** + * Radio Identification API Calls + *************************************************************************/ + std::string get_slot_name() const + { + return _radio_slot; + } + size_t get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t direction) const; + std::string get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t direction) const; + std::string get_fe_name(const size_t chan, const uhd::direction_t direction) const; + + /************************************************************************** + * node_t API Calls + *************************************************************************/ + void set_command_time(uhd::time_spec_t time, const size_t chan); + + /************************************************************************ + * ??? calls + ***********************************************************************/ // LO Distribution Control void set_tx_lo_output_enabled(const bool enabled, const std::string& port_name, const size_t chan); void set_rx_lo_output_enabled(const bool enabled, const std::string& port_name, const size_t chan); @@ -139,38 +194,49 @@ public: double get_tx_lo_power(const std::string &name, const size_t chan); double get_rx_lo_power(const std::string &name, 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: /************************************************************************** + * noc_block_base API + *************************************************************************/ + //! Safely shut down all peripherals + // + // Reminder: After this is called, no peeks and pokes are allowed! + void deinit() + { + RFNOC_LOG_TRACE("deinit()"); + // Remove access to all peripherals + _wb_iface.reset(); + _spi.reset(); + _tx_lo.reset(); + _rx_lo.reset(); + _cpld.reset(); + _gpio.reset(); + _fp_gpio.reset(); + _rx_fe_core.reset(); + _tx_fe_core.reset(); + } + + /************************************************************************** * Helpers *************************************************************************/ //! Initialize all the peripherals connected to this block void _init_peripherals(); + //! Sync up with MPM + void _init_mpm(); + //! 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 - ); + void _init_frontend_subtree(uhd::property_tree::sptr subtree); //! Initialize property tree void _init_prop_tree(); //! Discover and initialize any mpm sensors - void _init_mpm_sensors( - const direction_t dir, - const size_t chan_idx - ); + void _init_mpm_sensors(const direction_t dir, const size_t chan_idx); //! Get the frequency range for an LO freq_range_t _get_lo_freq_range(const std::string &name) const; @@ -235,8 +301,6 @@ private: // NOTE: Returns false if frequency is out of Rh's tx frequency range static bool _is_tx_lowband(const double freq); - //! Return the gain range for dir - static uhd::gain_range_t _get_gain_range(const direction_t dir); //! Return the gain range of the LMX LO static uhd::gain_range_t _get_lo_gain_range(); //! Return the power setting range of the LMX LO @@ -303,12 +367,18 @@ private: //! Daughterboard info from MPM std::map<std::string, std::string> _dboard_info; - //! Additional block args; gets set during set_rpc_client() - uhd::device_addr_t _block_args; + //! Reference to the MB controller + mpmd_mb_controller::sptr _n320_mb_control; + + //! Reference to the MB timekeeper + uhd::rfnoc::mpmd_mb_controller::mpmd_timekeeper::sptr _n3xx_timekeeper; //! Reference to the RPC client uhd::rpc_client::sptr _rpcc; + //! Reference to wb_iface compat adapter. This will call into this->regs() + uhd::timed_wb_iface::sptr _wb_iface; + //! Reference to the SPI core uhd::spi_iface::sptr _spi; @@ -343,7 +413,8 @@ private: std::string _rx_fe_connection; std::string _tx_fe_connection; //! Desired RF frequency - std::map<direction_t,double> _desired_rf_freq = { {RX_DIRECTION, 2.44e9}, {TX_DIRECTION, 2.44e9} }; + std::map<direction_t, double> _desired_rf_freq = { + {RX_DIRECTION, 2.44e9}, {TX_DIRECTION, 2.44e9}}; //! Frequency at which gain setting was last applied. The CPLD requires a new gain // control write when switching between lowband and highband frequencies, so save // the frequency when sending a gain control command. @@ -356,7 +427,8 @@ private: double _lo_rx_power = 0.0; double _lo_tx_power = 0.0; //! Gain profile - std::map<direction_t,std::string> _gain_profile = { {RX_DIRECTION, "default"}, {TX_DIRECTION, "default"} }; + std::map<direction_t, std::string> _gain_profile = { + {RX_DIRECTION, "default"}, {TX_DIRECTION, "default"}}; //! LO source std::string _rx_lo_source = "internal"; @@ -377,7 +449,30 @@ private: bool _lo_dist_rx_out_enabled[4] = { false, false, false, false }; bool _lo_dist_tx_out_enabled[4] = { false, false, false, false }; -}; /* class radio_ctrl_impl */ + std::unordered_map<uhd::direction_t, uhd::device_addr_t, std::hash<size_t>> + _tune_args{{uhd::RX_DIRECTION, uhd::device_addr_t()}, + {uhd::TX_DIRECTION, uhd::device_addr_t()}}; + + //! Cache the contents of the DB EEPROM + uhd::eeprom_map_t _db_eeprom; + + //! Cached list of RX sensor names + std::vector<std::string> _rx_sensor_names{"lo_locked"}; + //! Cached list of TX sensor names + std::vector<std::string> _tx_sensor_names{"lo_locked"}; + + property_t<std::string> _spur_dodging_mode{SPUR_DODGING_PROP_NAME, + RHODIUM_DEFAULT_SPUR_DOGING_MODE, + {res_source_info::USER}}; + property_t<double> _spur_dodging_threshold{SPUR_DODGING_THRESHOLD_PROP_NAME, + RHODIUM_DEFAULT_SPUR_DOGING_THRESHOLD, + {res_source_info::USER}}; + property_t<std::string> _highband_spur_reduction_mode{ + HIGHBAND_SPUR_REDUCTION_PROP_NAME, + RHODIUM_DEFAULT_HB_SPUR_REDUCTION_MODE, + {res_source_info::USER}}; + +}; /* class rhodium_radio_control_impl */ }} /* namespace uhd::rfnoc */ diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_control_cpld.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_cpld.cpp new file mode 100644 index 000000000..f4f17e8ab --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_cpld.cpp @@ -0,0 +1,252 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_constants.hpp" +#include "rhodium_cpld_ctrl.hpp" +#include "rhodium_radio_control.hpp" +#include <uhd/utils/log.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { + +const char* rx_band_to_log(rhodium_radio_control_impl::rx_band rx_band) +{ + switch (rx_band) { + case rhodium_radio_control_impl::rx_band::RX_BAND_0: + return "0"; + case rhodium_radio_control_impl::rx_band::RX_BAND_1: + return "1"; + case rhodium_radio_control_impl::rx_band::RX_BAND_2: + return "2"; + case rhodium_radio_control_impl::rx_band::RX_BAND_3: + return "3"; + case rhodium_radio_control_impl::rx_band::RX_BAND_4: + return "4"; + case rhodium_radio_control_impl::rx_band::RX_BAND_5: + return "5"; + case rhodium_radio_control_impl::rx_band::RX_BAND_6: + return "6"; + case rhodium_radio_control_impl::rx_band::RX_BAND_7: + return "7"; + case rhodium_radio_control_impl::rx_band::RX_BAND_INVALID: + return "INVALID"; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +const char* tx_band_to_log(rhodium_radio_control_impl::tx_band tx_band) +{ + switch (tx_band) { + case rhodium_radio_control_impl::tx_band::TX_BAND_0: + return "0"; + case rhodium_radio_control_impl::tx_band::TX_BAND_1: + return "1"; + case rhodium_radio_control_impl::tx_band::TX_BAND_2: + return "2"; + case rhodium_radio_control_impl::tx_band::TX_BAND_3: + return "3"; + case rhodium_radio_control_impl::tx_band::TX_BAND_4: + return "4"; + case rhodium_radio_control_impl::tx_band::TX_BAND_5: + return "5"; + case rhodium_radio_control_impl::tx_band::TX_BAND_6: + return "6"; + case rhodium_radio_control_impl::tx_band::TX_BAND_7: + return "7"; + case rhodium_radio_control_impl::tx_band::TX_BAND_INVALID: + return "INVALID"; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} +} // namespace + +void rhodium_radio_control_impl::_update_rx_freq_switches(const double freq) +{ + RFNOC_LOG_TRACE("Update all RX freq related switches. f=" << freq << " Hz"); + const auto band = _map_freq_to_rx_band(freq); + RFNOC_LOG_TRACE("Selected band " << rx_band_to_log(band)); + + // select values for lowband/highband switches + const bool is_lowband = (band == rx_band::RX_BAND_0); + auto rx_sw2_sw7 = is_lowband ? rhodium_cpld_ctrl::RX_SW2_SW7_LOWBANDFILTERBANK + : rhodium_cpld_ctrl::RX_SW2_SW7_HIGHBANDFILTERBANK; + auto rx_hb_lb_sel = is_lowband ? rhodium_cpld_ctrl::RX_HB_LB_SEL_LOWBAND + : rhodium_cpld_ctrl::RX_HB_LB_SEL_HIGHBAND; + + // select values for filter bank switches + rhodium_cpld_ctrl::rx_sw3_t rx_sw3; + rhodium_cpld_ctrl::rx_sw4_sw5_t rx_sw4_sw5; + rhodium_cpld_ctrl::rx_sw6_t rx_sw6; + switch (band) { + case rx_band::RX_BAND_0: + // Low band doesn't use the filter banks, use configuration for band 1 + case rx_band::RX_BAND_1: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; + break; + case rx_band::RX_BAND_2: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0760X1100MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; + break; + case rx_band::RX_BAND_3: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1100X1410MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; + break; + case rx_band::RX_BAND_4: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1410X2050MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; + break; + case rx_band::RX_BAND_5: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER2050X3000MHZ; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER2050X3000MHZ; + break; + case rx_band::RX_BAND_6: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER3000X4500MHZ; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER3000X4500MHZ; + break; + case rx_band::RX_BAND_7: + rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER4500X6000MHZ; + rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; + rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER4500X6000MHZ; + break; + case rx_band::RX_BAND_INVALID: + throw uhd::runtime_error( + str(boost::format("Cannot map RX frequency to band: %f") % freq)); + default: + UHD_THROW_INVALID_CODE_PATH(); + } + + // commit settings to cpld + _cpld->set_rx_switches(rx_sw2_sw7, rx_sw3, rx_sw4_sw5, rx_sw6, rx_hb_lb_sel); +} + +void rhodium_radio_control_impl::_update_tx_freq_switches(const double freq) +{ + RFNOC_LOG_TRACE("Update all TX freq related switches. f=" << freq << " Hz"); + const auto band = _map_freq_to_tx_band(freq); + RFNOC_LOG_TRACE("Selected band " << tx_band_to_log(band)); + + // select values for lowband/highband switches + const bool is_lowband = (band == tx_band::TX_BAND_0); + auto tx_hb_lb_sel = is_lowband ? rhodium_cpld_ctrl::TX_HB_LB_SEL_LOWBAND + : rhodium_cpld_ctrl::TX_HB_LB_SEL_HIGHBAND; + + // select values for filter bank switches + rhodium_cpld_ctrl::tx_sw2_t tx_sw2; + rhodium_cpld_ctrl::tx_sw3_sw4_t tx_sw3_sw4; + rhodium_cpld_ctrl::tx_sw5_t tx_sw5; + switch (band) { + case tx_band::TX_BAND_0: + // Low band doesn't use the filter banks, use configuration for band 1 + case tx_band::TX_BAND_1: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; + break; + case tx_band::TX_BAND_2: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1000MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; + break; + case tx_band::TX_BAND_3: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1350MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; + break; + case tx_band::TX_BAND_4: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1900MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; + break; + case tx_band::TX_BAND_5: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP3000MHZ; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP3000MHZ; + break; + case tx_band::TX_BAND_6: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP4100MHZ; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP4100MHZ; + break; + case tx_band::TX_BAND_7: + tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP6000MHZ; + tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; + tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP6000MHZ; + break; + case tx_band::TX_BAND_INVALID: + throw uhd::runtime_error( + str(boost::format("Cannot map TX frequency to band: %f") % freq)); + default: + UHD_THROW_INVALID_CODE_PATH(); + } + + // commit settings to cpld + _cpld->set_tx_switches(tx_sw2, tx_sw3_sw4, tx_sw5, tx_hb_lb_sel); +} + +void rhodium_radio_control_impl::_update_rx_input_switches(const std::string& input) +{ + RFNOC_LOG_TRACE("Update all RX input related switches. input=" << input); + const rhodium_cpld_ctrl::cal_iso_sw_t cal_iso = + (input == "CAL") ? rhodium_cpld_ctrl::CAL_ISO_CALLOOPBACK + : rhodium_cpld_ctrl::CAL_ISO_ISOLATION; + const rhodium_cpld_ctrl::rx_sw1_t sw1 = [input] { + if (input == "TX/RX") { + return rhodium_cpld_ctrl::RX_SW1_FROMTXRXINPUT; + } else if (input == "RX2") { + return rhodium_cpld_ctrl::RX_SW1_FROMRX2INPUT; + } else if (input == "CAL") { + return rhodium_cpld_ctrl::RX_SW1_FROMCALLOOPBACK; + } else if (input == "TERM") { + return rhodium_cpld_ctrl::RX_SW1_ISOLATION; + } else { + throw uhd::runtime_error( + "Invalid antenna in _update_rx_input_switches: " + input); + } + }(); + + RFNOC_LOG_TRACE("Selected switch values:" + " sw1=" + << sw1 << " cal_iso=" << cal_iso); + _cpld->set_rx_input_switches(sw1, cal_iso); +} + +void rhodium_radio_control_impl::_update_tx_output_switches(const std::string& output) +{ + RFNOC_LOG_TRACE("Update all TX output related switches. output=" << output); + rhodium_cpld_ctrl::tx_sw1_t sw1; + + if (output == "TX/RX") { + // SW1 needs to select low/high band + if (_is_tx_lowband(get_tx_frequency(0))) { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOLOWBAND; + } else { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOSWITCH2; + } + } else if (output == "CAL") { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOCALLOOPBACK; + } else if (output == "TERM") { + sw1 = rhodium_cpld_ctrl::TX_SW1_ISOLATION; + } else { + throw uhd::runtime_error( + "Invalid antenna in _update_tx_output_switches: " + output); + } + + RFNOC_LOG_TRACE("Selected switch values: sw1=" << sw1); + _cpld->set_tx_output_switches(sw1); +} diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_control_init.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_init.cpp new file mode 100644 index 000000000..d6b7afd09 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_init.cpp @@ -0,0 +1,611 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_constants.hpp" +#include "rhodium_radio_control.hpp" +#include <uhd/transport/chdr.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/rfnoc/reg_iface_adapter.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> +#include <uhdlib/usrp/cores/spi_core_3000.hpp> +#include <string> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { +enum slave_select_t { + SEN_CPLD = 8, + SEN_TX_LO = 1, + SEN_RX_LO = 2, + SEN_LO_DIST = 4 /* Unused */ +}; + +constexpr double RHODIUM_DEFAULT_FREQ = 2.5e9; // Hz +// An invalid default index ensures that set gain will apply settings +// the first time it is called +constexpr double RHODIUM_DEFAULT_INVALID_GAIN = -1; // gain index +constexpr double RHODIUM_DEFAULT_GAIN = 0; // gain index +constexpr double RHODIUM_DEFAULT_LO_GAIN = 30; // gain index +constexpr char RHODIUM_DEFAULT_RX_ANTENNA[] = "RX2"; +constexpr char RHODIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; +constexpr auto RHODIUM_DEFAULT_MASH_ORDER = lmx2592_iface::mash_order_t::THIRD; + +//! Returns the SPI config used by the CPLD +spi_config_t _get_cpld_spi_config() +{ + spi_config_t spi_config; + spi_config.use_custom_divider = true; + spi_config.divider = 10; + spi_config.mosi_edge = spi_config_t::EDGE_RISE; + spi_config.miso_edge = spi_config_t::EDGE_FALL; + + return spi_config; +} + +//! Returns the SPI config used by the TX LO +spi_config_t _get_tx_lo_spi_config() +{ + spi_config_t spi_config; + spi_config.use_custom_divider = true; + spi_config.divider = 10; + spi_config.mosi_edge = spi_config_t::EDGE_RISE; + spi_config.miso_edge = spi_config_t::EDGE_FALL; + + return spi_config; +} + +//! Returns the SPI config used by the RX LO +spi_config_t _get_rx_lo_spi_config() +{ + spi_config_t spi_config; + spi_config.use_custom_divider = true; + spi_config.divider = 10; + spi_config.mosi_edge = spi_config_t::EDGE_RISE; + spi_config.miso_edge = spi_config_t::EDGE_FALL; + + return spi_config; +} + +std::function<void(uint32_t)> _generate_write_spi( + uhd::spi_iface::sptr spi, slave_select_t slave, spi_config_t config) +{ + return [spi, slave, config](const uint32_t transaction) { + spi->write_spi(slave, config, transaction, 24); + }; +} + +std::function<uint32_t(uint32_t)> _generate_read_spi( + uhd::spi_iface::sptr spi, slave_select_t slave, spi_config_t config) +{ + return [spi, slave, config](const uint32_t transaction) { + return spi->read_spi(slave, config, transaction, 24); + }; +} +} // namespace + +void rhodium_radio_control_impl::_init_defaults() +{ + RFNOC_LOG_TRACE("Initializing defaults..."); + const size_t num_rx_chans = get_num_output_ports(); + const size_t num_tx_chans = get_num_input_ports(); + UHD_ASSERT_THROW(num_tx_chans == RHODIUM_NUM_CHANS); + UHD_ASSERT_THROW(num_rx_chans == RHODIUM_NUM_CHANS); + + for (size_t chan = 0; chan < num_rx_chans; chan++) { + radio_control_impl::set_rx_frequency(RHODIUM_DEFAULT_FREQ, chan); + radio_control_impl::set_rx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); + radio_control_impl::set_rx_antenna(RHODIUM_DEFAULT_RX_ANTENNA, chan); + radio_control_impl::set_rx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); + } + + for (size_t chan = 0; chan < num_tx_chans; chan++) { + radio_control_impl::set_tx_frequency(RHODIUM_DEFAULT_FREQ, chan); + radio_control_impl::set_tx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); + radio_control_impl::set_tx_antenna(RHODIUM_DEFAULT_TX_ANTENNA, chan); + radio_control_impl::set_tx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); + } + + register_property(&_spur_dodging_mode); + register_property(&_spur_dodging_threshold); + register_property(&_highband_spur_reduction_mode); + + // Update configurable block arguments from the device arguments provided + const auto block_args = get_block_args(); + if (block_args.has_key(SPUR_DODGING_PROP_NAME)) { + _spur_dodging_mode.set(block_args.get(SPUR_DODGING_PROP_NAME)); + } + if (block_args.has_key(SPUR_DODGING_THRESHOLD_PROP_NAME)) { + _spur_dodging_threshold.set(block_args.cast<double>( + SPUR_DODGING_THRESHOLD_PROP_NAME, RHODIUM_DEFAULT_SPUR_DOGING_THRESHOLD)); + } + if (block_args.has_key(HIGHBAND_SPUR_REDUCTION_PROP_NAME)) { + _highband_spur_reduction_mode.set( + block_args.get(HIGHBAND_SPUR_REDUCTION_PROP_NAME)); + } +} + +void rhodium_radio_control_impl::_init_peripherals() +{ + RFNOC_LOG_TRACE("Initializing SPI core..."); + _spi = spi_core_3000::make( + [this](uint32_t addr, uint32_t data) { + regs().poke32(addr, data, get_command_time(0)); + }, + [this](uint32_t addr) { return regs().peek32(addr, get_command_time(0)); }, + regmap::REG_SPI_W, + 8, + regmap::REG_SPI_R); + _wb_iface = RFNOC_MAKE_WB_IFACE(0, 0); + + RFNOC_LOG_TRACE("Initializing CPLD..."); + _cpld = std::make_shared<rhodium_cpld_ctrl>( + _generate_write_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config()), + _generate_read_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config())); + + RFNOC_LOG_TRACE("Initializing TX frontend DSP core...") + _tx_fe_core = tx_frontend_core_200::make(_wb_iface, n320_regs::SR_TX_FE_BASE); + _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); + _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); + _tx_fe_core->populate_subtree(get_tree()->subtree(FE_PATH / "tx_fe_corrections" / 0)); + + RFNOC_LOG_TRACE("Initializing RX frontend DSP core...") + _rx_fe_core = rx_frontend_core_3000::make(_wb_iface, n320_regs::SR_TX_FE_BASE); + _rx_fe_core->set_adc_rate(_master_clock_rate); + _rx_fe_core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); + _rx_fe_core->set_dc_offset_auto(rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); + _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); + _rx_fe_core->populate_subtree(get_tree()->subtree(FE_PATH / "rx_fe_corrections" / 0)); + + RFNOC_LOG_TRACE("Writing initial gain values..."); + set_tx_gain(RHODIUM_DEFAULT_GAIN, 0); + set_tx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); + set_rx_gain(RHODIUM_DEFAULT_GAIN, 0); + set_rx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); + + RFNOC_LOG_TRACE("Initializing TX LO..."); + _tx_lo = lmx2592_iface::make( + _generate_write_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config()), + _generate_read_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config())); + + RFNOC_LOG_TRACE("Writing initial TX LO state..."); + _tx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); + _tx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); + + RFNOC_LOG_TRACE("Initializing RX LO..."); + _rx_lo = lmx2592_iface::make( + _generate_write_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config()), + _generate_read_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config())); + + RFNOC_LOG_TRACE("Writing initial RX LO state..."); + _rx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); + _rx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); + + RFNOC_LOG_TRACE("Initializing GPIOs..."); + // DB GPIOs + _gpio = usrp::gpio_atr::gpio_atr_3000::make(_wb_iface, + n320_regs::SR_DB_GPIO, + n320_regs::RB_DB_GPIO, + n320_regs::PERIPH_REG_OFFSET); + _gpio->set_atr_mode(usrp::gpio_atr::MODE_ATR, // Enable ATR mode for Rhodium bits + RHODIUM_GPIO_MASK); + _gpio->set_atr_mode(usrp::gpio_atr::MODE_GPIO, // Disable ATR mode for unused bits + ~RHODIUM_GPIO_MASK); + _gpio->set_gpio_ddr(usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs + usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _fp_gpio = gpio_atr::gpio_atr_3000::make(_wb_iface, + n320_regs::SR_FP_GPIO, + n320_regs::RB_FP_GPIO, + n320_regs::PERIPH_REG_OFFSET); + + RFNOC_LOG_TRACE("Set initial ATR values..."); + _update_atr(RHODIUM_DEFAULT_TX_ANTENNA, TX_DIRECTION); + _update_atr(RHODIUM_DEFAULT_RX_ANTENNA, RX_DIRECTION); + + // Updating the TX frequency path may include an update to SW10, which is + // GPIO controlled, so this must follow CPLD and GPIO initialization + RFNOC_LOG_TRACE("Writing initial switch values..."); + _update_tx_freq_switches(RHODIUM_DEFAULT_FREQ); + _update_rx_freq_switches(RHODIUM_DEFAULT_FREQ); + + // Antenna setting requires both CPLD and GPIO control + RFNOC_LOG_TRACE("Setting initial antenna settings"); + _update_tx_output_switches(RHODIUM_DEFAULT_TX_ANTENNA); + _update_rx_input_switches(RHODIUM_DEFAULT_RX_ANTENNA); + + RFNOC_LOG_TRACE("Checking for existence of LO Distribution board"); + _lo_dist_present = + _rpcc->request_with_token<bool>(_rpc_prefix + "is_lo_dist_present"); + RFNOC_LOG_DEBUG( + "LO distribution board is" << (_lo_dist_present ? "" : " NOT") << " present"); + + RFNOC_LOG_TRACE("Reading EEPROM content..."); + const size_t db_idx = get_block_id().get_block_count(); + _db_eeprom = this->_rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", db_idx); +} + +// Reminder: The property must not own any properties, it can only interact with +// the API of this block! +void rhodium_radio_control_impl::_init_frontend_subtree(uhd::property_tree::sptr subtree) +{ + const fs_path tx_fe_path = fs_path("tx_frontends") / 0; + const fs_path rx_fe_path = fs_path("rx_frontends") / 0; + RFNOC_LOG_TRACE("Adding non-RFNoC block properties for channel 0" + << " to prop tree path " << tx_fe_path << " and " << rx_fe_path); + // TX Standard attributes + subtree->create<std::string>(tx_fe_path / "name").set(RHODIUM_FE_NAME); + subtree->create<std::string>(tx_fe_path / "connection") + .add_coerced_subscriber( + [this](const std::string& conn) { this->_set_tx_fe_connection(conn); }) + .set_publisher([this]() { return this->_get_tx_fe_connection(); }); + subtree->create<device_addr_t>(tx_fe_path / "tune_args") + .set(device_addr_t()) + .add_coerced_subscriber( + [this](const device_addr_t& args) { set_tx_tune_args(args, 0); }) + .set_publisher([this]() { return _tune_args.at(uhd::TX_DIRECTION); }); + // RX Standard attributes + subtree->create<std::string>(rx_fe_path / "name").set(RHODIUM_FE_NAME); + subtree->create<std::string>(rx_fe_path / "connection") + .add_coerced_subscriber( + [this](const std::string& conn) { this->_set_rx_fe_connection(conn); }) + .set_publisher([this]() { return this->_get_rx_fe_connection(); }); + subtree->create<device_addr_t>(rx_fe_path / "tune_args") + .set(device_addr_t()) + .add_coerced_subscriber( + [this](const device_addr_t& args) { set_rx_tune_args(args, 0); }) + .set_publisher([this]() { return _tune_args.at(uhd::RX_DIRECTION); }); + ; + // TX Antenna + subtree->create<std::string>(tx_fe_path / "antenna" / "value") + .add_coerced_subscriber( + [this](const std::string& ant) { this->set_tx_antenna(ant, 0); }) + .set_publisher([this]() { return this->get_tx_antenna(0); }); + subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }) + .set_publisher([this]() { return get_tx_antennas(0); }); + // RX Antenna + subtree->create<std::string>(rx_fe_path / "antenna" / "value") + .add_coerced_subscriber( + [this](const std::string& ant) { this->set_rx_antenna(ant, 0); }) + .set_publisher([this]() { return this->get_rx_antenna(0); }); + subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }) + .set_publisher([this]() { return get_rx_antennas(0); }); + // TX frequency + subtree->create<double>(tx_fe_path / "freq" / "value") + .set_coercer( + [this](const double freq) { return this->set_tx_frequency(freq, 0); }) + .set_publisher([this]() { return this->get_tx_frequency(0); }); + subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }) + .set_publisher([this]() { return get_tx_frequency_range(0); }); + // RX frequency + subtree->create<double>(rx_fe_path / "freq" / "value") + .set_coercer( + [this](const double freq) { return this->set_rx_frequency(freq, 0); }) + .set_publisher([this]() { return this->get_rx_frequency(0); }); + subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }) + .set_publisher([this]() { return get_rx_frequency_range(0); }); + // TX bandwidth + subtree->create<double>(tx_fe_path / "bandwidth" / "value") + .set_coercer([this](const double bw) { return this->set_tx_bandwidth(bw, 0); }) + .set_publisher([this]() { return this->get_tx_bandwidth(0); }); + subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }) + .set_publisher([this]() { return get_tx_bandwidth_range(0); }); + // RX bandwidth + subtree->create<double>(rx_fe_path / "bandwidth" / "value") + .set_coercer([this](const double bw) { return this->set_rx_bandwidth(bw, 0); }) + .set_publisher([this]() { return this->get_rx_bandwidth(0); }); + subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update bandwidth range!"); + }) + .set_publisher([this]() { return get_rx_bandwidth_range(0); }); + // TX gains + subtree->create<double>(tx_fe_path / "gains" / "all" / "value") + .set_coercer([this](const double gain) { return this->set_tx_gain(gain, 0); }) + .set_publisher([this]() { return radio_control_impl::get_tx_gain(0); }); + subtree->create<meta_range_t>(tx_fe_path / "gains" / "all" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this]() { return get_tx_gain_range(0); }); + // RX gains + subtree->create<double>(rx_fe_path / "gains" / "all" / "value") + .set_coercer([this](const double gain) { return this->set_rx_gain(gain, 0); }) + .set_publisher([this]() { return radio_control_impl::get_rx_gain(0); }); + subtree->create<meta_range_t>(rx_fe_path / "gains" / "all" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this]() { return get_rx_gain_range(0); }); + + // LO Specific + // RX LO + // RX LO1 Frequency + subtree->create<double>(rx_fe_path / "los" / RHODIUM_LO1 / "freq/value") + .set_publisher([this]() { return this->get_rx_lo_freq(RHODIUM_LO1, 0); }) + .set_coercer([this](const double freq) { + return this->set_rx_lo_freq(freq, RHODIUM_LO1, 0); + }); + subtree->create<meta_range_t>(rx_fe_path / "los" / RHODIUM_LO1 / "freq/range") + .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO1, 0); }); + // RX LO1 Source + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / RHODIUM_LO1 / "source/options") + .set_publisher([this]() { return this->get_rx_lo_sources(RHODIUM_LO1, 0); }); + subtree->create<std::string>(rx_fe_path / "los" / RHODIUM_LO1 / "source/value") + .add_coerced_subscriber([this](const std::string& src) { + this->set_rx_lo_source(src, RHODIUM_LO1, 0); + }) + .set_publisher([this]() { return this->get_rx_lo_source(RHODIUM_LO1, 0); }); + // RX LO1 Export + subtree->create<bool>(rx_fe_path / "los" / RHODIUM_LO1 / "export") + .add_coerced_subscriber([this](bool enabled) { + this->set_rx_lo_export_enabled(enabled, RHODIUM_LO1, 0); + }); + // RX LO1 Gain + subtree + ->create<double>( + rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "value") + .set_publisher([this]() { return this->get_rx_lo_gain(RHODIUM_LO1, 0); }) + .set_coercer([this](const double gain) { + return this->set_rx_lo_gain(gain, RHODIUM_LO1, 0); + }); + subtree + ->create<meta_range_t>( + rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "range") + .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_gain_range(); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update LO gain range!"); + }); + // RX LO1 Output Power + subtree + ->create<double>( + rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "value") + .set_publisher([this]() { return this->get_rx_lo_power(RHODIUM_LO1, 0); }) + .set_coercer([this](const double gain) { + return this->set_rx_lo_power(gain, RHODIUM_LO1, 0); + }); + subtree + ->create<meta_range_t>( + rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "range") + .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_power_range(); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update LO output power range!"); + }); + // RX LO2 Frequency + subtree->create<double>(rx_fe_path / "los" / RHODIUM_LO2 / "freq/value") + .set_publisher([this]() { return this->get_rx_lo_freq(RHODIUM_LO2, 0); }) + .set_coercer( + [this](double freq) { return this->set_rx_lo_freq(freq, RHODIUM_LO2, 0); }); + subtree->create<meta_range_t>(rx_fe_path / "los" / RHODIUM_LO2 / "freq/range") + .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO2, 0); }); + // RX LO2 Source + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / RHODIUM_LO2 / "source/options") + .set_publisher([this]() { return this->get_rx_lo_sources(RHODIUM_LO2, 0); }); + subtree->create<std::string>(rx_fe_path / "los" / RHODIUM_LO2 / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_rx_lo_source(src, RHODIUM_LO2, 0); }) + .set_publisher([this]() { return this->get_rx_lo_source(RHODIUM_LO2, 0); }); + // RX LO2 Export + subtree->create<bool>(rx_fe_path / "los" / RHODIUM_LO2 / "export") + .add_coerced_subscriber([this](bool enabled) { + this->set_rx_lo_export_enabled(enabled, RHODIUM_LO2, 0); + }); + // RX ALL LOs + subtree->create<std::string>(rx_fe_path / "los" / ALL_LOS / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_rx_lo_source(src, ALL_LOS, 0); }) + .set_publisher([this]() { return this->get_rx_lo_source(ALL_LOS, 0); }); + subtree + ->create<std::vector<std::string>>( + rx_fe_path / "los" / ALL_LOS / "source/options") + .set_publisher([this]() { return this->get_rx_lo_sources(ALL_LOS, 0); }); + subtree->create<bool>(rx_fe_path / "los" / ALL_LOS / "export") + .add_coerced_subscriber( + [this](bool enabled) { this->set_rx_lo_export_enabled(enabled, ALL_LOS, 0); }) + .set_publisher([this]() { return this->get_rx_lo_export_enabled(ALL_LOS, 0); }); + // TX LO + // TX LO1 Frequency + subtree->create<double>(tx_fe_path / "los" / RHODIUM_LO1 / "freq/value ") + .set_publisher([this]() { return this->get_tx_lo_freq(RHODIUM_LO1, 0); }) + .set_coercer( + [this](double freq) { return this->set_tx_lo_freq(freq, RHODIUM_LO1, 0); }); + subtree->create<meta_range_t>(tx_fe_path / "los" / RHODIUM_LO1 / "freq/range") + .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO1, 0); }); + // TX LO1 Source + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / RHODIUM_LO1 / "source/options") + .set_publisher([this]() { return this->get_tx_lo_sources(RHODIUM_LO1, 0); }); + subtree->create<std::string>(tx_fe_path / "los" / RHODIUM_LO1 / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_tx_lo_source(src, RHODIUM_LO1, 0); }) + .set_publisher([this]() { return this->get_tx_lo_source(RHODIUM_LO1, 0); }); + // TX LO1 Export + subtree->create<bool>(tx_fe_path / "los" / RHODIUM_LO1 / "export") + .add_coerced_subscriber([this](bool enabled) { + this->set_tx_lo_export_enabled(enabled, RHODIUM_LO1, 0); + }); + // TX LO1 Gain + subtree + ->create<double>( + tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "value") + .set_publisher([this]() { return this->get_tx_lo_gain(RHODIUM_LO1, 0); }) + .set_coercer([this](const double gain) { + return this->set_tx_lo_gain(gain, RHODIUM_LO1, 0); + }); + subtree + ->create<meta_range_t>( + tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "range") + .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_gain_range(); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update LO gain range!"); + }); + // TX LO1 Output Power + subtree + ->create<double>( + tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "value") + .set_publisher([this]() { return this->get_tx_lo_power(RHODIUM_LO1, 0); }) + .set_coercer([this](const double gain) { + return this->set_tx_lo_power(gain, RHODIUM_LO1, 0); + }); + subtree + ->create<meta_range_t>( + tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "range") + .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_power_range(); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update LO output power range!"); + }); + // TX LO2 Frequency + subtree->create<double>(tx_fe_path / "los" / RHODIUM_LO2 / "freq/value") + .set_publisher([this]() { return this->get_tx_lo_freq(RHODIUM_LO2, 0); }) + .set_coercer( + [this](double freq) { return this->set_tx_lo_freq(freq, RHODIUM_LO2, 0); }); + subtree->create<meta_range_t>(tx_fe_path / "los" / RHODIUM_LO2 / "freq/range") + .set_publisher([this]() { return this->get_tx_lo_freq_range(RHODIUM_LO2, 0); }); + // TX LO2 Source + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / RHODIUM_LO2 / "source/options") + .set_publisher([this]() { return this->get_tx_lo_sources(RHODIUM_LO2, 0); }); + subtree->create<std::string>(tx_fe_path / "los" / RHODIUM_LO2 / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_tx_lo_source(src, RHODIUM_LO2, 0); }) + .set_publisher([this]() { return this->get_tx_lo_source(RHODIUM_LO2, 0); }); + // TX LO2 Export + subtree->create<bool>(tx_fe_path / "los" / RHODIUM_LO2 / "export") + .add_coerced_subscriber([this](bool enabled) { + this->set_tx_lo_export_enabled(enabled, RHODIUM_LO2, 0); + }); + // TX ALL LOs + subtree->create<std::string>(tx_fe_path / "los" / ALL_LOS / "source/value") + .add_coerced_subscriber( + [this](std::string src) { this->set_tx_lo_source(src, ALL_LOS, 0); }) + .set_publisher([this]() { return this->get_tx_lo_source(ALL_LOS, 0); }); + subtree + ->create<std::vector<std::string>>( + tx_fe_path / "los" / ALL_LOS / "source/options") + .set_publisher([this]() { return this->get_tx_lo_sources(ALL_LOS, 0); }); + subtree->create<bool>(tx_fe_path / "los" / ALL_LOS / "export") + .add_coerced_subscriber( + [this](bool enabled) { this->set_tx_lo_export_enabled(enabled, ALL_LOS, 0); }) + .set_publisher([this]() { return this->get_tx_lo_export_enabled(ALL_LOS, 0); }); + + // LO Distribution Output Ports + if (_lo_dist_present) { + for (const auto& port : LO_OUTPUT_PORT_NAMES) { + subtree + ->create<bool>(tx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port + / "export") + .add_coerced_subscriber([this, port](bool enabled) { + this->set_tx_lo_output_enabled(enabled, port, 0); + }) + .set_publisher( + [this, port]() { return this->get_tx_lo_output_enabled(port, 0); }); + subtree + ->create<bool>(rx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port + / "export") + .add_coerced_subscriber([this, port](bool enabled) { + this->set_rx_lo_output_enabled(enabled, port, 0); + }) + .set_publisher( + [this, port]() { return this->get_rx_lo_output_enabled(port, 0); }); + } + } + + // Sensors + auto rx_sensor_names = get_rx_sensor_names(0); + for (const auto& sensor_name : rx_sensor_names) { + RFNOC_LOG_TRACE("Adding RX sensor " << sensor_name); + get_tree() + ->create<sensor_value_t>(rx_fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher( + [this, sensor_name]() { return get_rx_sensor(sensor_name, 0); }); + } + auto tx_sensor_names = get_tx_sensor_names(0); + for (const auto& sensor_name : tx_sensor_names) { + RFNOC_LOG_TRACE("Adding TX sensor " << sensor_name); + get_tree() + ->create<sensor_value_t>(tx_fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher( + [this, sensor_name]() { return get_tx_sensor(sensor_name, 0); }); + } +} + +void rhodium_radio_control_impl::_init_prop_tree() +{ + this->_init_frontend_subtree(get_tree()->subtree(DB_PATH)); + get_tree()->create<std::string>(fs_path("rx_codecs") / "name").set("ad9695-625"); + get_tree()->create<std::string>(fs_path("tx_codecs") / "name").set("dac37j82"); +} + +void rhodium_radio_control_impl::_init_mpm() +{ + auto block_args = get_block_args(); + 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; + } + RFNOC_LOG_INFO("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( + std::string("Master clock rate mismatch. Device returns ") + + std::to_string(_master_clock_rate) + + " MHz, " + "but should have been " + + std::to_string( + block_args.cast<double>("master_clock_rate", _master_clock_rate)) + + " MHz."); + } + RFNOC_LOG_DEBUG("Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); + set_tick_rate(_master_clock_rate); + _n3xx_timekeeper->update_tick_rate(_master_clock_rate); + radio_control_impl::set_rate(_master_clock_rate); + + // Unlike N310, N320 does not have any MPM sensors. +} diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_control_lo.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_lo.cpp new file mode 100644 index 000000000..717a1c94f --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_control_lo.cpp @@ -0,0 +1,713 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_constants.hpp" +#include "rhodium_radio_control.hpp" +#include <uhd/exception.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { +constexpr int NUM_THRESHOLDS = 13; +constexpr std::array<double, NUM_THRESHOLDS> FREQ_THRESHOLDS = { + 0.45e9, 0.5e9, 1e9, 1.5e9, 2e9, 2.5e9, 3e9, 3.55e9, 4e9, 4.5e9, 5e9, 5.5e9, 6e9}; +constexpr std::array<int, NUM_THRESHOLDS> LMX_GAIN_VALUES = { + 18, 18, 17, 17, 17, 16, 12, 11, 11, 11, 10, 13, 18}; +const std::array<int, NUM_THRESHOLDS> DSA_RX_GAIN_VALUES = { + 19, 19, 21, 21, 20, 20, 22, 21, 20, 22, 22, 24, 26}; +const std::array<int, NUM_THRESHOLDS> DSA_TX_GAIN_VALUES = { + 19, 19, 21, 21, 20, 20, 22, 21, 22, 24, 24, 26, 28}; + +// Describes the lowband LO in terms of the master clock rate +const std::map<double, double> MCR_TO_LOWBAND_IF = { + {200e6, 1200e6}, + {245.76e6, 1228.8e6}, + {250e6, 1500e6}, +}; + +// validation helpers + +std::vector<std::string> _get_lo_names() +{ + return {RHODIUM_LO1, RHODIUM_LO2}; +} + +void _validate_lo_name(const std::string& name, const std::string& function_name) +{ + if (!uhd::has(_get_lo_names(), name) and name != radio_control::ALL_LOS) { + throw uhd::value_error( + str(boost::format("%s was called with an invalid LO name: %s") % function_name + % name)); + } +} + +// object agnostic helpers +std::vector<std::string> _get_lo_sources(const std::string& name) +{ + if (name == RHODIUM_LO1 or name == radio_control::ALL_LOS) { + return {"internal", "external"}; + } else { + return {"internal"}; + } +} +} // namespace + +/****************************************************************************** + * Property Getters + *****************************************************************************/ + +std::vector<std::string> rhodium_radio_control_impl::get_tx_lo_names( + const size_t chan) const +{ + RFNOC_LOG_TRACE("get_tx_lo_names(chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + return _get_lo_names(); +} + +std::vector<std::string> rhodium_radio_control_impl::get_rx_lo_names( + const size_t chan) const +{ + RFNOC_LOG_TRACE("get_rx_lo_names(chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + return _get_lo_names(); +} + +std::vector<std::string> rhodium_radio_control_impl::get_tx_lo_sources( + const std::string& name, const size_t chan) const +{ + RFNOC_LOG_TRACE("get_tx_lo_sources(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_sources"); + + return _get_lo_sources(name); +} + +std::vector<std::string> rhodium_radio_control_impl::get_rx_lo_sources( + const std::string& name, const size_t chan) const +{ + RFNOC_LOG_TRACE("get_rx_lo_sources(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_sources"); + + return _get_lo_sources(name); +} + +freq_range_t rhodium_radio_control_impl::_get_lo_freq_range(const std::string& name) const +{ + if (name == RHODIUM_LO1) { + return freq_range_t{RHODIUM_LO1_MIN_FREQ, RHODIUM_LO1_MAX_FREQ}; + } else if (name == RHODIUM_LO2) { + // The Lowband LO is a fixed frequency + return freq_range_t{_get_lowband_lo_freq(), _get_lowband_lo_freq()}; + } else { + throw uhd::runtime_error( + "LO frequency range must be retrieved for each stage individually"); + } +} + +freq_range_t rhodium_radio_control_impl::get_tx_lo_freq_range( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_freq_range(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_freq_range"); + + return _get_lo_freq_range(name); +} + +freq_range_t rhodium_radio_control_impl::get_rx_lo_freq_range( + const std::string& name, const size_t chan) const +{ + RFNOC_LOG_TRACE("get_rx_lo_freq_range(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_freq_range"); + + return _get_lo_freq_range(name); +} + +/****************************************************************************** + * Frequency Control + *****************************************************************************/ + +double rhodium_radio_control_impl::set_tx_lo_freq( + const double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_tx_lo_freq(freq=" << freq << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_freq"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO cannot be tuned"); + return _get_lowband_lo_freq(); + } + + const auto sd_enabled = _get_spur_dodging_enabled(TX_DIRECTION); + const auto sd_threshold = _get_spur_dodging_threshold(TX_DIRECTION); + + _tx_lo_freq = _tx_lo->set_frequency(freq, sd_enabled, sd_threshold); + set_tx_lo_gain(_get_lo_dsa_setting(_tx_lo_freq, TX_DIRECTION), RHODIUM_LO1, chan); + set_tx_lo_power(_get_lo_power_setting(_tx_lo_freq), RHODIUM_LO1, chan); + _cpld->set_tx_lo_path(_tx_lo_freq); + + return _tx_lo_freq; +} + +double rhodium_radio_control_impl::set_rx_lo_freq( + const double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_rx_lo_freq(freq=" << freq << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_rx_lo_freq"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO cannot be tuned"); + return _get_lowband_lo_freq(); + } + + const auto sd_enabled = _get_spur_dodging_enabled(RX_DIRECTION); + const auto sd_threshold = _get_spur_dodging_threshold(RX_DIRECTION); + + _rx_lo_freq = _rx_lo->set_frequency(freq, sd_enabled, sd_threshold); + set_rx_lo_gain(_get_lo_dsa_setting(_rx_lo_freq, RX_DIRECTION), RHODIUM_LO1, chan); + set_rx_lo_power(_get_lo_power_setting(_rx_lo_freq), RHODIUM_LO1, chan); + _cpld->set_rx_lo_path(_rx_lo_freq); + + return _rx_lo_freq; +} + +double rhodium_radio_control_impl::get_tx_lo_freq( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_freq(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_freq"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO frequency must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _tx_lo_freq : _get_lowband_lo_freq(); +} + +double rhodium_radio_control_impl::get_rx_lo_freq( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_freq(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_freq"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO frequency must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _rx_lo_freq : _get_lowband_lo_freq(); +} + +/****************************************************************************** + * Source Control + *****************************************************************************/ + +void rhodium_radio_control_impl::set_tx_lo_source( + const std::string& src, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_tx_lo_source(src=" << src << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_source"); + + if (name == RHODIUM_LO2) { + if (src != "internal") { + throw uhd::value_error("The Lowband LO can only be set to internal"); + } + return; + } + + if (src == "internal") { + _tx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, true); + _cpld->set_tx_lo_source( + rhodium_cpld_ctrl::tx_lo_input_sel_t::TX_LO_INPUT_SEL_INTERNAL); + } else if (src == "external") { + _tx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, false); + _cpld->set_tx_lo_source( + rhodium_cpld_ctrl::tx_lo_input_sel_t::TX_LO_INPUT_SEL_EXTERNAL); + } else { + throw uhd::value_error( + str(boost::format("set_tx_lo_source was called with an invalid LO source: %s " + "Valid sources are [internal, external]") + % src)); + } + + const bool enable_corrections = not _is_tx_lowband(get_tx_frequency(0)) + and src == "internal"; + _update_corrections(get_tx_frequency(0), TX_DIRECTION, enable_corrections); + + _tx_lo_source = src; +} + +void rhodium_radio_control_impl::set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_rx_lo_source(src=" << src << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_source"); + + if (name == RHODIUM_LO2) { + if (src != "internal") { + throw uhd::value_error("The Lowband LO can only be set to internal"); + } + return; + } + + if (src == "internal") { + _rx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, true); + _cpld->set_rx_lo_source( + rhodium_cpld_ctrl::rx_lo_input_sel_t::RX_LO_INPUT_SEL_INTERNAL); + } else if (src == "external") { + _rx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, false); + _cpld->set_rx_lo_source( + rhodium_cpld_ctrl::rx_lo_input_sel_t::RX_LO_INPUT_SEL_EXTERNAL); + } else { + throw uhd::value_error( + str(boost::format("set_rx_lo_source was called with an invalid LO source: %s " + "Valid sources for LO1 are [internal, external]") + % src)); + } + + const bool enable_corrections = not _is_rx_lowband(get_rx_frequency(0)) + and src == "internal"; + _update_corrections(get_rx_frequency(0), RX_DIRECTION, enable_corrections); + + _rx_lo_source = src; +} + +const std::string rhodium_radio_control_impl::get_tx_lo_source( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_source(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_source"); + return (name == RHODIUM_LO1 or name == ALL_LOS) ? _tx_lo_source : "internal"; +} + +const std::string rhodium_radio_control_impl::get_rx_lo_source( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_source(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_source"); + return (name == RHODIUM_LO1 or name == ALL_LOS) ? _rx_lo_source : "internal"; +} + +/****************************************************************************** + * Export Control + *****************************************************************************/ + +void rhodium_radio_control_impl::_set_lo1_export_enabled( + const bool enabled, const direction_t dir) +{ + auto& _lo_ctrl = (dir == RX_DIRECTION) ? _rx_lo : _tx_lo; + _lo_ctrl->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_B, enabled); + if (_lo_dist_present) { + const auto direction = (dir == RX_DIRECTION) ? "RX" : "TX"; + _rpcc->notify_with_token(_rpc_prefix + "enable_lo_export", direction, enabled); + } +} + +void rhodium_radio_control_impl::set_tx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_export_enabled(enabled=" << enabled << ", name=" << name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_export_enabled"); + + if (name == RHODIUM_LO2) { + if (enabled) { + throw uhd::value_error("The lowband LO cannot be exported"); + } + return; + } + + _set_lo1_export_enabled(enabled, TX_DIRECTION); + _tx_lo_exported = enabled; +} + +void rhodium_radio_control_impl::set_rx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_export_enabled(enabled=" << enabled << ", name=" << name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_rx_lo_export_enabled"); + + if (name == RHODIUM_LO2) { + if (enabled) { + throw uhd::value_error("The lowband LO cannot be exported"); + } + return; + } + + _set_lo1_export_enabled(enabled, RX_DIRECTION); + _rx_lo_exported = enabled; +} + +bool rhodium_radio_control_impl::get_tx_lo_export_enabled( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_export_enabled(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_export_enabled"); + + return (name == RHODIUM_LO1 or name == ALL_LOS) ? _tx_lo_exported : false; +} + +bool rhodium_radio_control_impl::get_rx_lo_export_enabled( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_export_enabled(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_export_enabled"); + + return (name == RHODIUM_LO1 or name == ALL_LOS) ? _rx_lo_exported : false; +} + +/****************************************************************************** + * LO Distribution Control + *****************************************************************************/ + +void rhodium_radio_control_impl::_validate_output_port( + const std::string& port_name, const std::string& function_name) +{ + if (!_lo_dist_present) { + throw uhd::runtime_error( + str(boost::format( + "%s can only be called if the LO distribution board was detected") + % function_name)); + } + + if (!uhd::has(LO_OUTPUT_PORT_NAMES, port_name)) { + throw uhd::value_error( + str(boost::format("%s was called with an invalid LO output port: %s Valid " + "ports are [LO_OUT_0, LO_OUT_1, LO_OUT_2, LO_OUT_3]") + % function_name % port_name)); + } +} + +void rhodium_radio_control_impl::_set_lo_output_enabled( + const bool enabled, const std::string& port_name, const direction_t dir) +{ + auto direction = (dir == RX_DIRECTION) ? "RX" : "TX"; + auto name_iter = + std::find(LO_OUTPUT_PORT_NAMES.begin(), LO_OUTPUT_PORT_NAMES.end(), port_name); + auto index = std::distance(LO_OUTPUT_PORT_NAMES.begin(), name_iter); + + _rpcc->notify_with_token(_rpc_prefix + "enable_lo_output", direction, index, enabled); + auto out_enabled = (dir == RX_DIRECTION) ? _lo_dist_rx_out_enabled + : _lo_dist_tx_out_enabled; + out_enabled[index] = enabled; +} + +void rhodium_radio_control_impl::set_tx_lo_output_enabled( + const bool enabled, const std::string& port_name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_output_enabled(enabled=" << enabled + << ", port_name=" << port_name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_output_port(port_name, "set_tx_lo_output_enabled"); + + _set_lo_output_enabled(enabled, port_name, TX_DIRECTION); +} + +void rhodium_radio_control_impl::set_rx_lo_output_enabled( + const bool enabled, const std::string& port_name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_output_enabled(enabled=" << enabled + << ", port_name=" << port_name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_output_port(port_name, "set_rx_lo_output_enabled"); + + _set_lo_output_enabled(enabled, port_name, RX_DIRECTION); +} + +bool rhodium_radio_control_impl::_get_lo_output_enabled( + const std::string& port_name, const direction_t dir) +{ + auto name_iter = + std::find(LO_OUTPUT_PORT_NAMES.begin(), LO_OUTPUT_PORT_NAMES.end(), port_name); + auto index = std::distance(LO_OUTPUT_PORT_NAMES.begin(), name_iter); + + auto out_enabled = (dir == RX_DIRECTION) ? _lo_dist_rx_out_enabled + : _lo_dist_tx_out_enabled; + return out_enabled[index]; +} + +bool rhodium_radio_control_impl::get_tx_lo_output_enabled( + const std::string& port_name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "get_tx_lo_output_enabled(port_name=" << port_name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_output_port(port_name, "get_tx_lo_output_enabled"); + + return _get_lo_output_enabled(port_name, TX_DIRECTION); +} + +bool rhodium_radio_control_impl::get_rx_lo_output_enabled( + const std::string& port_name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "get_rx_lo_output_enabled(port_name=" << port_name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_output_port(port_name, "get_rx_lo_output_enabled"); + + return _get_lo_output_enabled(port_name, RX_DIRECTION); +} + +/****************************************************************************** + * Gain Control + *****************************************************************************/ + +double rhodium_radio_control_impl::set_tx_lo_gain( + double gain, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_tx_lo_gain(gain=" << gain << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_gain"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO gain must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO does not have configurable gain"); + return 0.0; + } + + auto index = _get_lo_gain_range().clip(gain); + + _cpld->set_lo_gain(index, TX_DIRECTION); + _lo_tx_gain = index; + return _lo_tx_gain; +} + +double rhodium_radio_control_impl::set_rx_lo_gain( + double gain, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE( + "set_rx_lo_gain(gain=" << gain << ", name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_rx_lo_gain"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO gain must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO does not have configurable gain"); + return 0.0; + } + + auto index = _get_lo_gain_range().clip(gain); + + _cpld->set_lo_gain(index, RX_DIRECTION); + _lo_rx_gain = index; + return _lo_rx_gain; +} + +double rhodium_radio_control_impl::get_tx_lo_gain( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_gain(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_gain"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO gain must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _lo_rx_gain : 0.0; +} + +double rhodium_radio_control_impl::get_rx_lo_gain( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_gain(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_gain"); + + if (name == ALL_LOS) { + throw uhd::runtime_error("LO gain must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _lo_tx_gain : 0.0; +} + +/****************************************************************************** + * Output Power Control + *****************************************************************************/ + +double rhodium_radio_control_impl::_set_lo1_power( + const double power, const direction_t dir) +{ + auto& _lo_ctrl = (dir == RX_DIRECTION) ? _rx_lo : _tx_lo; + auto coerced_power = static_cast<uint32_t>(_get_lo_power_range().clip(power, true)); + + _lo_ctrl->set_output_power(lmx2592_iface::RF_OUTPUT_A, coerced_power); + return coerced_power; +} + +double rhodium_radio_control_impl::set_tx_lo_power( + const double power, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_power(power=" << power << ", name=" << name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_tx_lo_power"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO output power must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO does not have configurable output power"); + return 0.0; + } + + _lo_tx_power = _set_lo1_power(power, TX_DIRECTION); + return _lo_tx_power; +} + +double rhodium_radio_control_impl::set_rx_lo_power( + const double power, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_power(power=" << power << ", name=" << name + << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "set_rx_lo_power"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO output power must be set for each stage individually"); + } + if (name == RHODIUM_LO2) { + RFNOC_LOG_WARNING("The Lowband LO does not have configurable output power"); + return 0.0; + } + + _lo_rx_power = _set_lo1_power(power, RX_DIRECTION); + return _lo_rx_power; +} + +double rhodium_radio_control_impl::get_tx_lo_power( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_power(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_tx_lo_power"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO output power must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _lo_tx_power : 0.0; +} + +double rhodium_radio_control_impl::get_rx_lo_power( + const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_power(name=" << name << ", chan=" << chan << ")"); + UHD_ASSERT_THROW(chan == 0); + _validate_lo_name(name, "get_rx_lo_power"); + + if (name == ALL_LOS) { + throw uhd::runtime_error( + "LO output power must be retrieved for each stage individually"); + } + + return (name == RHODIUM_LO1) ? _lo_rx_power : 0.0; +} + +/****************************************************************************** + * Helper Functions + *****************************************************************************/ + +double rhodium_radio_control_impl::_get_lowband_lo_freq() const +{ + return MCR_TO_LOWBAND_IF.at(_master_clock_rate); +} + +uhd::gain_range_t rhodium_radio_control_impl::_get_lo_gain_range() +{ + return gain_range_t(LO_MIN_GAIN, LO_MAX_GAIN, LO_GAIN_STEP); +} + +uhd::gain_range_t rhodium_radio_control_impl::_get_lo_power_range() +{ + return gain_range_t(LO_MIN_POWER, LO_MAX_POWER, LO_POWER_STEP); +} + +int rhodium_radio_control_impl::_get_lo_dsa_setting( + const double freq, const direction_t dir) +{ + int index = 0; + while (freq > FREQ_THRESHOLDS[index + 1]) { + index++; + } + + const double freq_low = FREQ_THRESHOLDS[index]; + const double freq_high = FREQ_THRESHOLDS[index + 1]; + + const auto& gain_table = (dir == RX_DIRECTION) ? DSA_RX_GAIN_VALUES + : DSA_TX_GAIN_VALUES; + const double gain_low = gain_table[index]; + const double gain_high = gain_table[index + 1]; + + + const double slope = (gain_high - gain_low) / (freq_high - freq_low); + const double gain_at_freq = gain_low + (freq - freq_low) * slope; + + RFNOC_LOG_TRACE("Interpolated DSA Gain is " << gain_at_freq); + return static_cast<int>(std::round(gain_at_freq)); +} + +unsigned int rhodium_radio_control_impl::_get_lo_power_setting(double freq) +{ + int index = 0; + while (freq > FREQ_THRESHOLDS[index + 1]) { + index++; + } + + const double freq_low = FREQ_THRESHOLDS[index]; + const double freq_high = FREQ_THRESHOLDS[index + 1]; + const double power_low = LMX_GAIN_VALUES[index]; + const double power_high = LMX_GAIN_VALUES[index + 1]; + const double slope = (power_high - power_low) / (freq_high - freq_low); + const double power_at_freq = power_low + (freq - freq_low) * slope; + + RFNOC_LOG_TRACE("Interpolated LMX Power is " << power_at_freq); + return uhd::narrow_cast<unsigned int>(std::lround(power_at_freq)); +} diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp deleted file mode 100644 index 846a4eac6..000000000 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp +++ /dev/null @@ -1,298 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rhodium_radio_ctrl_impl.hpp" -#include "rhodium_cpld_ctrl.hpp" -#include "rhodium_constants.hpp" -#include <uhd/utils/log.hpp> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -namespace { - const char* rx_band_to_log(rhodium_radio_ctrl_impl::rx_band rx_band) - { - switch (rx_band) - { - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_0: - return "0"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_1: - return "1"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_2: - return "2"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_3: - return "3"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_4: - return "4"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_5: - return "5"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_6: - return "6"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_7: - return "7"; - case rhodium_radio_ctrl_impl::rx_band::RX_BAND_INVALID: - return "INVALID"; - default: - UHD_THROW_INVALID_CODE_PATH(); - } - } - - const char* tx_band_to_log(rhodium_radio_ctrl_impl::tx_band tx_band) - { - switch (tx_band) - { - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_0: - return "0"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_1: - return "1"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_2: - return "2"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_3: - return "3"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_4: - return "4"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_5: - return "5"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_6: - return "6"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_7: - return "7"; - case rhodium_radio_ctrl_impl::tx_band::TX_BAND_INVALID: - return "INVALID"; - default: - UHD_THROW_INVALID_CODE_PATH(); - } - } -} - -void rhodium_radio_ctrl_impl::_update_rx_freq_switches( - const double freq -) { - UHD_LOG_TRACE(unique_id(), - "Update all RX freq related switches. f=" << freq << " Hz, " - ); - const auto band = _map_freq_to_rx_band(freq); - UHD_LOG_TRACE(unique_id(), - "Selected band " << rx_band_to_log(band)); - - // select values for lowband/highband switches - const bool is_lowband = (band == rx_band::RX_BAND_0); - auto rx_sw2_sw7 = is_lowband ? - rhodium_cpld_ctrl::RX_SW2_SW7_LOWBANDFILTERBANK : - rhodium_cpld_ctrl::RX_SW2_SW7_HIGHBANDFILTERBANK; - auto rx_hb_lb_sel = is_lowband ? - rhodium_cpld_ctrl::RX_HB_LB_SEL_LOWBAND : - rhodium_cpld_ctrl::RX_HB_LB_SEL_HIGHBAND; - - // select values for filter bank switches - rhodium_cpld_ctrl::rx_sw3_t rx_sw3; - rhodium_cpld_ctrl::rx_sw4_sw5_t rx_sw4_sw5; - rhodium_cpld_ctrl::rx_sw6_t rx_sw6; - switch (band) - { - case rx_band::RX_BAND_0: - // Low band doesn't use the filter banks, use configuration for band 1 - case rx_band::RX_BAND_1: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; - break; - case rx_band::RX_BAND_2: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0760X1100MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; - break; - case rx_band::RX_BAND_3: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1100X1410MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; - break; - case rx_band::RX_BAND_4: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1410X2050MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5; - break; - case rx_band::RX_BAND_5: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER2050X3000MHZ; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER2050X3000MHZ; - break; - case rx_band::RX_BAND_6: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER3000X4500MHZ; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER3000X4500MHZ; - break; - case rx_band::RX_BAND_7: - rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER4500X6000MHZ; - rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ; - rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER4500X6000MHZ; - break; - case rx_band::RX_BAND_INVALID: - throw uhd::runtime_error(str(boost::format( - "Cannot map RX frequency to band: %f") % freq)); - default: - UHD_THROW_INVALID_CODE_PATH(); - } - - // commit settings to cpld - _cpld->set_rx_switches( - rx_sw2_sw7, - rx_sw3, - rx_sw4_sw5, - rx_sw6, - rx_hb_lb_sel - ); -} - -void rhodium_radio_ctrl_impl::_update_tx_freq_switches( - const double freq -){ - UHD_LOG_TRACE(unique_id(), - "Update all TX freq related switches. f=" << freq << " Hz, " - ); - - const auto band = _map_freq_to_tx_band(freq); - - UHD_LOG_TRACE(unique_id(), - "Selected band " << tx_band_to_log(band)); - - // select values for lowband/highband switches - const bool is_lowband = (band == tx_band::TX_BAND_0); - auto tx_hb_lb_sel = is_lowband ? - rhodium_cpld_ctrl::TX_HB_LB_SEL_LOWBAND : - rhodium_cpld_ctrl::TX_HB_LB_SEL_HIGHBAND; - - // select values for filter bank switches - rhodium_cpld_ctrl::tx_sw2_t tx_sw2; - rhodium_cpld_ctrl::tx_sw3_sw4_t tx_sw3_sw4; - rhodium_cpld_ctrl::tx_sw5_t tx_sw5; - switch (band) - { - case tx_band::TX_BAND_0: - // Low band doesn't use the filter banks, use configuration for band 1 - case tx_band::TX_BAND_1: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; - break; - case tx_band::TX_BAND_2: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1000MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; - break; - case tx_band::TX_BAND_3: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1350MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; - break; - case tx_band::TX_BAND_4: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1900MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4; - break; - case tx_band::TX_BAND_5: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP3000MHZ; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP3000MHZ; - break; - case tx_band::TX_BAND_6: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP4100MHZ; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP4100MHZ; - break; - case tx_band::TX_BAND_7: - tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP6000MHZ; - tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ; - tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP6000MHZ; - break; - case tx_band::TX_BAND_INVALID: - throw uhd::runtime_error(str(boost::format( - "Cannot map TX frequency to band: %f") % freq)); - default: - UHD_THROW_INVALID_CODE_PATH(); - } - - // commit settings to cpld - _cpld->set_tx_switches( - tx_sw2, - tx_sw3_sw4, - tx_sw5, - tx_hb_lb_sel - ); -} - -void rhodium_radio_ctrl_impl::_update_rx_input_switches( - const std::string &input -) { - UHD_LOG_TRACE(unique_id(), - "Update all RX input related switches. input=" << input - ); - const rhodium_cpld_ctrl::cal_iso_sw_t cal_iso = (input == "CAL") ? - rhodium_cpld_ctrl::CAL_ISO_CALLOOPBACK : - rhodium_cpld_ctrl::CAL_ISO_ISOLATION; - const rhodium_cpld_ctrl::rx_sw1_t sw1 = [input]{ - if (input == "TX/RX") - { - return rhodium_cpld_ctrl::RX_SW1_FROMTXRXINPUT; - } - else if (input == "RX2") { - return rhodium_cpld_ctrl::RX_SW1_FROMRX2INPUT; - } - else if (input == "CAL") { - return rhodium_cpld_ctrl::RX_SW1_FROMCALLOOPBACK; - } - else if (input == "TERM") { - return rhodium_cpld_ctrl::RX_SW1_ISOLATION; - } - else { - throw uhd::runtime_error("Invalid antenna in _update_rx_input_switches: " + input); - } - }(); - - UHD_LOG_TRACE(unique_id(), - "Selected switch values:" - " sw1=" << sw1 << - " cal_iso=" << cal_iso - ); - _cpld->set_rx_input_switches(sw1, cal_iso); -} - -void rhodium_radio_ctrl_impl::_update_tx_output_switches( - const std::string &output -) { - UHD_LOG_TRACE(unique_id(), - "Update all TX output related switches. output=" << output - ); - rhodium_cpld_ctrl::tx_sw1_t sw1; - - if (output == "TX/RX") - { - //SW1 needs to select low/high band - if (_is_tx_lowband(get_tx_frequency(0))) - { - sw1 = rhodium_cpld_ctrl::TX_SW1_TOLOWBAND; - } - else { - sw1 = rhodium_cpld_ctrl::TX_SW1_TOSWITCH2; - } - } - else if (output == "CAL") { - sw1 = rhodium_cpld_ctrl::TX_SW1_TOCALLOOPBACK; - } - else if (output == "TERM") { - sw1 = rhodium_cpld_ctrl::TX_SW1_ISOLATION; - } - else { - throw uhd::runtime_error("Invalid antenna in _update_tx_output_switches: " + output); - } - - UHD_LOG_TRACE(unique_id(), - "Selected switch values: sw1=" << sw1 - ); - - _cpld->set_tx_output_switches(sw1); -} diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp deleted file mode 100644 index d6dbbc594..000000000 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp +++ /dev/null @@ -1,677 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rhodium_radio_ctrl_impl.hpp" -#include "rhodium_constants.hpp" -#include <uhdlib/utils/narrow.hpp> -#include <uhdlib/usrp/common/apply_corrections.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; - -namespace { - constexpr char RX_FE_CONNECTION_LOWBAND[] = "QI"; - constexpr char RX_FE_CONNECTION_HIGHBAND[] = "IQ"; - constexpr char TX_FE_CONNECTION_LOWBAND[] = "QI"; - constexpr char TX_FE_CONNECTION_HIGHBAND[] = "IQ"; - - constexpr double DEFAULT_IDENTIFY_DURATION = 5.0; // seconds - - constexpr uint64_t SET_RATE_RPC_TIMEOUT_MS = 10000; - - const fs_path TX_FE_PATH = fs_path("tx_frontends") / 0 / "tune_args"; - const fs_path RX_FE_PATH = fs_path("rx_frontends") / 0 / "tune_args"; - - device_addr_t _get_tune_args(uhd::property_tree::sptr tree, std::string _radio_slot, uhd::direction_t dir) - { - const auto subtree = tree->subtree(fs_path("dboards") / _radio_slot); - const auto tune_arg_path = (dir == RX_DIRECTION) ? RX_FE_PATH : TX_FE_PATH; - return subtree->access<device_addr_t>(tune_arg_path).get(); - } -} - - -/****************************************************************************** - * Structors - *****************************************************************************/ -UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(rhodium_radio_ctrl) -{ - UHD_LOG_TRACE(unique_id(), "Entering rhodium_radio_ctrl_impl ctor..."); - const char radio_slot_name[] = {'A', 'B'}; - _radio_slot = radio_slot_name[get_block_id().get_block_count()]; - _rpc_prefix = - (_radio_slot == "A") ? "db_0_" : "db_1_"; - UHD_LOG_TRACE(unique_id(), "Radio slot: " << _radio_slot); -} - -rhodium_radio_ctrl_impl::~rhodium_radio_ctrl_impl() -{ - UHD_LOG_TRACE(unique_id(), "rhodium_radio_ctrl_impl::dtor() "); -} - - -/****************************************************************************** - * API Calls - *****************************************************************************/ -double rhodium_radio_ctrl_impl::set_rate(double requested_rate) -{ - meta_range_t rates; - for (const double rate : RHODIUM_RADIO_RATES) { - rates.push_back(range_t(rate)); - } - - const double rate = rates.clip(requested_rate); - if (!math::frequencies_are_equal(requested_rate, rate)) { - UHD_LOG_WARNING(unique_id(), - "Coercing requested sample rate from " << (requested_rate / 1e6) << " MHz to " << - (rate / 1e6) << " MHz, the closest possible rate."); - } - - const double current_rate = get_rate(); - if (math::frequencies_are_equal(current_rate, rate)) { - UHD_LOG_DEBUG( - unique_id(), "Rate is already at " << (rate / 1e6) << " MHz. Skipping set_rate()"); - return current_rate; - } - - // The master clock rate is always set by requesting db0's clock rate. - UHD_LOG_TRACE(unique_id(), "Updating master clock rate to " << rate); - auto new_rate = _rpcc->request_with_token<double>( - SET_RATE_RPC_TIMEOUT_MS, "db_0_set_master_clock_rate", rate); - // The lowband LO frequency will change with the master clock rate, so - // update the tuning of the device. - set_tx_frequency(get_tx_frequency(0), 0); - set_rx_frequency(get_rx_frequency(0), 0); - - radio_ctrl_impl::set_rate(new_rate); - return new_rate; -} - -void rhodium_radio_ctrl_impl::set_tx_antenna( - const std::string &ant, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_antenna(ant=" << ant << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - if (!uhd::has(RHODIUM_TX_ANTENNAS, ant)) { - throw uhd::value_error(str( - boost::format("[%s] Requesting invalid TX antenna value: %s") - % unique_id() - % ant - )); - } - - _update_tx_output_switches(ant); - // _update_atr will set the cached antenna value, so no need to do - // it here. See comments in _update_antenna for more info. - _update_atr(ant, TX_DIRECTION); -} - -void rhodium_radio_ctrl_impl::set_rx_antenna( - const std::string &ant, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "Setting RX antenna to " << ant); - UHD_ASSERT_THROW(chan == 0); - - if (!uhd::has(RHODIUM_RX_ANTENNAS, ant)) { - throw uhd::value_error(str( - boost::format("[%s] Requesting invalid RX antenna value: %s") - % unique_id() - % ant - )); - } - - _update_rx_input_switches(ant); - // _update_atr will set the cached antenna value, so no need to do - // it here. See comments in _update_antenna for more info. - _update_atr(ant, RX_DIRECTION); -} - -void rhodium_radio_ctrl_impl::_set_tx_fe_connection(const std::string &conn) -{ - UHD_LOG_TRACE(unique_id(), "set_tx_fe_connection(conn=" << conn << ")"); - - if (conn != _tx_fe_connection) - { - _tx_fe_core->set_mux(conn); - _tx_fe_connection = conn; - } -} - -void rhodium_radio_ctrl_impl::_set_rx_fe_connection(const std::string &conn) -{ - UHD_LOG_TRACE(unique_id(), "set_rx_fe_connection(conn=" << conn << ")"); - - if (conn != _rx_fe_connection) - { - _rx_fe_core->set_fe_connection(conn); - _rx_fe_connection = conn; - } -} - -std::string rhodium_radio_ctrl_impl::_get_tx_fe_connection() const -{ - UHD_LOG_TRACE(unique_id(), "get_tx_fe_connection()"); - - return _tx_fe_connection; -} - -std::string rhodium_radio_ctrl_impl::_get_rx_fe_connection() const -{ - UHD_LOG_TRACE(unique_id(), "get_rx_fe_connection()"); - - return _rx_fe_connection; -} - -double rhodium_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 << ")"); - UHD_ASSERT_THROW(chan == 0); - - const auto old_freq = get_tx_frequency(0); - double coerced_target_freq = uhd::clip(freq, RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ); - - if (freq != coerced_target_freq) { - UHD_LOG_DEBUG(unique_id(), "Requested frequency is outside supported range. Coercing to " << coerced_target_freq); - } - - const bool is_highband = !_is_tx_lowband(coerced_target_freq); - - const double target_lo_freq = is_highband ? - coerced_target_freq : _get_lowband_lo_freq() - coerced_target_freq; - const double actual_lo_freq = - set_tx_lo_freq(target_lo_freq, RHODIUM_LO1, chan); - const double coerced_freq = is_highband ? - actual_lo_freq : _get_lowband_lo_freq() - actual_lo_freq; - const auto conn = is_highband ? - TX_FE_CONNECTION_HIGHBAND : TX_FE_CONNECTION_LOWBAND; - - // update the cached frequency value now so calls to set gain and update - // switches will read the new frequency - radio_ctrl_impl::set_tx_frequency(coerced_freq, chan); - - _set_tx_fe_connection(conn); - set_tx_gain(get_tx_gain(chan), 0); - - if (_get_highband_spur_reduction_enabled(TX_DIRECTION)) { - if (_get_timed_command_enabled() and _is_tx_lowband(old_freq) != not is_highband) { - UHD_LOG_WARNING(unique_id(), - "Timed tuning commands that transition between lowband and highband, 450 " - "MHz, do not function correctly when highband_spur_reduction is enabled! " - "Disable highband_spur_reduction or avoid using timed tuning commands."); - } - UHD_LOG_TRACE( - unique_id(), "TX Lowband LO is " << (is_highband ? "disabled" : "enabled")); - _rpcc->notify_with_token(_rpc_prefix + "enable_tx_lowband_lo", (!is_highband)); - } - _update_tx_freq_switches(coerced_freq); - const bool enable_corrections = is_highband - and (get_tx_lo_source(RHODIUM_LO1, 0) == "internal"); - _update_corrections(actual_lo_freq, TX_DIRECTION, enable_corrections); - // if TX lowband/highband changed and antenna is TX/RX, - // the ATR and SW1 need to be updated - _update_tx_output_switches(get_tx_antenna(0)); - _update_atr(get_tx_antenna(0), TX_DIRECTION); - - return coerced_freq; -} - -double rhodium_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 << ")"); - UHD_ASSERT_THROW(chan == 0); - - const auto old_freq = get_rx_frequency(0); - double coerced_target_freq = uhd::clip(freq, RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ); - - if (freq != coerced_target_freq) { - UHD_LOG_DEBUG(unique_id(), "Requested frequency is outside supported range. Coercing to " << coerced_target_freq); - } - - const bool is_highband = !_is_rx_lowband(coerced_target_freq); - - const double target_lo_freq = is_highband ? - coerced_target_freq : _get_lowband_lo_freq() - coerced_target_freq; - const double actual_lo_freq = - set_rx_lo_freq(target_lo_freq, RHODIUM_LO1, chan); - const double coerced_freq = is_highband ? - actual_lo_freq : _get_lowband_lo_freq() - actual_lo_freq; - const auto conn = is_highband ? - RX_FE_CONNECTION_HIGHBAND : RX_FE_CONNECTION_LOWBAND; - - // update the cached frequency value now so calls to set gain and update - // switches will read the new frequency - radio_ctrl_impl::set_rx_frequency(coerced_freq, chan); - - _set_rx_fe_connection(conn); - set_rx_gain(get_rx_gain(chan), 0); - - if (_get_highband_spur_reduction_enabled(RX_DIRECTION)) { - if (_get_timed_command_enabled() and _is_rx_lowband(old_freq) != not is_highband) { - UHD_LOG_WARNING(unique_id(), - "Timed tuning commands that transition between lowband and highband, 450 " - "MHz, do not function correctly when highband_spur_reduction is enabled! " - "Disable highband_spur_reduction or avoid using timed tuning commands."); - } - UHD_LOG_TRACE( - unique_id(), "RX Lowband LO is " << (is_highband ? "disabled" : "enabled")); - _rpcc->notify_with_token(_rpc_prefix + "enable_rx_lowband_lo", (!is_highband)); - } - _update_rx_freq_switches(coerced_freq); - const bool enable_corrections = is_highband - and (get_rx_lo_source(RHODIUM_LO1, 0) == "internal"); - _update_corrections(actual_lo_freq, RX_DIRECTION, enable_corrections); - - return coerced_freq; -} - -double rhodium_radio_ctrl_impl::set_rx_bandwidth( - const double bandwidth, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_bandwidth(bandwidth=" << bandwidth << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - return get_rx_bandwidth(chan); -} - -double rhodium_radio_ctrl_impl::set_tx_bandwidth( - const double bandwidth, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_bandwidth(bandwidth=" << bandwidth << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - return get_tx_bandwidth(chan); -} - -double rhodium_radio_ctrl_impl::set_tx_gain( - const double gain, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - auto freq = this->get_tx_frequency(chan); - auto index = _get_gain_range(TX_DIRECTION).clip(gain); - - auto old_band = _is_tx_lowband(_tx_frequency_at_last_gain_write) ? - rhodium_cpld_ctrl::gain_band_t::LOW : - rhodium_cpld_ctrl::gain_band_t::HIGH; - auto new_band = _is_tx_lowband(freq) ? - rhodium_cpld_ctrl::gain_band_t::LOW : - rhodium_cpld_ctrl::gain_band_t::HIGH; - - // The CPLD requires a rewrite of the gain control command on a change of lowband or highband - if (get_tx_gain(chan) != index or old_band != new_band) { - UHD_LOG_TRACE(unique_id(), "Writing new TX gain index: " << index); - _cpld->set_gain_index(index, new_band, TX_DIRECTION); - _tx_frequency_at_last_gain_write = freq; - radio_ctrl_impl::set_tx_gain(index, chan); - } else { - UHD_LOG_TRACE(unique_id(), "No change in index or band, skipped writing TX gain index: " << index); - } - - return index; -} - -double rhodium_radio_ctrl_impl::set_rx_gain( - const double gain, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - auto freq = this->get_rx_frequency(chan); - auto index = _get_gain_range(RX_DIRECTION).clip(gain); - - auto old_band = _is_rx_lowband(_rx_frequency_at_last_gain_write) ? - rhodium_cpld_ctrl::gain_band_t::LOW : - rhodium_cpld_ctrl::gain_band_t::HIGH; - auto new_band = _is_rx_lowband(freq) ? - rhodium_cpld_ctrl::gain_band_t::LOW : - rhodium_cpld_ctrl::gain_band_t::HIGH; - - // The CPLD requires a rewrite of the gain control command on a change of lowband or highband - if (get_rx_gain(chan) != index or old_band != new_band) { - UHD_LOG_TRACE(unique_id(), "Writing new RX gain index: " << index); - _cpld->set_gain_index(index, new_band, RX_DIRECTION); - _rx_frequency_at_last_gain_write = freq; - radio_ctrl_impl::set_rx_gain(index, chan); - } else { - UHD_LOG_TRACE(unique_id(), "No change in index or band, skipped writing RX gain index: " << index); - } - - return index; -} - -void rhodium_radio_ctrl_impl::_identify_with_leds( - double identify_duration -) { - auto duration_ms = static_cast<uint64_t>(identify_duration * 1000); - auto end_time = - std::chrono::steady_clock::now() + std::chrono::milliseconds(duration_ms); - bool led_state = true; - { - std::lock_guard<std::mutex> lock(_ant_mutex); - while (std::chrono::steady_clock::now() < end_time) { - auto atr = led_state ? (LED_RX | LED_RX2 | LED_TX) : 0; - _gpio->set_atr_reg(gpio_atr::ATR_REG_IDLE, atr, RHODIUM_GPIO_MASK); - led_state = !led_state; - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - } - _update_atr(get_tx_antenna(0), TX_DIRECTION); - _update_atr(get_rx_antenna(0), RX_DIRECTION); -} - -void rhodium_radio_ctrl_impl::_update_atr( - const std::string& ant, - const direction_t dir -) { - // This function updates sw10 based on the value of both antennas, so we - // use a mutex to prevent other calls in this class instance from running - // at the same time. - std::lock_guard<std::mutex> lock(_ant_mutex); - - UHD_LOG_TRACE(unique_id(), - "Updating ATRs for " << ((dir == RX_DIRECTION) ? "RX" : "TX") << " to " << ant); - - const auto rx_ant = (dir == RX_DIRECTION) ? ant : get_rx_antenna(0); - const auto tx_ant = (dir == TX_DIRECTION) ? ant : get_tx_antenna(0); - const auto sw10_tx = _is_tx_lowband(get_tx_frequency(0)) ? - SW10_FROMTXLOWBAND : SW10_FROMTXHIGHBAND; - - - const uint32_t atr_idle = SW10_ISOLATION; - - const uint32_t atr_rx = [rx_ant]{ - if (rx_ant == "TX/RX") { - return SW10_TORX | LED_RX; - } else if (rx_ant == "RX2") { - return SW10_ISOLATION | LED_RX2; - } else { - return SW10_ISOLATION; - } - }(); - - const uint32_t atr_tx = (tx_ant == "TX/RX") ? - (sw10_tx | LED_TX) : SW10_ISOLATION; - - const uint32_t atr_dx = [tx_ant, rx_ant, sw10_tx] { - uint32_t sw10_return; - if (tx_ant == "TX/RX") { - // if both channels are set to TX/RX, TX will override - sw10_return = sw10_tx | LED_TX; - } else if (rx_ant == "TX/RX") { - sw10_return = SW10_TORX | LED_RX; - } else { - sw10_return = SW10_ISOLATION; - } - sw10_return |= (rx_ant == "RX2") ? LED_RX2 : 0; - return sw10_return; - }(); - - _gpio->set_atr_reg(gpio_atr::ATR_REG_IDLE, atr_idle, RHODIUM_GPIO_MASK); - _gpio->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, atr_rx, RHODIUM_GPIO_MASK); - _gpio->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, atr_tx, RHODIUM_GPIO_MASK); - _gpio->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, atr_dx, RHODIUM_GPIO_MASK); - - UHD_LOG_TRACE(unique_id(), - str(boost::format("Wrote ATR registers i:0x%02X, r:0x%02X, t:0x%02X, d:0x%02X") - % atr_idle % atr_rx % atr_tx % atr_dx)); - - if (dir == RX_DIRECTION) { - radio_ctrl_impl::set_rx_antenna(ant, 0); - } else { - radio_ctrl_impl::set_tx_antenna(ant, 0); - } -} - -void rhodium_radio_ctrl_impl::_update_corrections( - const double freq, - const direction_t dir, - const bool enable) -{ - const std::string fe_path_part = dir == RX_DIRECTION ? "rx_fe_corrections" - : "tx_fe_corrections"; - const fs_path fe_corr_path = _root_path / fe_path_part / 0; - const fs_path dboard_path = fs_path("dboards") / _radio_slot; - - if (enable) - { - UHD_LOG_DEBUG(unique_id(), - "Loading any available frontend corrections for " - << ((dir == RX_DIRECTION) ? "RX" : "TX") << " at " << freq); - if (dir == RX_DIRECTION) { - apply_rx_fe_corrections(_tree, dboard_path, fe_corr_path, freq); - } else { - apply_tx_fe_corrections(_tree, dboard_path, fe_corr_path, freq); - } - } else { - UHD_LOG_DEBUG(unique_id(), - "Disabling frontend corrections for " - << ((dir == RX_DIRECTION) ? "RX" : "TX")); - if (dir == RX_DIRECTION) { - _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); - } else { - _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); - } - } - -} - -uhd::gain_range_t rhodium_radio_ctrl_impl::_get_gain_range(direction_t dir) -{ - if (dir == RX_DIRECTION) { - return gain_range_t(RX_MIN_GAIN, RX_MAX_GAIN, RX_GAIN_STEP); - } else if (dir == TX_DIRECTION) { - return gain_range_t(TX_MIN_GAIN, TX_MAX_GAIN, TX_GAIN_STEP); - } else { - UHD_THROW_INVALID_CODE_PATH(); - } -} - -bool rhodium_radio_ctrl_impl::_get_spur_dodging_enabled(uhd::direction_t dir) const -{ - UHD_ASSERT_THROW(_tree->exists(get_arg_path(SPUR_DODGING_ARG_NAME) / "value")); - auto block_value = _tree->access<std::string>(get_arg_path(SPUR_DODGING_ARG_NAME) / "value").get(); - auto dict = _get_tune_args(_tree, _radio_slot, dir); - - // get the current tune_arg for spur_dodging - // if the tune_arg doesn't exist, use the radio block argument instead - std::string spur_dodging_arg = dict.cast<std::string>( - SPUR_DODGING_ARG_NAME, - block_value); - - if (spur_dodging_arg == "enabled") - { - UHD_LOG_TRACE(unique_id(), "_get_spur_dodging_enabled returning enabled"); - return true; - } - else if (spur_dodging_arg == "disabled") { - UHD_LOG_TRACE(unique_id(), "_get_spur_dodging_enabled returning disabled"); - return false; - } - else { - throw uhd::value_error( - str(boost::format("Invalid spur_dodging argument: %s Valid options are [enabled, disabled]") - % spur_dodging_arg)); - } -} - -double rhodium_radio_ctrl_impl::_get_spur_dodging_threshold(uhd::direction_t dir) const -{ - UHD_ASSERT_THROW(_tree->exists(get_arg_path(SPUR_DODGING_THRESHOLD_ARG_NAME) / "value")); - auto block_value = _tree->access<double>(get_arg_path(SPUR_DODGING_THRESHOLD_ARG_NAME) / "value").get(); - auto dict = _get_tune_args(_tree, _radio_slot, dir); - - // get the current tune_arg for spur_dodging_threshold - // if the tune_arg doesn't exist, use the radio block argument instead - auto threshold = dict.cast<double>(SPUR_DODGING_THRESHOLD_ARG_NAME, block_value); - - UHD_LOG_TRACE(unique_id(), "_get_spur_dodging_threshold returning " << threshold); - - return threshold; -} - -bool rhodium_radio_ctrl_impl::_get_highband_spur_reduction_enabled(uhd::direction_t dir) const -{ - UHD_ASSERT_THROW( - _tree->exists(get_arg_path(HIGHBAND_SPUR_REDUCTION_ARG_NAME) / "value")); - auto block_value = _tree - ->access<std::string>( - get_arg_path(HIGHBAND_SPUR_REDUCTION_ARG_NAME) / "value") - .get(); - auto dict = _get_tune_args(_tree, _radio_slot, dir); - - // get the current tune_arg for highband_spur_reduction - // if the tune_arg doesn't exist, use the radio block argument instead - std::string highband_spur_reduction_arg = - dict.cast<std::string>(HIGHBAND_SPUR_REDUCTION_ARG_NAME, block_value); - - if (highband_spur_reduction_arg == "enabled") { - UHD_LOG_TRACE(unique_id(), __func__ << " returning enabled"); - return true; - } else if (highband_spur_reduction_arg == "disabled") { - UHD_LOG_TRACE(unique_id(), __func__ << " returning disabled"); - return false; - } else { - throw uhd::value_error( - str(boost::format("Invalid highband_spur_reduction argument: %s Valid " - "options are [enabled, disabled]") - % highband_spur_reduction_arg)); - } -} - -bool rhodium_radio_ctrl_impl::_get_timed_command_enabled() const -{ - auto& prop = _tree->access<time_spec_t>(fs_path("time") / "cmd"); - // if timed commands are never set, the property will be empty - // if timed commands were set but cleared, time_spec will be set to 0.0 - return !prop.empty() and prop.get() != time_spec_t(0.0); -} - -size_t rhodium_radio_ctrl_impl::get_chan_from_dboard_fe( - const std::string &fe, const direction_t /* dir */ -) { - UHD_ASSERT_THROW(boost::lexical_cast<size_t>(fe) == 0); - return 0; -} - -std::string rhodium_radio_ctrl_impl::get_dboard_fe_from_chan( - const size_t chan, - const direction_t /* dir */ -) { - UHD_ASSERT_THROW(chan == 0); - return "0"; -} - -void rhodium_radio_ctrl_impl::set_rpc_client( - uhd::rpc_client::sptr rpcc, - const uhd::device_addr_t &block_args -) { - _rpcc = rpcc; - _block_args = block_args; - - // Get and verify the MCR before _init_peripherals, which will use this value - // 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."); - radio_ctrl_impl::set_rate(_master_clock_rate); - - UHD_LOG_TRACE(unique_id(), "Checking for existence of Rhodium DB in slot " << _radio_slot); - const auto all_dboard_info = _rpcc->request<std::vector<std::map<std::string, std::string>>>("get_dboard_info"); - - // There is a bug that if only one DB is plugged into slot B the vector - // will only have 1 element but not be correlated to slot B at all. - // For now, we assume a 1 element array means the DB is in slot A. - if (all_dboard_info.size() <= get_block_id().get_block_count()) - { - UHD_LOG_DEBUG(unique_id(), "No DB detected in slot " << _radio_slot); - // Name and master clock rate are needed for RFNoC init, so set the - // name now and let this function continue to set the MCR - _tree->subtree(fs_path("dboards") / _radio_slot / "tx_frontends" / "0") - ->create<std::string>("name").set("Unknown"); - _tree->subtree(fs_path("dboards") / _radio_slot / "rx_frontends" / "0") - ->create<std::string>("name").set("Unknown"); - } - else { - _dboard_info = all_dboard_info.at(get_block_id().get_block_count()); - UHD_LOG_DEBUG(unique_id(), - "Rhodium DB detected in slot " << _radio_slot << - ". Serial: " << _dboard_info.at("serial")); - _init_defaults(); - _init_peripherals(); - _init_prop_tree(); - - if (block_args.has_key("identify")) { - const std::string identify_val = block_args.get("identify"); - double identify_duration = 0.0; - try { - identify_duration = std::stod(identify_val); - if (!std::isnormal(identify_duration)) { - identify_duration = DEFAULT_IDENTIFY_DURATION; - } - } catch (std::invalid_argument) { - identify_duration = DEFAULT_IDENTIFY_DURATION; - } - - UHD_LOG_INFO(unique_id(), - "Running LED identification process for " << identify_duration - << " seconds."); - _identify_with_leds(identify_duration); - } - } -} - -bool rhodium_radio_ctrl_impl::get_lo_lock_status( - const direction_t dir -) const -{ - return - ((dir == RX_DIRECTION) or _tx_lo->get_lock_status()) and - ((dir == TX_DIRECTION) or _rx_lo->get_lock_status()); -} - -UHD_RFNOC_BLOCK_REGISTER(rhodium_radio_ctrl, "RhodiumRadio"); diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp deleted file mode 100644 index 356932bc2..000000000 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp +++ /dev/null @@ -1,843 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rhodium_radio_ctrl_impl.hpp" -#include "rhodium_constants.hpp" -#include <uhdlib/usrp/cores/spi_core_3000.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/types/eeprom.hpp> -#include <uhd/types/sensors.hpp> -#include <uhd/transport/chdr.hpp> -#include <vector> -#include <string> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -namespace { - enum slave_select_t { - SEN_CPLD = 8, - SEN_TX_LO = 1, - SEN_RX_LO = 2, - SEN_LO_DIST = 4 /* Unused */ - }; - - constexpr uint32_t TX_FE_BASE = 224; - constexpr uint32_t RX_FE_BASE = 232; - - constexpr double RHODIUM_DEFAULT_FREQ = 2.5e9; // Hz - // An invalid default index ensures that set gain will apply settings - // the first time it is called - constexpr double RHODIUM_DEFAULT_INVALID_GAIN = -1; // gain index - constexpr double RHODIUM_DEFAULT_GAIN = 0; // gain index - constexpr double RHODIUM_DEFAULT_LO_GAIN = 30; // gain index - constexpr char RHODIUM_DEFAULT_RX_ANTENNA[] = "RX2"; - constexpr char RHODIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; - constexpr double RHODIUM_DEFAULT_BANDWIDTH = 250e6; // Hz - constexpr auto RHODIUM_DEFAULT_MASH_ORDER = lmx2592_iface::mash_order_t::THIRD; - - //! Rhodium gain profile options - const std::vector<std::string> RHODIUM_GP_OPTIONS = { - "default" - }; - - //! Returns the SPI config used by the CPLD - spi_config_t _get_cpld_spi_config() { - spi_config_t spi_config; - spi_config.use_custom_divider = true; - spi_config.divider = 10; - spi_config.mosi_edge = spi_config_t::EDGE_RISE; - spi_config.miso_edge = spi_config_t::EDGE_FALL; - - return spi_config; - } - - //! Returns the SPI config used by the TX LO - spi_config_t _get_tx_lo_spi_config() { - spi_config_t spi_config; - spi_config.use_custom_divider = true; - spi_config.divider = 10; - spi_config.mosi_edge = spi_config_t::EDGE_RISE; - spi_config.miso_edge = spi_config_t::EDGE_FALL; - - return spi_config; - } - - //! Returns the SPI config used by the RX LO - spi_config_t _get_rx_lo_spi_config() { - spi_config_t spi_config; - spi_config.use_custom_divider = true; - spi_config.divider = 10; - spi_config.mosi_edge = spi_config_t::EDGE_RISE; - spi_config.miso_edge = spi_config_t::EDGE_FALL; - - return spi_config; - } - - std::function<void(uint32_t)> _generate_write_spi( - uhd::spi_iface::sptr spi, - slave_select_t slave, - spi_config_t config - ) { - return [spi, slave, config](const uint32_t transaction) { - spi->write_spi(slave, config, transaction, 24); - }; - } - - std::function<uint32_t(uint32_t)> _generate_read_spi( - uhd::spi_iface::sptr spi, - slave_select_t slave, - spi_config_t config - ) { - return [spi, slave, config](const uint32_t transaction) { - return spi->read_spi(slave, config, transaction, 24); - }; - } -} - -void rhodium_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(RHODIUM_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_rx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); - radio_ctrl_impl::set_rx_antenna(RHODIUM_DEFAULT_RX_ANTENNA, chan); - radio_ctrl_impl::set_rx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); - } - - for (size_t chan = 0; chan < num_tx_chans; chan++) { - radio_ctrl_impl::set_tx_frequency(RHODIUM_DEFAULT_FREQ, chan); - radio_ctrl_impl::set_tx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); - radio_ctrl_impl::set_tx_antenna(RHODIUM_DEFAULT_TX_ANTENNA, chan); - radio_ctrl_impl::set_tx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, 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); - - // Update configurable block arguments from the device arguments provided - if (_block_args.has_key(SPUR_DODGING_ARG_NAME)) { - _tree->access<std::string>(get_arg_path(SPUR_DODGING_ARG_NAME) / "value") - .set(_block_args.get(SPUR_DODGING_ARG_NAME)); - } - - if (_block_args.has_key(SPUR_DODGING_THRESHOLD_ARG_NAME)) { - _tree->access<double>(get_arg_path(SPUR_DODGING_THRESHOLD_ARG_NAME) / "value") - .set(boost::lexical_cast<double>(_block_args.get(SPUR_DODGING_THRESHOLD_ARG_NAME))); - } - - if (_block_args.has_key(HIGHBAND_SPUR_REDUCTION_ARG_NAME)) { - _tree - ->access<std::string>( - get_arg_path(HIGHBAND_SPUR_REDUCTION_ARG_NAME) / "value") - .set(_block_args.get(HIGHBAND_SPUR_REDUCTION_ARG_NAME)); - } -} - -void rhodium_radio_ctrl_impl::_init_peripherals() -{ - UHD_LOG_TRACE(unique_id(), "Initializing peripherals..."); - - UHD_LOG_TRACE(unique_id(), "Initializing SPI core..."); - _spi = spi_core_3000::make(_get_ctrl(0), - regs::sr_addr(regs::SPI), - regs::rb_addr(regs::RB_SPI) - ); - - UHD_LOG_TRACE(unique_id(), "Initializing CPLD..."); - _cpld = std::make_shared<rhodium_cpld_ctrl>( - _generate_write_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config()), - _generate_read_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config())); - - UHD_LOG_TRACE(unique_id(), "Initializing TX frontend DSP core...") - _tx_fe_core = tx_frontend_core_200::make(_get_ctrl(0), regs::sr_addr(TX_FE_BASE)); - _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); - _tx_fe_core->populate_subtree(_tree->subtree(_root_path / "tx_fe_corrections" / 0)); - - UHD_LOG_TRACE(unique_id(), "Initializing RX frontend DSP core...") - _rx_fe_core = rx_frontend_core_3000::make(_get_ctrl(0), regs::sr_addr(RX_FE_BASE)); - _rx_fe_core->set_adc_rate(_master_clock_rate); - _rx_fe_core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); - _rx_fe_core->set_dc_offset_auto(rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); - _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); - _rx_fe_core->populate_subtree(_tree->subtree(_root_path / "rx_fe_corrections" / 0)); - - UHD_LOG_TRACE(unique_id(), "Writing initial gain values..."); - set_tx_gain(RHODIUM_DEFAULT_GAIN, 0); - set_tx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); - set_rx_gain(RHODIUM_DEFAULT_GAIN, 0); - set_rx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); - - UHD_LOG_TRACE(unique_id(), "Initializing TX LO..."); - _tx_lo = lmx2592_iface::make( - _generate_write_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config()), - _generate_read_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config())); - - UHD_LOG_TRACE(unique_id(), "Writing initial TX LO state..."); - _tx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); - _tx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); - - UHD_LOG_TRACE(unique_id(), "Initializing RX LO..."); - _rx_lo = lmx2592_iface::make( - _generate_write_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config()), - _generate_read_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config())); - - UHD_LOG_TRACE(unique_id(), "Writing initial RX LO state..."); - _rx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); - _rx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); - - UHD_LOG_TRACE(unique_id(), "Initializing GPIOs..."); - _gpio = - usrp::gpio_atr::gpio_atr_3000::make( - _get_ctrl(0), - regs::sr_addr(regs::GPIO), - regs::rb_addr(regs::RB_DB_GPIO) - ); - _gpio->set_atr_mode( - usrp::gpio_atr::MODE_ATR, // Enable ATR mode for Rhodium bits - RHODIUM_GPIO_MASK - ); - _gpio->set_atr_mode( - usrp::gpio_atr::MODE_GPIO, // Disable ATR mode for unused bits - ~RHODIUM_GPIO_MASK - ); - _gpio->set_gpio_ddr( - usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs - usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL - ); - - UHD_LOG_TRACE(unique_id(), "Set initial ATR values..."); - _update_atr(RHODIUM_DEFAULT_TX_ANTENNA, TX_DIRECTION); - _update_atr(RHODIUM_DEFAULT_RX_ANTENNA, RX_DIRECTION); - - // Updating the TX frequency path may include an update to SW10, which is - // GPIO controlled, so this must follow CPLD and GPIO initialization - UHD_LOG_TRACE(unique_id(), "Writing initial switch values..."); - _update_tx_freq_switches(RHODIUM_DEFAULT_FREQ); - _update_rx_freq_switches(RHODIUM_DEFAULT_FREQ); - - // Antenna setting requires both CPLD and GPIO control - UHD_LOG_TRACE(unique_id(), "Setting initial antenna settings"); - _update_tx_output_switches(RHODIUM_DEFAULT_TX_ANTENNA); - _update_rx_input_switches(RHODIUM_DEFAULT_RX_ANTENNA); - - UHD_LOG_TRACE(unique_id(), "Checking for existence of LO Distribution board"); - _lo_dist_present = _rpcc->request_with_token<bool>(_rpc_prefix + "is_lo_dist_present"); - UHD_LOG_DEBUG(unique_id(), str(boost::format("LO distribution board is%s present") % (_lo_dist_present ? "" : " NOT"))); -} - -void rhodium_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("Rhodium"))) - ; - subtree->create<std::string>(tx_fe_path / "connection") - .add_coerced_subscriber([this](const std::string& conn){ - this->_set_tx_fe_connection(conn); - }) - .set_publisher([this](){ - return this->_get_tx_fe_connection(); - }) - ; - subtree->create<device_addr_t>(tx_fe_path / "tune_args") - .set(device_addr_t()) - ; - // RX Standard attributes - subtree->create<std::string>(rx_fe_path / "name") - .set(str(boost::format("Rhodium"))) - ; - subtree->create<std::string>(rx_fe_path / "connection") - .add_coerced_subscriber([this](const std::string& conn){ - this->_set_rx_fe_connection(conn); - }) - .set_publisher([this](){ - return this->_get_rx_fe_connection(); - }) - ; - subtree->create<device_addr_t>(rx_fe_path / "tune_args") - .set(device_addr_t()) - ; - // 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(RHODIUM_TX_ANTENNAS) - .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(RHODIUM_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(RHODIUM_MIN_FREQ, RHODIUM_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(RHODIUM_MIN_FREQ, RHODIUM_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_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(RHODIUM_DEFAULT_BANDWIDTH, RHODIUM_DEFAULT_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_coercer([this, chan_idx](const double bw){ - return this->set_rx_bandwidth(bw, chan_idx); - }) - .set_publisher([this, chan_idx](){ - return this->get_rx_bandwidth(chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") - .set(meta_range_t(RHODIUM_DEFAULT_BANDWIDTH, RHODIUM_DEFAULT_BANDWIDTH)) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update bandwidth range!"); - }) - ; - // TX gains - subtree->create<double>(tx_fe_path / "gains" / "all" / "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" / "all" / "range") - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update gain range!"); - }) - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_gain_range(TX_DIRECTION); - }) - ; - - subtree->create<std::vector<std::string>>(tx_fe_path / "gains/all/profile/options") - .set(RHODIUM_GP_OPTIONS); - - subtree->create<std::string>(tx_fe_path / "gains/all/profile/value") - .set_coercer([this](const std::string& profile){ - std::string return_profile = profile; - if (!uhd::has(RHODIUM_GP_OPTIONS, profile)) - { - return_profile = "default"; - } - _gain_profile[TX_DIRECTION] = return_profile; - return return_profile; - }) - .set_publisher([this](){ - return _gain_profile[TX_DIRECTION]; - }) - ; - - // RX gains - subtree->create<double>(rx_fe_path / "gains" / "all" / "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" / "all" / "range") - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error( - "Attempting to update gain range!"); - }) - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_gain_range(RX_DIRECTION); - }) - ; - - subtree->create<std::vector<std::string> >(rx_fe_path / "gains/all/profile/options") - .set(RHODIUM_GP_OPTIONS); - - subtree->create<std::string>(rx_fe_path / "gains/all/profile/value") - .set_coercer([this](const std::string& profile){ - std::string return_profile = profile; - if (!uhd::has(RHODIUM_GP_OPTIONS, profile)) - { - return_profile = "default"; - } - _gain_profile[RX_DIRECTION] = return_profile; - return return_profile; - }) - .set_publisher([this](){ - return _gain_profile[RX_DIRECTION]; - }) - ; - - // TX LO lock sensor - 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 - 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" - ); - }) - ; - //LO Specific - //RX LO - //RX LO1 Frequency - subtree->create<double>(rx_fe_path / "los"/RHODIUM_LO1/"freq/value") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double freq){ - return this->set_rx_lo_freq(freq, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "los"/RHODIUM_LO1/"freq/range") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq_range(RHODIUM_LO1, chan_idx); - }) - ; - //RX LO1 Source - subtree->create<std::vector<std::string>>(rx_fe_path / "los"/RHODIUM_LO1/"source/options") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_sources(RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<std::string>(rx_fe_path / "los"/RHODIUM_LO1/"source/value") - .add_coerced_subscriber([this,chan_idx](std::string src){ - this->set_rx_lo_source(src, RHODIUM_LO1,chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_source(RHODIUM_LO1, chan_idx); - }) - ; - //RX LO1 Export - subtree->create<bool>(rx_fe_path / "los"/RHODIUM_LO1/"export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_rx_lo_export_enabled(enabled, RHODIUM_LO1, chan_idx); - }) - ; - //RX LO1 Gain - subtree->create<double>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "value") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_gain(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double gain){ - return this->set_rx_lo_gain(gain, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "range") - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_lo_gain_range(); - }) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error("Attempting to update LO gain range!"); - }) - ; - //RX LO1 Output Power - subtree->create<double>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "value") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_power(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double gain){ - return this->set_rx_lo_power(gain, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "range") - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_lo_power_range(); - }) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error("Attempting to update LO output power range!"); - }) - ; - //RX LO2 Frequency - subtree->create<double>(rx_fe_path / "los"/RHODIUM_LO2/"freq/value") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq(RHODIUM_LO2, chan_idx); - }) - .set_coercer([this,chan_idx](double freq){ - return this->set_rx_lo_freq(freq, RHODIUM_LO2, chan_idx); - }) - ; - subtree->create<meta_range_t>(rx_fe_path / "los"/RHODIUM_LO2/"freq/range") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq_range(RHODIUM_LO2, chan_idx); - }) - ; - //RX LO2 Source - subtree->create<std::vector<std::string>>(rx_fe_path / "los"/RHODIUM_LO2/"source/options") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_sources(RHODIUM_LO2, chan_idx); - }) - ; - subtree->create<std::string>(rx_fe_path / "los"/RHODIUM_LO2/"source/value") - .add_coerced_subscriber([this,chan_idx](std::string src){ - this->set_rx_lo_source(src, RHODIUM_LO2, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_source(RHODIUM_LO2, chan_idx); - }) - ; - //RX LO2 Export - subtree->create<bool>(rx_fe_path / "los"/RHODIUM_LO2/"export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_rx_lo_export_enabled(enabled, RHODIUM_LO2, chan_idx); - }); - //RX ALL LOs - subtree->create<std::string>(rx_fe_path / "los" / ALL_LOS / "source/value") - .add_coerced_subscriber([this,chan_idx](std::string src) { - this->set_rx_lo_source(src, ALL_LOS, chan_idx); - }) - .set_publisher([this,chan_idx]() { - return this->get_rx_lo_source(ALL_LOS, chan_idx); - }) - ; - subtree->create<std::vector<std::string>>(rx_fe_path / "los" / ALL_LOS / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_rx_lo_sources(ALL_LOS, chan_idx); - }) - ; - subtree->create<bool>(rx_fe_path / "los" / ALL_LOS / "export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_rx_lo_export_enabled(enabled, ALL_LOS, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_export_enabled(ALL_LOS, chan_idx); - }) - ; - //TX LO - //TX LO1 Frequency - subtree->create<double>(tx_fe_path / "los"/RHODIUM_LO1/"freq/value ") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_freq(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](double freq){ - return this->set_tx_lo_freq(freq, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "los"/RHODIUM_LO1/"freq/range") - .set_publisher([this,chan_idx](){ - return this->get_rx_lo_freq_range(RHODIUM_LO1, chan_idx); - }) - ; - //TX LO1 Source - subtree->create<std::vector<std::string>>(tx_fe_path / "los"/RHODIUM_LO1/"source/options") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_sources(RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<std::string>(tx_fe_path / "los"/RHODIUM_LO1/"source/value") - .add_coerced_subscriber([this,chan_idx](std::string src){ - this->set_tx_lo_source(src, RHODIUM_LO1, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_source(RHODIUM_LO1, chan_idx); - }) - ; - //TX LO1 Export - subtree->create<bool>(tx_fe_path / "los"/RHODIUM_LO1/"export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_tx_lo_export_enabled(enabled, RHODIUM_LO1, chan_idx); - }) - ; - //TX LO1 Gain - subtree->create<double>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "value") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_gain(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double gain){ - return this->set_tx_lo_gain(gain, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "range") - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_lo_gain_range(); - }) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error("Attempting to update LO gain range!"); - }) - ; - //TX LO1 Output Power - subtree->create<double>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "value") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_power(RHODIUM_LO1, chan_idx); - }) - .set_coercer([this,chan_idx](const double gain){ - return this->set_tx_lo_power(gain, RHODIUM_LO1, chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "range") - .set_publisher([](){ - return rhodium_radio_ctrl_impl::_get_lo_power_range(); - }) - .add_coerced_subscriber([](const meta_range_t &){ - throw uhd::runtime_error("Attempting to update LO output power range!"); - }) - ; - //TX LO2 Frequency - subtree->create<double>(tx_fe_path / "los"/RHODIUM_LO2/"freq/value") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_freq(RHODIUM_LO2, chan_idx); - }) - .set_coercer([this,chan_idx](double freq){ - return this->set_tx_lo_freq(freq, RHODIUM_LO2, chan_idx); - }) - ; - subtree->create<meta_range_t>(tx_fe_path / "los"/RHODIUM_LO2/"freq/range") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_freq_range(RHODIUM_LO2,chan_idx); - }) - ; - //TX LO2 Source - subtree->create<std::vector<std::string>>(tx_fe_path / "los"/RHODIUM_LO2/"source/options") - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_sources(RHODIUM_LO2, chan_idx); - }) - ; - subtree->create<std::string>(tx_fe_path / "los"/RHODIUM_LO2/"source/value") - .add_coerced_subscriber([this,chan_idx](std::string src){ - this->set_tx_lo_source(src, RHODIUM_LO2, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_source(RHODIUM_LO2, chan_idx); - }) - ; - //TX LO2 Export - subtree->create<bool>(tx_fe_path / "los"/RHODIUM_LO2/"export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_tx_lo_export_enabled(enabled, RHODIUM_LO2, chan_idx); - }) - ; - //TX ALL LOs - subtree->create<std::string>(tx_fe_path / "los" / ALL_LOS / "source/value") - .add_coerced_subscriber([this,chan_idx](std::string src) { - this->set_tx_lo_source(src, ALL_LOS, chan_idx); - }) - .set_publisher([this,chan_idx]() { - return this->get_tx_lo_source(ALL_LOS, chan_idx); - }) - ; - subtree->create<std::vector<std::string>>(tx_fe_path / "los" / ALL_LOS / "source/options") - .set_publisher([this, chan_idx]() { - return this->get_tx_lo_sources(ALL_LOS, chan_idx); - }) - ; - subtree->create<bool>(tx_fe_path / "los" / ALL_LOS / "export") - .add_coerced_subscriber([this,chan_idx](bool enabled){ - this->set_tx_lo_export_enabled(enabled, ALL_LOS, chan_idx); - }) - .set_publisher([this,chan_idx](){ - return this->get_tx_lo_export_enabled(ALL_LOS, chan_idx); - }) - ; - - //LO Distribution Output Ports - if (_lo_dist_present) { - for (const auto& port : LO_OUTPUT_PORT_NAMES) { - subtree->create<bool>(tx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port / "export") - .add_coerced_subscriber([this, chan_idx, port](bool enabled) { - this->set_tx_lo_output_enabled(enabled, port, chan_idx); - }) - .set_publisher([this, chan_idx, port]() { - return this->get_tx_lo_output_enabled(port, chan_idx); - }) - ; - subtree->create<bool>(rx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port / "export") - .add_coerced_subscriber([this, chan_idx, port](bool enabled) { - this->set_rx_lo_output_enabled(enabled, port, chan_idx); - }) - .set_publisher([this, chan_idx, port]() { - return this->get_rx_lo_output_enabled(port, chan_idx); - }) - ; - } - } -} - -void rhodium_radio_ctrl_impl::_init_prop_tree() -{ - const fs_path fe_base = fs_path("dboards") / _radio_slot; - this->_init_frontend_subtree(_tree->subtree(fe_base), 0); - - // legacy EEPROM paths - auto eeprom_get = [this]() { - auto eeprom = dboard_eeprom_t(); - eeprom.id = boost::lexical_cast<uint16_t>(_dboard_info.at("pid")); - eeprom.revision = _dboard_info.at("rev"); - eeprom.serial = _dboard_info.at("serial"); - return eeprom; - }; - - auto eeprom_set = [](dboard_eeprom_t) { - throw uhd::not_implemented_error("Setting DB EEPROM from this interface not implemented"); - }; - - _tree->create<dboard_eeprom_t>(fe_base / "rx_eeprom") - .set_publisher(eeprom_get) - .add_coerced_subscriber(eeprom_set); - - _tree->create<dboard_eeprom_t>(fe_base / "tx_eeprom") - .set_publisher(eeprom_get) - .add_coerced_subscriber(eeprom_set); - - // EEPROM paths subject to change FIXME - _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("ad9695-625"); - _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("dac37j82"); - - // The tick_rate is equivalent to the master clock rate of the DB in slot A - if (_radio_slot == "A") - { - UHD_ASSERT_THROW(!_tree->exists("tick_rate")); - // set_rate sets the clock rate of the entire device, not just this DB, - // so only add DB A's set and get functions to the tree. - _tree->create<double>("tick_rate") - .set_publisher([this](){ return this->get_rate(); }) - .add_coerced_subscriber([this](double rate) { return this->set_rate(rate); }) - ; - } -} - -void rhodium_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/rhodium/rhodium_radio_ctrl_lo.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp deleted file mode 100644 index 405862485..000000000 --- a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp +++ /dev/null @@ -1,726 +0,0 @@ -// -// Copyright 2018 Ettus Research, a National Instruments Company -// -// SPDX-License-Identifier: GPL-3.0-or-later -// - -#include "rhodium_radio_ctrl_impl.hpp" -#include "rhodium_constants.hpp" -#include <uhdlib/utils/narrow.hpp> -#include <uhd/utils/log.hpp> -#include <uhd/utils/algorithm.hpp> -#include <uhd/types/direction.hpp> -#include <uhd/exception.hpp> -#include <boost/format.hpp> - -using namespace uhd; -using namespace uhd::usrp; -using namespace uhd::rfnoc; - -namespace { - constexpr int NUM_THRESHOLDS = 13; - constexpr std::array<double, NUM_THRESHOLDS> FREQ_THRESHOLDS = - {0.45e9, 0.5e9, 1e9, 1.5e9, 2e9, 2.5e9, 3e9, 3.55e9, 4e9, 4.5e9, 5e9, 5.5e9, 6e9}; - constexpr std::array<int, NUM_THRESHOLDS> LMX_GAIN_VALUES = - {18, 18, 17, 17, 17, 16, 12, 11, 11, 11, 10, 13, 18}; - const std::array<int, NUM_THRESHOLDS> DSA_RX_GAIN_VALUES = - {19, 19, 21, 21, 20, 20, 22, 21, 20, 22, 22, 24, 26}; - const std::array<int, NUM_THRESHOLDS> DSA_TX_GAIN_VALUES = - {19, 19, 21, 21, 20, 20, 22, 21, 22, 24, 24, 26, 28}; - - // Describes the lowband LO in terms of the master clock rate - const std::map<double, double> MCR_TO_LOWBAND_IF = { - {200e6, 1200e6}, - {245.76e6, 1228.8e6}, - {250e6, 1500e6}, - }; - - // validation helpers - - std::vector<std::string> _get_lo_names() - { - return { RHODIUM_LO1, RHODIUM_LO2 }; - } - - void _validate_lo_name(const std::string& name, const std::string& function_name) - { - if (!uhd::has(_get_lo_names(), name) and name != radio_ctrl::ALL_LOS) { - throw uhd::value_error(str(boost::format( - "%s was called with an invalid LO name: %s") - % function_name - % name)); - } - } - - // object agnostic helpers - std::vector<std::string> _get_lo_sources(const std::string& name) - { - if (name == RHODIUM_LO1 or name == radio_ctrl::ALL_LOS) { - return { "internal", "external" }; - } else { - return { "internal" }; - } - } -} - -/****************************************************************************** - * Property Getters - *****************************************************************************/ - -std::vector<std::string> rhodium_radio_ctrl_impl::get_tx_lo_names( - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_names(chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - return _get_lo_names(); -} - -std::vector<std::string> rhodium_radio_ctrl_impl::get_rx_lo_names( - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_names(chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - - return _get_lo_names(); -} - -std::vector<std::string> rhodium_radio_ctrl_impl::get_tx_lo_sources( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_sources(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_sources"); - - return _get_lo_sources(name); -} - -std::vector<std::string> rhodium_radio_ctrl_impl::get_rx_lo_sources( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_sources(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_sources"); - - return _get_lo_sources(name); -} - -freq_range_t rhodium_radio_ctrl_impl::_get_lo_freq_range(const std::string &name) const -{ - if (name == RHODIUM_LO1) { - return freq_range_t{ RHODIUM_LO1_MIN_FREQ, RHODIUM_LO1_MAX_FREQ }; - } else if (name == RHODIUM_LO2) { - // The Lowband LO is a fixed frequency - return freq_range_t{ _get_lowband_lo_freq(), _get_lowband_lo_freq() }; - } else { - throw uhd::runtime_error( - "LO frequency range must be retrieved for each stage individually"); - } -} - -freq_range_t rhodium_radio_ctrl_impl::get_tx_lo_freq_range( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_freq_range(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_freq_range"); - - return _get_lo_freq_range(name); -} - -freq_range_t rhodium_radio_ctrl_impl::get_rx_lo_freq_range( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_freq_range(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_freq_range"); - - return _get_lo_freq_range(name); -} - -/****************************************************************************** - * Frequency Control - *****************************************************************************/ - -double rhodium_radio_ctrl_impl::set_tx_lo_freq( - const double freq, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_freq(freq=" << freq << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_freq"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO frequency must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO cannot be tuned"); - return _get_lowband_lo_freq(); - } - - const auto sd_enabled = _get_spur_dodging_enabled(TX_DIRECTION); - const auto sd_threshold = _get_spur_dodging_threshold(TX_DIRECTION); - - _tx_lo_freq = _tx_lo->set_frequency(freq, sd_enabled, sd_threshold); - set_tx_lo_gain(_get_lo_dsa_setting(_tx_lo_freq, TX_DIRECTION), RHODIUM_LO1, chan); - set_tx_lo_power(_get_lo_power_setting(_tx_lo_freq), RHODIUM_LO1, chan); - _cpld->set_tx_lo_path(_tx_lo_freq); - - return _tx_lo_freq; -} - -double rhodium_radio_ctrl_impl::set_rx_lo_freq( - const double freq, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_freq(freq=" << freq << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_rx_lo_freq"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO frequency must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO cannot be tuned"); - return _get_lowband_lo_freq(); - } - - const auto sd_enabled = _get_spur_dodging_enabled(RX_DIRECTION); - const auto sd_threshold = _get_spur_dodging_threshold(RX_DIRECTION); - - _rx_lo_freq = _rx_lo->set_frequency(freq, sd_enabled, sd_threshold); - set_rx_lo_gain(_get_lo_dsa_setting(_rx_lo_freq, RX_DIRECTION), RHODIUM_LO1, chan); - set_rx_lo_power(_get_lo_power_setting(_rx_lo_freq), RHODIUM_LO1, chan); - _cpld->set_rx_lo_path(_rx_lo_freq); - - return _rx_lo_freq; -} - -double rhodium_radio_ctrl_impl::get_tx_lo_freq( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_freq(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_freq"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO frequency must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _tx_lo_freq : _get_lowband_lo_freq(); -} - -double rhodium_radio_ctrl_impl::get_rx_lo_freq( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_freq(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_freq"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO frequency must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _rx_lo_freq : _get_lowband_lo_freq(); -} - -/****************************************************************************** - * Source Control - *****************************************************************************/ - -void rhodium_radio_ctrl_impl::set_tx_lo_source( - const std::string& src, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_source(src=" << src << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_source"); - - if (name == RHODIUM_LO2) { - if (src != "internal") { - throw uhd::value_error("The Lowband LO can only be set to internal"); - } - return; - } - - if (src == "internal") { - _tx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, true); - _cpld->set_tx_lo_source(rhodium_cpld_ctrl::tx_lo_input_sel_t::TX_LO_INPUT_SEL_INTERNAL); - } else if (src == "external") { - _tx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, false); - _cpld->set_tx_lo_source(rhodium_cpld_ctrl::tx_lo_input_sel_t::TX_LO_INPUT_SEL_EXTERNAL); - } else { - throw uhd::value_error(str(boost::format("set_tx_lo_source was called with an invalid LO source: %s Valid sources are [internal, external]") % src)); - } - - const bool enable_corrections = not _is_tx_lowband(get_tx_frequency(0)) - and src == "internal"; - _update_corrections(get_tx_frequency(0), TX_DIRECTION, enable_corrections); - - _tx_lo_source = src; -} - -void rhodium_radio_ctrl_impl::set_rx_lo_source( - const std::string& src, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_source(src=" << src << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_source"); - - if (name == RHODIUM_LO2) { - if (src != "internal") { - throw uhd::value_error("The Lowband LO can only be set to internal"); - } - return; - } - - if (src == "internal") { - _rx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, true); - _cpld->set_rx_lo_source(rhodium_cpld_ctrl::rx_lo_input_sel_t::RX_LO_INPUT_SEL_INTERNAL); - } else if (src == "external") { - _rx_lo->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_A, false); - _cpld->set_rx_lo_source(rhodium_cpld_ctrl::rx_lo_input_sel_t::RX_LO_INPUT_SEL_EXTERNAL); - } else { - throw uhd::value_error(str(boost::format("set_rx_lo_source was called with an invalid LO source: %s Valid sources for LO1 are [internal, external]") % src)); - } - - const bool enable_corrections = not _is_rx_lowband(get_rx_frequency(0)) - and src == "internal"; - _update_corrections(get_rx_frequency(0), RX_DIRECTION, enable_corrections); - - _rx_lo_source = src; -} - -const std::string rhodium_radio_ctrl_impl::get_tx_lo_source( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_source(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_source"); - - return (name == RHODIUM_LO1 or name == ALL_LOS) ? _tx_lo_source : "internal"; -} - -const std::string rhodium_radio_ctrl_impl::get_rx_lo_source( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_source(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_source"); - - return (name == RHODIUM_LO1 or name == ALL_LOS) ? _rx_lo_source : "internal"; -} - -/****************************************************************************** - * Export Control - *****************************************************************************/ - -void rhodium_radio_ctrl_impl::_set_lo1_export_enabled( - const bool enabled, - const direction_t dir -) { - auto& _lo_ctrl = (dir == RX_DIRECTION) ? _rx_lo : _tx_lo; - _lo_ctrl->set_output_enable(lmx2592_iface::output_t::RF_OUTPUT_B, enabled); - if (_lo_dist_present) { - const auto direction = (dir == RX_DIRECTION) ? "RX" : "TX"; - _rpcc->notify_with_token(_rpc_prefix + "enable_lo_export", direction, enabled); - } -} - -void rhodium_radio_ctrl_impl::set_tx_lo_export_enabled( - const bool enabled, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_export_enabled(enabled=" << enabled << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_export_enabled"); - - if (name == RHODIUM_LO2) { - if (enabled) { - throw uhd::value_error("The lowband LO cannot be exported"); - } - return; - } - - _set_lo1_export_enabled(enabled, TX_DIRECTION); - _tx_lo_exported = enabled; -} - -void rhodium_radio_ctrl_impl::set_rx_lo_export_enabled( - const bool enabled, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_export_enabled(enabled=" << enabled << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_rx_lo_export_enabled"); - - if (name == RHODIUM_LO2) { - if (enabled) { - throw uhd::value_error("The lowband LO cannot be exported"); - } - return; - } - - _set_lo1_export_enabled(enabled, RX_DIRECTION); - _rx_lo_exported = enabled; -} - -bool rhodium_radio_ctrl_impl::get_tx_lo_export_enabled( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_export_enabled(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_export_enabled"); - - return (name == RHODIUM_LO1 or name == ALL_LOS) ? _tx_lo_exported : false; -} - -bool rhodium_radio_ctrl_impl::get_rx_lo_export_enabled( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_export_enabled(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_export_enabled"); - - return (name == RHODIUM_LO1 or name == ALL_LOS) ? _rx_lo_exported : false; -} - -/****************************************************************************** - * LO Distribution Control - *****************************************************************************/ - -void rhodium_radio_ctrl_impl::_validate_output_port(const std::string& port_name, const std::string& function_name) -{ - if (!_lo_dist_present) { - throw uhd::runtime_error(str(boost::format( - "%s can only be called if the LO distribution board was detected") % function_name)); - } - - if (!uhd::has(LO_OUTPUT_PORT_NAMES, port_name)) { - throw uhd::value_error(str(boost::format( - "%s was called with an invalid LO output port: %s Valid ports are [LO_OUT_0, LO_OUT_1, LO_OUT_2, LO_OUT_3]") - % function_name % port_name)); - } -} - -void rhodium_radio_ctrl_impl::_set_lo_output_enabled( - const bool enabled, - const std::string& port_name, - const direction_t dir -) { - auto direction = (dir == RX_DIRECTION) ? "RX" : "TX"; - auto name_iter = std::find(LO_OUTPUT_PORT_NAMES.begin(), LO_OUTPUT_PORT_NAMES.end(), port_name); - auto index = std::distance(LO_OUTPUT_PORT_NAMES.begin(), name_iter); - - _rpcc->notify_with_token(_rpc_prefix + "enable_lo_output", direction, index, enabled); - auto out_enabled = (dir == RX_DIRECTION) ? _lo_dist_rx_out_enabled : _lo_dist_tx_out_enabled; - out_enabled[index] = enabled; -} - -void rhodium_radio_ctrl_impl::set_tx_lo_output_enabled( - const bool enabled, - const std::string& port_name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_output_enabled(enabled=" << enabled << ", port_name=" << port_name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_output_port(port_name, "set_tx_lo_output_enabled"); - - _set_lo_output_enabled(enabled, port_name, TX_DIRECTION); -} - -void rhodium_radio_ctrl_impl::set_rx_lo_output_enabled( - const bool enabled, - const std::string& port_name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_output_enabled(enabled=" << enabled << ", port_name=" << port_name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_output_port(port_name, "set_rx_lo_output_enabled"); - - _set_lo_output_enabled(enabled, port_name, RX_DIRECTION); -} - -bool rhodium_radio_ctrl_impl::_get_lo_output_enabled( - const std::string& port_name, - const direction_t dir -) { - auto name_iter = std::find(LO_OUTPUT_PORT_NAMES.begin(), LO_OUTPUT_PORT_NAMES.end(), port_name); - auto index = std::distance(LO_OUTPUT_PORT_NAMES.begin(), name_iter); - - auto out_enabled = (dir == RX_DIRECTION) ? _lo_dist_rx_out_enabled : _lo_dist_tx_out_enabled; - return out_enabled[index]; -} - -bool rhodium_radio_ctrl_impl::get_tx_lo_output_enabled( - const std::string& port_name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_output_enabled(port_name=" << port_name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_output_port(port_name, "get_tx_lo_output_enabled"); - - return _get_lo_output_enabled(port_name, TX_DIRECTION); -} - -bool rhodium_radio_ctrl_impl::get_rx_lo_output_enabled( - const std::string& port_name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_output_enabled(port_name=" << port_name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_output_port(port_name, "get_rx_lo_output_enabled"); - - return _get_lo_output_enabled(port_name, RX_DIRECTION); -} - -/****************************************************************************** - * Gain Control - *****************************************************************************/ - -double rhodium_radio_ctrl_impl::set_tx_lo_gain( - double gain, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_gain(gain=" << gain << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_gain"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO gain must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO does not have configurable gain"); - return 0.0; - } - - auto index = _get_lo_gain_range().clip(gain); - - _cpld->set_lo_gain(index, TX_DIRECTION); - _lo_tx_gain = index; - return _lo_tx_gain; -} - -double rhodium_radio_ctrl_impl::set_rx_lo_gain( - double gain, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_gain(gain=" << gain << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_rx_lo_gain"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO gain must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO does not have configurable gain"); - return 0.0; - } - - auto index = _get_lo_gain_range().clip(gain); - - _cpld->set_lo_gain(index, RX_DIRECTION); - _lo_rx_gain = index; - return _lo_rx_gain; -} - -double rhodium_radio_ctrl_impl::get_tx_lo_gain( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_gain(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_gain"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO gain must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _lo_rx_gain : 0.0; -} - -double rhodium_radio_ctrl_impl::get_rx_lo_gain( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_gain(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_gain"); - - if (name == ALL_LOS) { - throw uhd::runtime_error("LO gain must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _lo_tx_gain : 0.0; -} - -/****************************************************************************** - * Output Power Control - *****************************************************************************/ - -double rhodium_radio_ctrl_impl::_set_lo1_power( - const double power, - const direction_t dir -) { - auto& _lo_ctrl = (dir == RX_DIRECTION) ? _rx_lo : _tx_lo; - auto coerced_power = static_cast<uint32_t>(_get_lo_power_range().clip(power, true)); - - _lo_ctrl->set_output_power(lmx2592_iface::RF_OUTPUT_A, coerced_power); - return coerced_power; -} - -double rhodium_radio_ctrl_impl::set_tx_lo_power( - const double power, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_tx_lo_power(power=" << power << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_tx_lo_power"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO output power must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO does not have configurable output power"); - return 0.0; - } - - _lo_tx_power = _set_lo1_power(power, TX_DIRECTION); - return _lo_tx_power; -} - -double rhodium_radio_ctrl_impl::set_rx_lo_power( - const double power, - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "set_rx_lo_power(power=" << power << ", name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "set_rx_lo_power"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO output power must be set for each stage individually"); - } - if (name == RHODIUM_LO2) { - UHD_LOG_WARNING(unique_id(), "The Lowband LO does not have configurable output power"); - return 0.0; - } - - _lo_rx_power = _set_lo1_power(power, RX_DIRECTION); - return _lo_rx_power; -} - -double rhodium_radio_ctrl_impl::get_tx_lo_power( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_tx_lo_power(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_tx_lo_power"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO output power must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _lo_tx_power : 0.0; -} - -double rhodium_radio_ctrl_impl::get_rx_lo_power( - const std::string& name, - const size_t chan -) { - UHD_LOG_TRACE(unique_id(), "get_rx_lo_power(name=" << name << ", chan=" << chan << ")"); - UHD_ASSERT_THROW(chan == 0); - _validate_lo_name(name, "get_rx_lo_power"); - - if (name == ALL_LOS) { - throw uhd::runtime_error( - "LO output power must be retrieved for each stage individually"); - } - - return (name == RHODIUM_LO1) ? _lo_rx_power : 0.0; -} - -/****************************************************************************** - * Helper Functions - *****************************************************************************/ - -double rhodium_radio_ctrl_impl::_get_lowband_lo_freq() const -{ - return MCR_TO_LOWBAND_IF.at(_master_clock_rate); -} - -uhd::gain_range_t rhodium_radio_ctrl_impl::_get_lo_gain_range() -{ - return gain_range_t(LO_MIN_GAIN, LO_MAX_GAIN, LO_GAIN_STEP); -} - -uhd::gain_range_t rhodium_radio_ctrl_impl::_get_lo_power_range() -{ - return gain_range_t(LO_MIN_POWER, LO_MAX_POWER, LO_POWER_STEP); -} - -int rhodium_radio_ctrl_impl::_get_lo_dsa_setting(const double freq, const direction_t dir) { - int index = 0; - while (freq > FREQ_THRESHOLDS[index+1]) { - index++; - } - - const double freq_low = FREQ_THRESHOLDS[index]; - const double freq_high = FREQ_THRESHOLDS[index+1]; - - const auto& gain_table = (dir == RX_DIRECTION) ? DSA_RX_GAIN_VALUES : DSA_TX_GAIN_VALUES; - const double gain_low = gain_table[index]; - const double gain_high = gain_table[index+1]; - - - const double slope = (gain_high - gain_low) / (freq_high - freq_low); - const double gain_at_freq = gain_low + (freq - freq_low) * slope; - - UHD_LOG_TRACE(unique_id(), "Interpolated DSA Gain is " << gain_at_freq); - return static_cast<int>(std::round(gain_at_freq)); -} - -unsigned int rhodium_radio_ctrl_impl::_get_lo_power_setting(double freq) { - int index = 0; - while (freq > FREQ_THRESHOLDS[index+1]) { - index++; - } - - const double freq_low = FREQ_THRESHOLDS[index]; - const double freq_high = FREQ_THRESHOLDS[index+1]; - const double power_low = LMX_GAIN_VALUES[index]; - const double power_high = LMX_GAIN_VALUES[index+1]; - - - const double slope = (power_high - power_low) / (freq_high - freq_low); - const double power_at_freq = power_low + (freq - freq_low) * slope; - - UHD_LOG_TRACE(unique_id(), "Interpolated LMX Power is " << power_at_freq); - return uhd::narrow_cast<unsigned int>(std::lround(power_at_freq)); -} |