diff options
author | Martin Braun <martin.braun@ettus.com> | 2019-07-03 20:15:35 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2019-11-26 12:16:25 -0800 |
commit | c256b9df6502536c2e451e690f1ad5962c664d1a (patch) | |
tree | a83ad13e6f5978bbe14bb3ecf8294ba1e3d28db4 /host/lib/usrp/dboard/rhodium | |
parent | 9a8435ed998fc5c65257f4c55768750b227ab19e (diff) | |
download | uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.gz uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.tar.bz2 uhd-c256b9df6502536c2e451e690f1ad5962c664d1a.zip |
x300/mpmd: Port all RFNoC devices to the new RFNoC framework
Co-Authored-By: Alex Williams <alex.williams@ni.com>
Co-Authored-By: Sugandha Gupta <sugandha.gupta@ettus.com>
Co-Authored-By: Brent Stapleton <brent.stapleton@ettus.com>
Co-Authored-By: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Diffstat (limited to 'host/lib/usrp/dboard/rhodium')
-rw-r--r-- | host/lib/usrp/dboard/rhodium/CMakeLists.txt | 8 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_bands.cpp | 22 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_constants.hpp | 40 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp | 723 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_control.hpp (renamed from host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp) | 213 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_control_cpld.cpp | 252 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_control_init.cpp | 611 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_control_lo.cpp | 713 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp | 298 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp | 677 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp | 843 | ||||
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp | 726 |
12 files changed, 2504 insertions, 2622 deletions
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)); -} |