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 --- host/lib/usrp/dboard/CMakeLists.txt | 5 +- host/lib/usrp/dboard/rhodium/CMakeLists.txt | 18 + host/lib/usrp/dboard/rhodium/rhodium_bands.cpp | 135 ++++ host/lib/usrp/dboard/rhodium/rhodium_constants.hpp | 69 ++ host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.cpp | 474 ++++++++++++++ host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.hpp | 344 ++++++++++ .../dboard/rhodium/rhodium_radio_ctrl_cpld.cpp | 374 +++++++++++ .../dboard/rhodium/rhodium_radio_ctrl_impl.cpp | 437 +++++++++++++ .../dboard/rhodium/rhodium_radio_ctrl_impl.hpp | 345 ++++++++++ .../dboard/rhodium/rhodium_radio_ctrl_init.cpp | 710 +++++++++++++++++++++ .../usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp | 569 +++++++++++++++++ 11 files changed, 3479 insertions(+), 1 deletion(-) create mode 100644 host/lib/usrp/dboard/rhodium/CMakeLists.txt create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_bands.cpp create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_constants.hpp create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.cpp create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.hpp create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp create mode 100644 host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp (limited to 'host/lib/usrp/dboard') diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 8ebe8e642..25e1bd2b9 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -2,7 +2,7 @@ # Copyright 2010-2011 Ettus Research LLC # Copyright 2018 Ettus Research, a National Instruments Company # -# SPDX-License-Identifier: GPL-3.0 +# SPDX-License-Identifier: GPL-3.0-or-later # ######################################################################## @@ -49,6 +49,9 @@ ENDIF(ENABLE_X300) IF(ENABLE_N300) INCLUDE_SUBDIRECTORY(magnesium) ENDIF(ENABLE_N300) +IF(ENABLE_N320) + INCLUDE_SUBDIRECTORY(rhodium) +ENDIF(ENABLE_N320) IF(ENABLE_MPMD AND ENABLE_EISCAT) INCLUDE_SUBDIRECTORY(eiscat) ENDIF(ENABLE_MPMD AND ENABLE_EISCAT) diff --git a/host/lib/usrp/dboard/rhodium/CMakeLists.txt b/host/lib/usrp/dboard/rhodium/CMakeLists.txt new file mode 100644 index 000000000..617e4af58 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright 2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +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_bands.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rhodium_cpld_ctrl.cpp + ) + LIBUHD_APPEND_SOURCES(${RHODIUM_SOURCES}) +ENDIF(ENABLE_MPMD) + diff --git a/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp b/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp new file mode 100644 index 000000000..ffa206195 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_bands.cpp @@ -0,0 +1,135 @@ +// +// 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 + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +namespace { + constexpr double FREQ_COMPARE_EPSILON = 1e-5; + + /* Note on the RX filter bank: + * + * The RX path has 8 bands, which we call BAND0 through BAND7. BAND0 is the + * lowest frequency band. BAND7 is the highest frequency band. + * + * The following constants define lower cutoff frequencies for each band. + * RHODIUM_RX_BAND1_MIN_FREQ is the cutover frequency for switching from + * BAND0 to BAND1, and so on. + * + * Bands 1-7 have both high- and low-pass filters (effectively band + * passes). Band 0 has only low-pass filters. Frequencies have been + * 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() + */ + constexpr double RHODIUM_RX_BAND0_MIN_FREQ = RHODIUM_MIN_FREQ; + constexpr double RHODIUM_RX_BAND1_MIN_FREQ = 450e6; + constexpr double RHODIUM_RX_BAND2_MIN_FREQ = 760e6; + constexpr double RHODIUM_RX_BAND3_MIN_FREQ = 1100e6; + constexpr double RHODIUM_RX_BAND4_MIN_FREQ = 1410e6; + constexpr double RHODIUM_RX_BAND5_MIN_FREQ = 2050e6; + constexpr double RHODIUM_RX_BAND6_MIN_FREQ = 3000e6; + constexpr double RHODIUM_RX_BAND7_MIN_FREQ = 4500e6; + + /* Note on the TX filter bank: + * + * The TX path has 8 bands, which we call BAND0 through BAND7. BAND0 is the + * lowest frequency band. BAND7 is the highest frequency band. + * + * The following constants define lower cutoff frequencies for each band. + * RHODIUM_TX_BAND1_MIN_FREQ is the cutover frequency for switching from + * BAND0 to BAND1, and so on. + * + * All filters on the TX filter bank are low pass filters (no high pass + * filters). Frequencies have been 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_tx_freq_switches() + */ + constexpr double RHODIUM_TX_BAND0_MIN_FREQ = RHODIUM_MIN_FREQ; + constexpr double RHODIUM_TX_BAND1_MIN_FREQ = 450e6; + constexpr double RHODIUM_TX_BAND2_MIN_FREQ = 650e6; + constexpr double RHODIUM_TX_BAND3_MIN_FREQ = 1000e6; + constexpr double RHODIUM_TX_BAND4_MIN_FREQ = 1350e6; + constexpr double RHODIUM_TX_BAND5_MIN_FREQ = 1900e6; + constexpr double RHODIUM_TX_BAND6_MIN_FREQ = 3000e6; + 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) { + + auto freq_compare = fp_compare_epsilon(freq, RHODIUM_FREQ_COMPARE_EPSILON); + + if (freq_compare < RHODIUM_RX_BAND0_MIN_FREQ) { + return rx_band::RX_BAND_INVALID; + } else if (freq_compare < RHODIUM_RX_BAND1_MIN_FREQ) { + return rx_band::RX_BAND_0; + } else if (freq_compare < RHODIUM_RX_BAND2_MIN_FREQ) { + return rx_band::RX_BAND_1; + } else if (freq_compare < RHODIUM_RX_BAND3_MIN_FREQ) { + return rx_band::RX_BAND_2; + } else if (freq_compare < RHODIUM_RX_BAND4_MIN_FREQ) { + return rx_band::RX_BAND_3; + } else if (freq_compare < RHODIUM_RX_BAND5_MIN_FREQ) { + return rx_band::RX_BAND_4; + } else if (freq_compare < RHODIUM_RX_BAND6_MIN_FREQ) { + return rx_band::RX_BAND_5; + } else if (freq_compare < RHODIUM_RX_BAND7_MIN_FREQ) { + return rx_band::RX_BAND_6; + } else if (freq_compare <= RHODIUM_MAX_FREQ) { + return rx_band::RX_BAND_7; + } else { + return rx_band::RX_BAND_INVALID; + } +} + +rhodium_radio_ctrl_impl::tx_band +rhodium_radio_ctrl_impl::_map_freq_to_tx_band(const double freq) { + + auto freq_compare = fp_compare_epsilon(freq, RHODIUM_FREQ_COMPARE_EPSILON); + + if (freq_compare < RHODIUM_TX_BAND0_MIN_FREQ) { + return tx_band::TX_BAND_INVALID; + } else if (freq_compare < RHODIUM_TX_BAND1_MIN_FREQ) { + return tx_band::TX_BAND_0; + } else if (freq_compare < RHODIUM_TX_BAND2_MIN_FREQ) { + return tx_band::TX_BAND_1; + } else if (freq_compare < RHODIUM_TX_BAND3_MIN_FREQ) { + return tx_band::TX_BAND_2; + } else if (freq_compare < RHODIUM_TX_BAND4_MIN_FREQ) { + return tx_band::TX_BAND_3; + } else if (freq_compare < RHODIUM_TX_BAND5_MIN_FREQ) { + return tx_band::TX_BAND_4; + } else if (freq_compare < RHODIUM_TX_BAND6_MIN_FREQ) { + return tx_band::TX_BAND_5; + } else if (freq_compare < RHODIUM_TX_BAND7_MIN_FREQ) { + return tx_band::TX_BAND_6; + } else if (freq_compare <= RHODIUM_MAX_FREQ) { + return tx_band::TX_BAND_7; + } else { + return tx_band::TX_BAND_INVALID; + } +} + +bool rhodium_radio_ctrl_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) +{ + 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 new file mode 100644 index 000000000..82ed5b4c8 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_constants.hpp @@ -0,0 +1,69 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RHODIUM_CONSTANTS_HPP +#define INCLUDED_LIBUHD_RHODIUM_CONSTANTS_HPP + +#include +#include +#include + +static constexpr double RHODIUM_FREQ_COMPARE_EPSILON = 1e-5; + +static constexpr double RHODIUM_RADIO_RATE = 122.88e6; // Hz +static constexpr double RHODIUM_MIN_FREQ = 1e6; // Hz +static constexpr double RHODIUM_MAX_FREQ = 6e9; // Hz + +static constexpr double RHODIUM_LO1_MIN_FREQ = 450e6; // Hz +static constexpr double RHODIUM_LO1_MAX_FREQ = 6e9; // Hz +static constexpr double RHODIUM_LO1_REF_FREQ = 122.88e6; // Hz + +static constexpr double RHODIUM_LO_0_9_GHZ_LPF_THRESHOLD_FREQ = 0.9e9; // Hz +static constexpr double RHODIUM_LO_2_25_GHZ_LPF_THRESHOLD_FREQ = 2.3e9; // Hz + +static constexpr double RHODIUM_LOWBAND_FREQ = 450e6; // Hz +static constexpr double RHODIUM_RX_IF_FREQ = 2.44e9; // Hz +static constexpr double RHODIUM_TX_IF_FREQ = 1.95e9; // Hz + +static constexpr double RX_MIN_GAIN = 0.0; +static constexpr double RX_MAX_GAIN = 60.0; +static constexpr double RX_GAIN_STEP = 1.0; +static constexpr double TX_MIN_GAIN = 0.0; +static constexpr double TX_MAX_GAIN = 60.0; +static constexpr double TX_GAIN_STEP = 1.0; + +static constexpr double LO_MIN_GAIN = 0.0; +static constexpr double LO_MAX_GAIN = 30.0; +static constexpr double LO_GAIN_STEP = 1.0; + +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 const std::vector RHODIUM_RX_ANTENNAS = { + "TX/RX", "RX2", "CAL", "TERM" +}; + +static const std::vector RHODIUM_TX_ANTENNAS = { + "TX/RX", "CAL", "TERM" +}; + +static constexpr uint32_t SW10_GPIO_MASK = 0x3; + +//! Main LO +static constexpr char RHODIUM_LO1[] = "lo1"; +//! Low-band LO (for IF conversion) +static constexpr char RHODIUM_LO2[] = "lowband"; +//! DSA attenuation +static constexpr char RHODIUM_GAIN[] = "gain_table"; +//! LO DSA attenuation +static constexpr char RHODIUM_LO_GAIN[] = "dsa"; +//! LO output power +static constexpr char RHODIUM_LO_POWER[] = "lo"; + +static constexpr size_t RHODIUM_NUM_CHANS = 1; + +#endif /* INCLUDED_LIBUHD_RHODIUM_CONSTANTS_HPP */ diff --git a/host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.cpp b/host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.cpp new file mode 100644 index 000000000..8d294bc48 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.cpp @@ -0,0 +1,474 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "rhodium_cpld_ctrl.hpp" +#include "rhodium_constants.hpp" +#include +#include +#include +#include + +using namespace uhd; +using namespace uhd::math::fp_compare; + +namespace { + //! Address of the CPLD scratch register + constexpr uint8_t CPLD_REGS_SCRATCH = 0x05; + //! Address of the CPLD gain table selection register + constexpr uint8_t CPLD_REGS_GAIN_TBL_SEL = 0x06; + + constexpr uint32_t MAX_GAIN_INDEX = 60; + constexpr uint32_t MAX_LO_GAIN_INDEX = 30; + + //! RX Demodulator Adjustment thresholds + constexpr double RX_DEMOD_ADJ_1500OHM_THRESHOLD = 3e9; + constexpr double RX_DEMOD_ADJ_200OHM_THRESHOLD = 4.5e9; + + /* + Unlike other CPLD fields, gain control doesn't use a register and instead + commands are directly sent over SPI. The format of these 24-bit commands is + as follows. + 23:22 Table select (1 = RX, 2 = TX) + 21:16 Gain index + 15:14 Reserved + 13 Write enable for DSA1 + 12:7 Reserved + 6 Write enable for DSA2 + 5:0 Reserved + The CPLD replies with the current attenuation settings as follows, but we + ignore this reply in our code. + 23:13 Reserved + 12:7 Current attenuator setting for DSA1 + 6 Reserved + 5:0 Current attenuator setting for DSA2 + */ + //! Gain loader constants + constexpr size_t GAIN_CTRL_TABLE_FIELD = 22; + constexpr size_t GAIN_CTRL_INDEX_FIELD = 16; + constexpr size_t GAIN_CTRL_DSA1_FIELD = 13; + constexpr size_t GAIN_CTRL_DSA2_FIELD = 6; + + constexpr uint32_t GAIN_CTRL_TABLE_RX = 1; + constexpr uint32_t GAIN_CTRL_TABLE_TX = 2; + constexpr uint32_t GAIN_CTRL_DSA1_WRITE_ENABLE = 1; + constexpr uint32_t GAIN_CTRL_DSA2_WRITE_ENABLE = 1; + + /* + Similar to gain control, LO control doesn't use a register and instead + commands are directly sent over SPI. The format of these 24-bit commands is + as follows: + 23:22 Table select (Always 3 = LO) + 21:16 Attenuator setting + 15:14 Reserved + 13 Write enable for RX LO DSA + 12:7 Reserved + 6 Write enable for TX LO DSA + 5:0 Reserved + The CPLD replies with the current attenuator settings as follows, but we + ignore this reply in our code. + 23:13 Reserved + 12:7 Current attenuator setting for RX LO DSA + 6 Reserved + 5:0 Current attenuator setting for TX LO DSA + */ + //! LO Gain loader constants + constexpr size_t LO_GAIN_CTRL_TABLE_FIELD = 22; + constexpr size_t LO_GAIN_CTRL_INDEX_FIELD = 16; + constexpr size_t LO_GAIN_CTRL_RX_LO_FIELD = 13; + constexpr size_t LO_GAIN_CTRL_TX_LO_FIELD = 6; + + constexpr uint32_t LO_GAIN_CTRL_TABLE_LO = 3; + constexpr uint32_t LO_GAIN_CTRL_RX_LO_WRITE_ENABLE = 1; + constexpr uint32_t LO_GAIN_CTRL_RX_LO_WRITE_DISABLE = 0; + constexpr uint32_t LO_GAIN_CTRL_TX_LO_WRITE_ENABLE = 1; + constexpr uint32_t LO_GAIN_CTRL_TX_LO_WRITE_DISABLE = 0; +} + +rhodium_cpld_ctrl::rhodium_cpld_ctrl( + write_spi_t write_fn, + read_spi_t read_fn +) +{ + _write_reg_fn = [write_fn](const uint8_t addr, const uint32_t data){ + UHD_LOG_TRACE("RH_CPLD", + str(boost::format("Writing to CPLD: 0x%02X -> 0x%04X") + % uint32_t(addr) % data)); + const uint32_t spi_transaction = 0 + | ((addr & 0x7F) << 17) + | data + ; + UHD_LOG_TRACE("RH_CPLD", + str(boost::format("SPI Transaction: 0x%04X") + % spi_transaction)); + write_fn(spi_transaction); + }; + _write_raw_fn = [write_fn](const uint32_t data) { + UHD_LOG_TRACE("RH_CPLD", + str(boost::format("Writing to CPLD: 0x%06X") + % data)); + UHD_LOG_TRACE("RH_CPLD", + str(boost::format("SPI Transaction: 0x%06X") + % data)); + write_fn(data); + }; + _read_reg_fn = [read_fn](const uint8_t addr){ + UHD_LOG_TRACE("RH_CPLD", + str(boost::format("Reading from CPLD address 0x%02X") + % uint32_t(addr))); + const uint32_t spi_transaction = (1<<16) + | ((addr & 0x7F) << 17) + ; + UHD_LOG_TRACE("RH_CPLD", + str(boost::format("SPI Transaction: 0x%04X") + % spi_transaction)); + return read_fn(spi_transaction); + }; + + reset(); + _loopback_test(); +} + +/****************************************************************************** + * API + *****************************************************************************/ +void rhodium_cpld_ctrl::reset() +{ + std::lock_guard l(_set_mutex); + UHD_LOG_TRACE("RH_CPLD", "Resetting CPLD to default state"); + // Set local register cache to default values and commit + _regs = rhodium_cpld_regs_t(); + _gain_queue.clear(); + commit(true); +} + +uint16_t rhodium_cpld_ctrl::get_reg(const uint8_t addr) +{ + std::lock_guard l(_set_mutex); + return _read_reg_fn(addr); +} + +void rhodium_cpld_ctrl::set_scratch(const uint16_t val) +{ + std::lock_guard l(_set_mutex); + _regs.scratch = val; + commit(); +} + +uint16_t rhodium_cpld_ctrl::get_scratch() +{ + return get_reg(CPLD_REGS_SCRATCH); +} + +void rhodium_cpld_ctrl::set_tx_switches( + const tx_sw2_t tx_sw2, + const tx_sw3_sw4_t tx_sw3_sw4, + const tx_sw5_t tx_sw5, + const tx_hb_lb_sel_t tx_hb_lb_sel, + const bool defer_commit +) { + UHD_LOG_TRACE("RH_CPLD", + "Configuring TX band selection switches."\ + " sw2=" << tx_sw2 << + " sw3_sw4=" << tx_sw3_sw4 << + " sw5=" << tx_sw5 << + " hb_lb_sel=" << tx_hb_lb_sel + ); + + std::lock_guard l(_set_mutex); + + _regs.tx_sw2 = rhodium_cpld_regs_t::tx_sw2_t(tx_sw2); + _regs.tx_sw3_sw4 = rhodium_cpld_regs_t::tx_sw3_sw4_t(tx_sw3_sw4); + _regs.tx_sw5 = rhodium_cpld_regs_t::tx_sw5_t(tx_sw5); + _regs.tx_hb_lb_sel = rhodium_cpld_regs_t::tx_hb_lb_sel_t(tx_hb_lb_sel); + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_rx_switches( + const rx_sw2_sw7_t rx_sw2_sw7, + const rx_sw3_t rx_sw3, + const rx_sw4_sw5_t rx_sw4_sw5, + const rx_sw6_t rx_sw6, + const rx_hb_lb_sel_t rx_hb_lb_sel, + const bool defer_commit +) { + UHD_LOG_TRACE("RH_CPLD", + "Configuring RX band selection switches."\ + " sw2_sw7=" << rx_sw2_sw7 << + " sw3=" << rx_sw3 << + " sw4_sw5=" << rx_sw4_sw5 << + " sw6=" << rx_sw6 << + " hb_lb_sel=" << rx_hb_lb_sel + ); + + std::lock_guard l(_set_mutex); + + _regs.rx_sw2_sw7 = rhodium_cpld_regs_t::rx_sw2_sw7_t(rx_sw2_sw7); + _regs.rx_sw3 = rhodium_cpld_regs_t::rx_sw3_t(rx_sw3); + _regs.rx_sw4_sw5 = rhodium_cpld_regs_t::rx_sw4_sw5_t(rx_sw4_sw5); + _regs.rx_sw6 = rhodium_cpld_regs_t::rx_sw6_t(rx_sw6); + _regs.rx_hb_lb_sel = rhodium_cpld_regs_t::rx_hb_lb_sel_t(rx_hb_lb_sel); + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_rx_input_switches( + const rx_sw1_t rx_sw1, + const cal_iso_sw_t cal_iso_sw, + const bool defer_commit +) { + UHD_LOG_TRACE("RH_CPLD", + "Configuring RX input selection switches."\ + " sw1=" << rx_sw1 << + " cal_iso=" << cal_iso_sw + ); + + std::lock_guard l(_set_mutex); + + _regs.rx_sw1 = rhodium_cpld_regs_t::rx_sw1_t(rx_sw1); + _regs.cal_iso_sw = rhodium_cpld_regs_t::cal_iso_sw_t(cal_iso_sw); + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_tx_output_switches( + const tx_sw1_t tx_sw1, + const bool defer_commit +) { + UHD_LOG_TRACE("RH_CPLD", + "Configuring TX output selection switches."\ + " sw1=" << tx_sw1 + ); + + std::lock_guard l(_set_mutex); + + _regs.tx_sw1 = rhodium_cpld_regs_t::tx_sw1_t(tx_sw1); + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_rx_lo_source( + const rx_lo_input_sel_t rx_lo_input_sel, + const bool defer_commit +) { + UHD_LOG_TRACE("RH_CPLD", "Setting RX LO source to " << rx_lo_input_sel); + std::lock_guard l(_set_mutex); + + _regs.rx_lo_input_sel = rhodium_cpld_regs_t::rx_lo_input_sel_t(rx_lo_input_sel); + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_tx_lo_source( + const tx_lo_input_sel_t tx_lo_input_sel, + const bool defer_commit +) { + UHD_LOG_TRACE("RH_CPLD", "Setting TX LO source to " << tx_lo_input_sel); + std::lock_guard l(_set_mutex); + + _regs.tx_lo_input_sel = rhodium_cpld_regs_t::tx_lo_input_sel_t(tx_lo_input_sel); + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_rx_lo_path( + const double freq, + const bool defer_commit +) { + UHD_LOG_TRACE("RH_CPLD", "Configuring RX LO filter and settings. freq=" << freq); + std::lock_guard l(_set_mutex); + + auto freq_compare = fp_compare_epsilon(freq, RHODIUM_FREQ_COMPARE_EPSILON); + + if (freq_compare < RX_DEMOD_ADJ_1500OHM_THRESHOLD) { + _regs.rx_demod_adj = rhodium_cpld_regs_t::rx_demod_adj_t::RX_DEMOD_ADJ_RES_1500_OHM; + } else if (freq_compare < RX_DEMOD_ADJ_200OHM_THRESHOLD) { + _regs.rx_demod_adj = rhodium_cpld_regs_t::rx_demod_adj_t::RX_DEMOD_ADJ_RES_200_OHM; + } else { + _regs.rx_demod_adj = rhodium_cpld_regs_t::rx_demod_adj_t::RX_DEMOD_ADJ_RES_OPEN; + } + + if (freq_compare < RHODIUM_LO_0_9_GHZ_LPF_THRESHOLD_FREQ) { + _regs.rx_lo_filter_sel = rhodium_cpld_regs_t::rx_lo_filter_sel_t::RX_LO_FILTER_SEL_0_9GHZ_LPF; + } else if (freq_compare < RHODIUM_LO_2_25_GHZ_LPF_THRESHOLD_FREQ) { + _regs.rx_lo_filter_sel = rhodium_cpld_regs_t::rx_lo_filter_sel_t::RX_LO_FILTER_SEL_2_25GHZ_LPF; + } else { + _regs.rx_lo_filter_sel = rhodium_cpld_regs_t::rx_lo_filter_sel_t::RX_LO_FILTER_SEL_5_85GHZ_LPF; + } + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_tx_lo_path( + const double freq, + const bool defer_commit +) { + UHD_LOG_TRACE("RH_CPLD", "Configuring TX LO filter and settings. freq=" << freq); + std::lock_guard l(_set_mutex); + + auto freq_compare = fp_compare_epsilon(freq, RHODIUM_FREQ_COMPARE_EPSILON); + + if (freq_compare < RHODIUM_LO_0_9_GHZ_LPF_THRESHOLD_FREQ) { + _regs.tx_lo_filter_sel = rhodium_cpld_regs_t::tx_lo_filter_sel_t::TX_LO_FILTER_SEL_0_9GHZ_LPF; + } else if (freq_compare < RHODIUM_LO_2_25_GHZ_LPF_THRESHOLD_FREQ) { + _regs.tx_lo_filter_sel = rhodium_cpld_regs_t::tx_lo_filter_sel_t::TX_LO_FILTER_SEL_2_25GHZ_LPF; + } else { + _regs.tx_lo_filter_sel = rhodium_cpld_regs_t::tx_lo_filter_sel_t::TX_LO_FILTER_SEL_5_85GHZ_LPF; + } + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_gain_index( + const uint32_t index, + const gain_band_t band, + const uhd::direction_t dir, + const bool defer_commit +) { + UHD_ASSERT_THROW(index <= MAX_GAIN_INDEX); + UHD_ASSERT_THROW(dir == RX_DIRECTION or dir == TX_DIRECTION); + + if (band == HIGH) + { + if (dir == RX_DIRECTION) + { + _regs.rx_gain_tbl_sel = rhodium_cpld_regs_t::RX_GAIN_TBL_SEL_HIGHBAND; + } + else { + _regs.tx_gain_tbl_sel = rhodium_cpld_regs_t::TX_GAIN_TBL_SEL_HIGHBAND; + } + } else { + if (dir == RX_DIRECTION) + { + _regs.rx_gain_tbl_sel = rhodium_cpld_regs_t::RX_GAIN_TBL_SEL_LOWBAND; + } + else { + _regs.tx_gain_tbl_sel = rhodium_cpld_regs_t::TX_GAIN_TBL_SEL_LOWBAND; + } + } + + const uint8_t table_id = (dir == RX_DIRECTION) ? + GAIN_CTRL_TABLE_RX : + GAIN_CTRL_TABLE_TX; + + const uint32_t cmd = + (table_id << GAIN_CTRL_TABLE_FIELD) | + (index << GAIN_CTRL_INDEX_FIELD) | + (GAIN_CTRL_DSA1_WRITE_ENABLE << GAIN_CTRL_DSA1_FIELD) | + (GAIN_CTRL_DSA2_WRITE_ENABLE << GAIN_CTRL_DSA2_FIELD); + + std::lock_guard l(_set_mutex); + _gain_queue.emplace_back(cmd); + + if (not defer_commit) { + commit(); + } +} + +void rhodium_cpld_ctrl::set_lo_gain( + const uint32_t index, + const uhd::direction_t dir, + const bool defer_commit +) { + UHD_ASSERT_THROW(index <= MAX_LO_GAIN_INDEX); + + // The DSA has 0-30 dB of attenuation in 1 dB steps + // This index directly controls the attenuation value of the LO DSA, + // so reverse the gain value to write the value + const uint32_t attenuation = MAX_LO_GAIN_INDEX - index; + const uint8_t set_rx = (dir == RX_DIRECTION or dir == DX_DIRECTION) ? + LO_GAIN_CTRL_RX_LO_WRITE_ENABLE : + LO_GAIN_CTRL_RX_LO_WRITE_DISABLE; + const uint8_t set_tx = (dir == TX_DIRECTION or dir == DX_DIRECTION) ? + LO_GAIN_CTRL_TX_LO_WRITE_ENABLE : + LO_GAIN_CTRL_TX_LO_WRITE_DISABLE; + + const uint32_t cmd = + (LO_GAIN_CTRL_TABLE_LO << LO_GAIN_CTRL_TABLE_FIELD) | + (attenuation << LO_GAIN_CTRL_INDEX_FIELD) | + (set_rx << LO_GAIN_CTRL_RX_LO_FIELD) | + (set_tx << LO_GAIN_CTRL_TX_LO_FIELD); + + std::lock_guard l(_set_mutex); + _gain_queue.emplace_back(cmd); + + if (not defer_commit) { + commit(); + } +} + + +/****************************************************************************** + * Private methods + *****************************************************************************/ +void rhodium_cpld_ctrl::_loopback_test() +{ + UHD_LOG_TRACE("RH_CPLD", "Performing CPLD scratch loopback test..."); + using namespace std::chrono; + const uint16_t random_number = // Derived from current time + uint16_t(system_clock::to_time_t(system_clock::now()) & 0xFFFF); + set_scratch(random_number); + const uint16_t actual = get_scratch(); + if (actual != random_number) { + UHD_LOGGER_ERROR("RH_CPLD") + << "CPLD scratch loopback failed! " + << boost::format("Expected: 0x%04X Got: 0x%04X") + % random_number % actual + ; + throw uhd::runtime_error("CPLD scratch loopback failed!"); + } + UHD_LOG_TRACE("RH_CPLD", "CPLD scratch loopback test passed!"); +} + +void rhodium_cpld_ctrl::commit(const bool save_all) +{ + UHD_LOGGER_TRACE("RH_CPLD") + << "Storing register cache " + << (save_all ? "completely" : "selectively") + << " to CPLD via SPI..." + ; + auto changed_addrs = save_all ? + _regs.get_all_addrs() : + _regs.get_changed_addrs(); + for (const auto addr: changed_addrs) { + _write_reg_fn(addr, _regs.get_reg(addr)); + } + _regs.save_state(); + UHD_LOG_TRACE("RH_CPLD", + "Storing cache complete: " \ + "Updated " << changed_addrs.size() << " registers.") + ; + + UHD_LOGGER_TRACE("RH_CPLD") + << "Writing queued gain commands " + << "to CPLD via SPI..." + ; + for (const auto cmd : _gain_queue) { + _write_raw_fn(cmd); + } + UHD_LOG_TRACE("RH_CPLD", + "Writing queued gain commands complete: " \ + "Wrote " << _gain_queue.size() << " commands.") + ; + _gain_queue.clear(); +} + diff --git a/host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.hpp b/host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.hpp new file mode 100644 index 000000000..a11f29d7d --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.hpp @@ -0,0 +1,344 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RHODIUM_CPLD_CTRL_HPP +#define INCLUDED_LIBUHD_RHODIUM_CPLD_CTRL_HPP + +#include "adf4351_regs.hpp" +#include "rhodium_cpld_regs.hpp" +#include +#include +#include +#include + +//! Controls the CPLD on a Rhodium daughterboard +// +// Setters are thread-safe through lock guards. This lets a CPLD control object +// be shared by multiple owners. +class rhodium_cpld_ctrl +{ +public: + /************************************************************************** + * Types + *************************************************************************/ + using sptr = std::shared_ptr; + //! SPI write function: Can take a SPI transaction and clock it out + using write_spi_t = std::function; + //! SPI read function: Return SPI + using read_spi_t = std::function; + + enum gain_band_t { + LOW, + HIGH, + }; + + enum tx_sw1_t { + TX_SW1_TOLOWBAND = 0, + TX_SW1_TOSWITCH2 = 1, + TX_SW1_TOCALLOOPBACK = 2, + TX_SW1_ISOLATION = 3 + }; + + enum tx_sw2_t { + TX_SW2_FROMSWITCH3 = 0, + TX_SW2_FROMTXFILTERLP6000MHZ = 1, + TX_SW2_FROMTXFILTERLP4100MHZ = 2, + TX_SW2_FROMTXFILTERLP3000MHZ = 3 + }; + + enum tx_sw3_sw4_t { + TX_SW3_SW4_FROMTXFILTERLP1000MHZ = 1, + TX_SW3_SW4_FROMTXFILTERLP0650MHZ = 2, + TX_SW3_SW4_FROMTXFILTERLP1900MHZ = 4, + TX_SW3_SW4_FROMTXFILTERLP1350MHZ = 8 + }; + + enum tx_sw5_t { + TX_SW5_TOTXFILTERLP3000MHZ = 0, + TX_SW5_TOTXFILTERLP4100MHZ = 1, + TX_SW5_TOTXFILTERLP6000MHZ = 2, + TX_SW5_TOSWITCH4 = 3 + }; + + enum rx_sw1_t { + RX_SW1_FROMCALLOOPBACK = 0, + RX_SW1_FROMRX2INPUT = 1, + RX_SW1_ISOLATION = 2, + RX_SW1_FROMTXRXINPUT = 3 + }; + + enum rx_sw2_sw7_t { + RX_SW2_SW7_LOWBANDFILTERBANK = 0, + RX_SW2_SW7_HIGHBANDFILTERBANK = 1 + }; + + enum rx_sw3_t { + RX_SW3_TOSWITCH4 = 0, + RX_SW3_TOFILTER4500X6000MHZ = 1, + RX_SW3_TOFILTER3000X4500MHZ = 2, + RX_SW3_TOFILTER2050X3000MHZ = 3 + }; + + enum rx_sw4_sw5_t { + RX_SW4_SW5_FILTER0760X1100MHZ = 1, + RX_SW4_SW5_FILTER0450X0760MHZ = 2, + RX_SW4_SW5_FILTER1410X2050MHZ = 4, + RX_SW4_SW5_FILTER1100X1410MHZ = 8 + }; + + enum rx_sw6_t { + RX_SW6_FROMFILTER2050X3000MHZ = 0, + RX_SW6_FROMFILTER3000X4500MHZ = 1, + RX_SW6_FROMFILTER4500X6000MHZ = 2, + RX_SW6_FROMSWITCH5 = 3, + }; + + enum cal_iso_sw_t { + CAL_ISO_ISOLATION = 0, + CAL_ISO_CALLOOPBACK = 1 + }; + + enum tx_hb_lb_sel_t { + TX_HB_LB_SEL_LOWBAND = 0, + TX_HB_LB_SEL_HIGHBAND = 1 + }; + + enum tx_lo_input_sel_t { + TX_LO_INPUT_SEL_INTERNAL = 0, + TX_LO_INPUT_SEL_EXTERNAL = 1 + }; + + enum rx_hb_lb_sel_t { + RX_HB_LB_SEL_LOWBAND = 0, + RX_HB_LB_SEL_HIGHBAND = 1 + }; + + enum rx_lo_input_sel_t { + RX_LO_INPUT_SEL_INTERNAL = 1, + RX_LO_INPUT_SEL_EXTERNAL = 0 + }; + + enum rx_demod_adj { + RX_DEMOD_OPEN = 0, + RX_DEMOD_200OHM = 1, + RX_DEMOD_1500OHM = 2 + }; + + enum tx_lo_filter_sel_t { + TX_LO_FILTER_SEL_0_9GHZ_LPF = 0, + TX_LO_FILTER_SEL_5_85GHZ_LPF = 1, + TX_LO_FILTER_SEL_2_25GHZ_LPF = 2, + TX_LO_FILTER_SEL_ISOLATION = 3 + }; + + enum rx_lo_filter_sel_t { + RX_LO_FILTER_SEL_0_9GHZ_LPF = 0, + RX_LO_FILTER_SEL_5_85GHZ_LPF = 1, + RX_LO_FILTER_SEL_2_25GHZ_LPF = 2, + RX_LO_FILTER_SEL_ISOLATION = 3 + }; + + /*! Constructor. + * + * \param write_spi_fn SPI write function + * \param read_spi_fn SPI read function + */ + rhodium_cpld_ctrl( + write_spi_t write_spi_fn, + read_spi_t read_spi_fn + ); + + /************************************************************************** + * API + *************************************************************************/ + //! Reset all registers to their default state + void reset(); + + //! Return the current value of register at \p addr. + // + // Note: This will initiate a SPI transaction, it doesn't read from the + // internal register cache. However, it won't actually update the register + // cache. + uint16_t get_reg(const uint8_t addr); + + //! Set the value of the scratch register (has no effect on chip functions) + void set_scratch(const uint16_t val); + + //! Get the value of the scratch reg. + // + // This should be zero unless set_scratch() was called beforehand (note + // that _loopback_test() will also call set_scratch()). If set_scratch() + // was previously called, this should return the previously written value. + // + // Note: This will call get_reg(), and not simply return the value of the + // internal cache. + uint16_t get_scratch(); + + /*! Frequency-related settings, transmit side + * + * \param tx_sw2 Filter bank switch 2 + * \param tx_sw3_sw4 Filter bank switch 3 and 4 + * \param tx_sw5 Filter bank switch 5 + * \param tx_hb_lb_sel Power on the highband or lowband amplifier + * \param tx_lo_filter_sel Select LPF filter for LO + */ + void set_tx_switches( + const tx_sw2_t tx_sw2, + const tx_sw3_sw4_t tx_sw3_sw4, + const tx_sw5_t tx_sw5, + const tx_hb_lb_sel_t tx_hb_lb_sel, + const bool defer_commit = false + ); + + /*! Frequency-related settings, receive side + * + * \param rx_sw2_sw7 Filter bank switch 2 and 7 + * \param rx_sw3 Filter bank switch 3 + * \param rx_sw4_sw5 Filter bank switch 4 and 5 + * \param rx_sw6 Filter bank switch 6 + * \param rx_hb_lb_sel Power on the highband or lowband amplifier + * \param rx_lo_filter_sel Select LPF filter for LO + */ + void set_rx_switches( + const rx_sw2_sw7_t rx_sw2_sw7, + const rx_sw3_t rx_sw3, + const rx_sw4_sw5_t rx_sw4_sw5, + const rx_sw6_t rx_sw6, + const rx_hb_lb_sel_t rx_hb_lb_sel, + const bool defer_commit = false + ); + + /*! Input switches for RX side + * + * Note: These are not frequency dependent. + * + * \param rx_sw1 Input selection of RX path + * \param cal_iso_sw Terminates the calibration loopback path + */ + void set_rx_input_switches( + const rx_sw1_t rx_sw1, + const cal_iso_sw_t cal_iso_sw, + const bool defer_commit = false + ); + + /*! Output switches for TX side + * + * Note: These are not frequency dependent. + * + * \param tx_sw1 Output selection of TX path + */ + void set_tx_output_switches( + const tx_sw1_t tx_sw1, + const bool defer_commit = false + ); + + /*! Input switch for RX LO + * + * \param rx_lo_input_sel Selects RX LO source + */ + void set_rx_lo_source( + const rx_lo_input_sel_t rx_lo_input_sel, + const bool defer_commit = false + ); + + /*! Input switch for TX LO + * + * \param tx_lo_input_sel Selects TX LO source + */ + void set_tx_lo_source( + const tx_lo_input_sel_t tx_lo_input_sel, + const bool defer_commit = false + ); + + /*! Configure RX LO filter, synth, and mixer settings + * + * \param freq RX LO Frequency + */ + void set_rx_lo_path( + const double freq, + const bool defer_commit = false + ); + + /*! Configure TX LO filter, synth, and mixer settings + * + * \param freq TX LO Frequency + */ + void set_tx_lo_path( + const double freq, + const bool defer_commit = false + ); + + + /*! Gain index setting for the RF frontend + * + * Sets the gain index to one of the predefined values that have been + * loaded into the CPLD by gain table loader in MPM. + * + * \param index Index of the gain table entry to apply (0-60) + * \param band Selects which table to use (lowband or highband) + * \param dir Selects which RF frontend to apply to (RX or TX) + */ + void set_gain_index( + const uint32_t index, + const gain_band_t band, + const uhd::direction_t dir, + const bool defer_commit = false + ); + + /*! Gain setting for LO1 + * + * Sets the attenuation of the RX LO1 DSA or TX LO1 DSA. + * + * Note: This function uses gain as a parameter, although it is + * setting an attenuation. + * + * \param index Gain value to apply (0-30) + * \param dir Selects which LO to apply to (RX, TX, or DX) + */ + void set_lo_gain( + const uint32_t index, + const uhd::direction_t dir, + const bool defer_commit = false + ); + +private: + //! Write function: Take address / data pair, craft SPI transaction + using write_reg_fn_t = std::function; + //! Write function: Send bits directly to CPLD + using write_raw_fn_t = std::function; + //! Read function: Return value given address + using read_reg_fn_t = std::function; + + //! Dump the state of the registers into the CPLD + // + // \param save_all If true, save all registers. If false, only change those + // that changes recently. + void commit(const bool save_all = false); + + //! Writes to the scratch reg and reads again. Throws on failure. + // + // Note: This is not thread-safe. Accesses to the scratch reg are not + // atomic. Only call this from a thread-safe environment, please. + void _loopback_test(); + + //! Write function for regs pokes + write_reg_fn_t _write_reg_fn; + //! Read function for regs peeks + read_reg_fn_t _read_reg_fn; + //! Write function for raw poke command + write_raw_fn_t _write_raw_fn; + + //! Current state of the CPLD registers (for write operations only) + rhodium_cpld_regs_t _regs; + + //! Queue of gain commands to be written at next commit + std::vector _gain_queue; + + //! Lock access to setters + std::mutex _set_mutex; +}; + +#endif /* INCLUDED_LIBUHD_RHODIUM_CPLD_CTRL_HPP */ diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp new file mode 100644 index 000000000..68efe0f83 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp @@ -0,0 +1,374 @@ +// +// 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 + +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 + ); + + // If TX lowband/highband changes, SW10 needs to be updated + _update_tx_output_switches(get_tx_antenna(0)); +} + +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 sw10_t sw10 = (input == "TX/RX") ? + SW10_TORX : + SW10_ISOLATION; + 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:" + " sw10=" << sw10 << + " sw1=" << sw1 << + " cal_iso=" << cal_iso + ); + + // the TX path may be using TX/RX already, in which case only override sw10 if + // we are attempting to use TX/RX + if (get_tx_antenna(0) == "TX/RX") + { + if (input == "TX/RX") + { + UHD_LOG_TRACE(unique_id(), + "Overriding TX antenna to TERM" + ); + // TODO: setting antenna here could cause race conditions + set_tx_antenna("TERM", 0); + + UHD_LOG_TRACE(unique_id(), + "Setting switch values: sw10=" << sw10 + ); + _gpio->set_gpio_out(sw10, SW10_GPIO_MASK); + } + else { + // skip setting sw10, allowing TX to continue using TX/RX + UHD_LOG_TRACE(unique_id(), + "sw10 setting was not applied because TX antenna is set to TX/RX" + ); + } + } + else { + // TX/RX is not in use, fire away + UHD_LOG_TRACE(unique_id(), + "Setting switch values: sw10=" << sw10 + ); + _gpio->set_gpio_out(sw10, SW10_GPIO_MASK); + } + _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 + ); + sw10_t sw10; + rhodium_cpld_ctrl::tx_sw1_t sw1; + + if (output == "TX/RX") + { + //Both sw1 and sw10 need to select low/high band + if (_map_freq_to_tx_band(get_tx_frequency(0)) == tx_band::TX_BAND_0) + { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOLOWBAND; + sw10 = SW10_FROMTXLOWBAND; + } + else { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOSWITCH2; + sw10 = SW10_FROMTXHIGHBAND; + } + } + else if (output == "CAL") { + sw1 = rhodium_cpld_ctrl::TX_SW1_TOCALLOOPBACK; + sw10 = SW10_ISOLATION; + } + else if (output == "TERM") { + sw1 = rhodium_cpld_ctrl::TX_SW1_ISOLATION; + sw10 = SW10_ISOLATION; + } + else { + throw uhd::runtime_error("Invalid antenna in _update_tx_output_switches: " + output); + } + + UHD_LOG_TRACE(unique_id(), + "Selected switch values: sw1=" << sw1 << " sw10=" << sw10 + ); + + // If RX is on TX/RX, only set sw10 if TX is requesting TX/RX + // and override the RX antenna value + if (get_rx_antenna(0) == "TX/RX") + { + if (output == "TX/RX") + { + UHD_LOG_TRACE(unique_id(), + "Overriding RX antenna to TERM" + ); + // TODO: setting antenna here could cause race conditions + set_rx_antenna("TERM", 0); + + UHD_LOG_TRACE(unique_id(), + "Setting switch values: sw10=" << sw10 + ); + _gpio->set_gpio_out(sw10, SW10_GPIO_MASK); + } + else { + // skip setting sw10, allowing RX to continue using TX/RX + UHD_LOG_TRACE(unique_id(), + "sw10 setting was not applied because RX antenna is set to TX/RX" + ); + } + } + // If RX is on any other setting, set sw10 normally + else { + UHD_LOG_TRACE(unique_id(), + "Setting switch values: sw10=" << sw10 + ); + _gpio->set_gpio_out(sw10, SW10_GPIO_MASK); + } + + _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 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"); diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp new file mode 100644 index 000000000..4f4dd925c --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp @@ -0,0 +1,345 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_RHODIUM_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_RHODIUM_RADIO_CTRL_IMPL_HPP + +#include "rhodium_cpld_ctrl.hpp" +#include "rhodium_cpld_regs.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to an Rhodium radio. + */ +class rhodium_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl +{ +public: + typedef boost::shared_ptr sptr; + + //! Frequency bands for RX. Bands are a function of the analog filter banks + enum class rx_band { + RX_BAND_INVALID, + RX_BAND_0, + RX_BAND_1, + RX_BAND_2, + RX_BAND_3, + RX_BAND_4, + RX_BAND_5, + RX_BAND_6, + RX_BAND_7 + }; + + //! Frequency bands for TX. Bands are a function of the analog filter banks + enum class tx_band { + TX_BAND_INVALID, + TX_BAND_0, + TX_BAND_1, + TX_BAND_2, + TX_BAND_3, + TX_BAND_4, + TX_BAND_5, + TX_BAND_6, + TX_BAND_7 + }; + + enum sw10_t { + SW10_FROMTXLOWBAND = 0, + SW10_FROMTXHIGHBAND = 1, + SW10_ISOLATION = 2, + SW10_TORX = 3 + }; + + /************************************************************************ + * Structors + ***********************************************************************/ + UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(rhodium_radio_ctrl) + virtual ~rhodium_radio_ctrl_impl(); + + /************************************************************************ + * 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); + + 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); + + 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 get_tx_lo_names(const size_t chan); + std::vector get_rx_lo_names(const size_t chan); + std::vector get_tx_lo_sources(const std::string& name, const size_t chan); + std::vector 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); + + // 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); + 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); + 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); + + // 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); + 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); + + // LO Gain Control + + //! Set the external gain for a TX LO + // Out of range values will be coerced + double set_tx_lo_gain(const double gain, const std::string &name, const size_t chan); + + //! Set the external gain for an RX LO + // Out of range values will be coerced + double set_rx_lo_gain(const double gain, const std::string &name, const size_t chan); + + double get_tx_lo_gain(const std::string &name, const size_t chan); + double get_rx_lo_gain(const std::string &name, const size_t chan); + + // LO Output Power Control + + //! Set the output power setting of a TX LO + // Out of range values will be coerced + double set_tx_lo_power(const double power, const std::string &name, const size_t chan); + + //! Set the output power setting of a RX LO + // Out of range values will be coerced + double set_rx_lo_power(const double power, const std::string &name, const size_t chan); + + 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: + /************************************************************************** + * Helpers + *************************************************************************/ + //! Initialize all the peripherals connected to this block + void _init_peripherals(); + + //! Set state of this class to sensible defaults + void _init_defaults(); + + //! Init a subtree for the RF frontends + void _init_frontend_subtree( + uhd::property_tree::sptr subtree, + const size_t chan_idx + ); + + //! Initialize 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 + ); + + //! Get the frequency range for an LO + freq_range_t _get_lo_freq_range(const std::string &name) const; + + //! Get the current lowband intermediate frequency + double _get_lowband_lo_freq() const; + + //! Configure LO1's export + void _set_lo1_export_enabled( + const bool enabled, + const direction_t dir + ); + + //! Configure LO1's output power + // Out of range values will be coerced to [0-63] + double _set_lo1_power( + const double power, + const direction_t dir + ); + + //! Map a frequency in Hz to an rx_band value. Will return + // rx_band::INVALID_BAND if the frequency is out of range. + static rx_band _map_freq_to_rx_band(const double freq); + //! Map a frequency in Hz to an tx_band value. Will return + // tx_band::INVALID_BAND if the frequency is out of range. + static tx_band _map_freq_to_tx_band(const double freq); + + //! Return if the given rx frequency is in lowband + // NOTE: Returns false if frequency is out of Rh's rx frequency range + static bool _is_rx_lowband(const double freq); + //! Return if the given tx frequency is in lowband + // 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 + static uhd::gain_range_t _get_lo_power_range(); + + //! Lookup the LO DSA setting from LO frequency + int _get_lo_dsa_setting(const double freq); + + //! Lookup the LO output power setting from LO frequency + unsigned int _get_lo_power_setting(const double freq); + + bool _get_spur_dodging_enabled(const uhd::direction_t dir) const; + double _get_spur_dodging_threshold(const uhd::direction_t dir) const; + + /************************************************************************** + * Sensors + *************************************************************************/ + //! Return LO lock status. Factors in current band (low/high) and + // direction (TX/RX) + bool get_lo_lock_status(const direction_t dir) const; + + /************************************************************************** + * Frontend Controls + *************************************************************************/ + + void _set_tx_fe_connection(const std::string &conn); + void _set_rx_fe_connection(const std::string &conn); + std::string _get_tx_fe_connection() const; + std::string _get_rx_fe_connection() const; + + /************************************************************************** + * CPLD Controls (implemented in rhodium_radio_ctrl_cpld.cpp) + *************************************************************************/ + void _update_rx_freq_switches( + const double freq + ); + + void _update_tx_freq_switches( + const double freq + ); + + void _update_rx_input_switches( + const std::string &input + ); + + void _update_tx_output_switches( + const std::string &output + ); + + /************************************************************************** + * Private attributes + *************************************************************************/ + //! Locks access to setter APIs + std::mutex _set_lock; + + //! Letter representation of the radio we're currently running + std::string _radio_slot; + + //! Prepended for all dboard RPC calls + std::string _rpc_prefix; + + //! Additional block args; gets set during set_rpc_client() + uhd::device_addr_t _block_args; + + //! Reference to the RPC client + uhd::rpc_client::sptr _rpcc; + + //! Reference to the SPI core + uhd::spi_iface::sptr _spi; + + //! Reference to the TX LO + lmx2592_iface::sptr _tx_lo; + + //! Reference to the RX LO + lmx2592_iface::sptr _rx_lo; + + //! Reference to the CPLD controls. Even if there's multiple radios, + // there's only one CPLD control. + std::shared_ptr _cpld; + + //! ATR controls. These control the external DSA and the AD9371 gain + // up/down bits. They do *not* control the ATR state of the CPLD, the + // tx/rx run states are hooked up directly to the CPLD. + // + // Every radio channel gets its own ATR state register. + usrp::gpio_atr::gpio_atr_3000::sptr _gpio; + + //! Front panel GPIO controller. Note that only one radio block per + // module can be the FP-GPIO master. + usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + + //! One DSP core per channel + rx_frontend_core_3000::sptr _rx_fe_core; + tx_frontend_core_200::sptr _tx_fe_core; + + //! Sampling rate, and also ref clock frequency for the lowband LOs. + double _master_clock_rate = 1.0; + //! Saved frontend connection for DSP core + std::string _rx_fe_connection; + std::string _tx_fe_connection; + //! Desired RF frequency + std::map _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. + double _tx_frequency_at_last_gain_write = 0.0; + double _rx_frequency_at_last_gain_write = 0.0; + //! LO gain + double _lo_rx_gain = 0.0; + double _lo_tx_gain = 0.0; + //! LO output power + double _lo_rx_power = 0.0; + double _lo_tx_power = 0.0; + //! Gain profile + std::map _gain_profile = { {RX_DIRECTION, "default"}, {TX_DIRECTION, "default"} }; + + //! LO source + std::string _rx_lo_source = "internal"; + std::string _tx_lo_source = "internal"; + + //! LO export enabled + bool _rx_lo_exported = false; + bool _tx_lo_exported = false; + + //! LO state frequency + double _rx_lo_freq = 0.0; + double _tx_lo_freq = 0.0; + +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_RHODIUM_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: + diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp new file mode 100644 index 000000000..c2c2002eb --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp @@ -0,0 +1,710 @@ +// +// 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 + +using namespace uhd; +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 + + //! Rhodium gain profile options + const std::vector 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 _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 _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_rx_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("mtu/recv").get() - max_bytes_header) + / (2 * sizeof(int16_t)); + UHD_LOG_DEBUG(unique_id(), + "Setting default spp to " << default_spp); + _tree->access(get_arg_path("spp") / "value").set(default_spp); +} + +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( + _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(), "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(lmx2592_iface::mash_order_t::THIRD); + + 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(lmx2592_iface::mash_order_t::THIRD); + + 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_GPIO, // Disable ATR mode + usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL + ); + _gpio->set_gpio_ddr( + usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs + usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL + ); + + // TODO: put this in the right spot + UHD_LOG_TRACE(unique_id(), "Setting Switch 10 to 0x1"); + _gpio->set_gpio_out(0x1, 0x3); + + _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->populate_subtree(_tree->subtree(_root_path / "rx_fe_corrections" / 0)); + + _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)); +} + +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(tx_fe_path / "name") + .set(str(boost::format("Rhodium"))) + ; + subtree->create(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(tx_fe_path / "tune_args") + .set(device_addr_t()) + ; + // RX Standard attributes + subtree->create(rx_fe_path / "name") + .set(str(boost::format("Rhodium"))) + ; + subtree->create(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(rx_fe_path / "tune_args") + .set(device_addr_t()) + ; + // TX Antenna + subtree->create(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>(tx_fe_path / "antenna" / "options") + .set({RHODIUM_DEFAULT_TX_ANTENNA}) + .add_coerced_subscriber([](const std::vector &){ + throw uhd::runtime_error( + "Attempting to update antenna options!"); + }) + ; + // RX Antenna + subtree->create(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>(rx_fe_path / "antenna" / "options") + .set(RHODIUM_RX_ANTENNAS) + .add_coerced_subscriber([](const std::vector &){ + throw uhd::runtime_error( + "Attempting to update antenna options!"); + }) + ; + // TX frequency + subtree->create(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(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(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(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(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(tx_fe_path / "bandwidth" / "range") + .set(meta_range_t(0.0, 0.0, 0.0)) // FIXME + .add_coerced_subscriber([](const meta_range_t &){ + throw uhd::runtime_error( + "Attempting to update bandwidth range!"); + }) + ; + // RX bandwidth + subtree->create(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(rx_fe_path / "bandwidth" / "range") + .set(meta_range_t(0.0, 0.0, 0.0)) // FIXME + .add_coerced_subscriber([](const meta_range_t &){ + throw uhd::runtime_error( + "Attempting to update bandwidth range!"); + }) + ; + // TX gains + subtree->create(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(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>(tx_fe_path / "gains/all/profile/options") + .set(RHODIUM_GP_OPTIONS); + + subtree->create(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(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(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 >(rx_fe_path / "gains/all/profile/options") + .set(RHODIUM_GP_OPTIONS); + + subtree->create(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(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(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(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(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>(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(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(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(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(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(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(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(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(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>(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(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(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); + }); + //TX LO + //TX LO1 Frequency + subtree->create(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(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>(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(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(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(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(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(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(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(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(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>(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(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(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); + }) + ; +} + +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); + + // EEPROM paths subject to change FIXME + _tree->create(_root_path / "eeprom") + .set(eeprom_map_t()); + + _tree->create("rx_codecs" / _radio_slot / "gains"); + _tree->create("tx_codecs" / _radio_slot / "gains"); + _tree->create("rx_codecs" / _radio_slot / "name").set("ad9695-625"); + _tree->create("tx_codecs" / _radio_slot / "name").set("dac37j82"); + + // TODO remove this dirty hack + if (not _tree->exists("tick_rate")) + { + _tree->create("tick_rate") + .set_publisher([this](){ return this->get_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>( + 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(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( + 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 new file mode 100644 index 000000000..49eb854d5 --- /dev/null +++ b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp @@ -0,0 +1,569 @@ +// +// 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 + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; + +namespace { + constexpr int NUM_THRESHOLDS = 13; + constexpr std::array 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 LMX_GAIN_VALUES = + {18, 18, 17, 17, 17, 16, 12, 11, 11, 11, 10, 13, 18}; + constexpr std::array DSA_GAIN_VALUES = + {19, 19, 21, 21, 20, 20, 22, 21, 20, 22, 22, 24, 26}; + + // Describes the lowband LO in terms of the master clock rate + const std::map MCR_TO_LOWBAND_IF = { + {200e6, 1200e6}, + {245.76e6, 1228.8e6}, + {250e6, 1500e6}, + }; + + // validation helpers + + std::vector _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)) { + throw uhd::value_error(str(boost::format( + "%s was called with an invalid LO name: %s") + % function_name + % name)); + } + } + + // object agnostic helpers + std::vector _get_lo_sources(const std::string& name) + { + if (name == RHODIUM_LO1) { + return { "internal", "external" }; + } else { + return { "internal" }; + } + } +} + +/****************************************************************************** + * Property Getters + *****************************************************************************/ + +std::vector 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 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 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 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 { + // The Lowband LO is a fixed frequency + return freq_range_t{ _get_lowband_lo_freq(), _get_lowband_lo_freq() }; + } +} + +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 == 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), 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 == 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), 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"); + + 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"); + + 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") { + UHD_LOG_ERROR(unique_id(), "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)); + } + + _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") { + UHD_LOG_WARNING(unique_id(), "The Lowband LO can only be 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)); + } + + _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) ? _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) ? _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); +} + +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) ? _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) ? _rx_lo_exported : false; +} + +/****************************************************************************** + * 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 == 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 == 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"); + + 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"); + + 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(_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 == 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 == 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"); + + 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"); + + 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(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 gain_low = DSA_GAIN_VALUES[index]; + const double gain_high = DSA_GAIN_VALUES[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(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(std::lround(power_at_freq)); +} -- cgit v1.2.3