From d87ae61d0490339bfbd7de5d57e692e9e8961237 Mon Sep 17 00:00:00 2001 From: Mark Meserve Date: Wed, 24 Oct 2018 15:24:21 -0500 Subject: rh: add support for rhodium devices Co-authored-by: Humberto Jimenez Co-authored-by: Alex Williams Co-authored-by: Derek Kozel --- .../dboard/rhodium/rhodium_radio_ctrl_impl.cpp | 437 +++++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp (limited to 'host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp') diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp new file mode 100644 index 000000000..b9c56a7f0 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp @@ -0,0 +1,437 @@ +// +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 int DEFAULT_IDENTIFY_DURATION = 5; // seconds + + 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(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 /* rate */) +{ + // TODO: implement + // TODO: set_rate may also need to update the LO since master clock rate + // changes also change the lowband LO frequency. (run set_frequency) + UHD_LOG_WARNING(unique_id(), "set_rate() called but not implemented"); + return 0.0; +} + +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 + )); + } + + radio_ctrl_impl::set_tx_antenna(ant, chan); + _update_tx_output_switches(ant); +} + +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 + )); + } + + radio_ctrl_impl::set_rx_antenna(ant, chan); + _update_rx_input_switches(ant); +} + +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 != _tx_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); + + 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; + + _set_tx_fe_connection(conn); + set_tx_gain(get_tx_gain(chan), 0); + radio_ctrl_impl::set_tx_frequency(coerced_freq, chan); + _update_tx_freq_switches(coerced_freq); + + 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); + + 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; + + _set_rx_fe_connection(conn); + set_rx_gain(get_rx_gain(chan), 0); + radio_ctrl_impl::set_rx_frequency(coerced_freq, chan); + _update_rx_freq_switches(coerced_freq); + + 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; +} + +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 +{ + auto dict = _get_tune_args(_tree, _radio_slot, dir); + + // get the current tune_arg for spur_dodging + // if the tune_arg doesn't exist, get the radio block argument instead + std::string spur_dodging_arg = dict.cast( + "spur_dodging", + (_tree->access(get_arg_path("spur_dodging") / "value").get())); + + if (spur_dodging_arg == "enabled") + { + return true; + } + else if (spur_dodging_arg == "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 +{ + 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, get the radio block argument instead + return dict.cast( + "spur_dodging_threshold", + _tree->access(get_arg_path("spur_dodging_threshold") / "value").get()); +} + +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(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(_rpc_prefix + "get_master_clock_rate"); + if (block_args.cast("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( + "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 dboard_info = _rpcc->request>>("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 (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("name").set("Unknown"); + _tree->subtree(fs_path("dboards") / _radio_slot / "rx_frontends" / "0") + ->create("name").set("Unknown"); + } + else { + UHD_LOG_DEBUG(unique_id(), + "Rhodium DB detected in slot " << + _radio_slot << + ". Serial: " << + dboard_info.at(get_block_id().get_block_count()).at("serial")); + _init_defaults(); + _init_peripherals(); + _init_prop_tree(); + } + + 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 = DEFAULT_IDENTIFY_DURATION; + } + // TODO: Update this when LED control is added + //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"); -- cgit v1.2.3