aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp')
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_radio_control.cpp723
1 files changed, 723 insertions, 0 deletions
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");