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/rhodium_radio_control_init.cpp | |
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/rhodium_radio_control_init.cpp')
-rw-r--r-- | host/lib/usrp/dboard/rhodium/rhodium_radio_control_init.cpp | 611 |
1 files changed, 611 insertions, 0 deletions
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. +} |