aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib
diff options
context:
space:
mode:
authorMark Meserve <mark.meserve@ni.com>2018-10-24 15:24:21 -0500
committerBrent Stapleton <bstapleton@g.hmc.edu>2018-10-25 10:30:59 -0700
commitd87ae61d0490339bfbd7de5d57e692e9e8961237 (patch)
tree3e11b3c5f3c0aab3624dadf78ddcbcb2af85b3b9 /host/lib
parentec2977d8cbb233988b93f81deab7af99daec4165 (diff)
downloaduhd-d87ae61d0490339bfbd7de5d57e692e9e8961237.tar.gz
uhd-d87ae61d0490339bfbd7de5d57e692e9e8961237.tar.bz2
uhd-d87ae61d0490339bfbd7de5d57e692e9e8961237.zip
rh: add support for rhodium devices
Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Alex Williams <alex.williams@ni.com> Co-authored-by: Derek Kozel <derek.kozel@ni.com>
Diffstat (limited to 'host/lib')
-rw-r--r--host/lib/CMakeLists.txt3
-rw-r--r--host/lib/ic_reg_maps/CMakeLists.txt7
-rw-r--r--host/lib/ic_reg_maps/gen_rhcpld_regs.py93
-rw-r--r--host/lib/usrp/dboard/CMakeLists.txt5
-rw-r--r--host/lib/usrp/dboard/rhodium/CMakeLists.txt18
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_bands.cpp135
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_constants.hpp69
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.cpp474
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_cpld_ctrl.hpp344
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_cpld.cpp374
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.cpp437
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_impl.hpp345
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_init.cpp710
-rw-r--r--host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp569
14 files changed, 3580 insertions, 3 deletions
diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt
index 99598570e..f5539ef6f 100644
--- a/host/lib/CMakeLists.txt
+++ b/host/lib/CMakeLists.txt
@@ -2,7 +2,7 @@
# Copyright 2010-2015 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
#
########################################################################
@@ -76,6 +76,7 @@ LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("N230" ENABLE_N230 ON "ENABLE_LIBUHD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("MPMD" ENABLE_MPMD ON "ENABLE_LIBUHD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("N300" ENABLE_N300 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF)
+# LIBUHD_REGISTER_COMPONENT("N320" ENABLE_N320 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("E320" ENABLE_E320 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF)
LIBUHD_REGISTER_COMPONENT("DPDK" ENABLE_DPDK ON "ENABLE_MPMD;DPDK_FOUND" OFF OFF)
diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt
index de0e0660e..3f06d9419 100644
--- a/host/lib/ic_reg_maps/CMakeLists.txt
+++ b/host/lib/ic_reg_maps/CMakeLists.txt
@@ -2,7 +2,7 @@
# Copyright 2010-2013 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
#
########################################################################
@@ -137,4 +137,9 @@ LIBUHD_PYTHON_GEN_SOURCE(
${CMAKE_CURRENT_BINARY_DIR}/magnesium_cpld_regs.hpp
)
+LIBUHD_PYTHON_GEN_SOURCE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/gen_rhcpld_regs.py
+ ${CMAKE_CURRENT_BINARY_DIR}/rhodium_cpld_regs.hpp
+)
+
SET(LIBUHD_PYTHON_GEN_SOURCE_DEPS)
diff --git a/host/lib/ic_reg_maps/gen_rhcpld_regs.py b/host/lib/ic_reg_maps/gen_rhcpld_regs.py
new file mode 100644
index 000000000..1ce88fdcb
--- /dev/null
+++ b/host/lib/ic_reg_maps/gen_rhcpld_regs.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Register map for the Rhodium CPLD. This is controlled via SPI.
+"""
+
+########################################################################
+# Template for raw text data describing registers
+# name addr[bit range inclusive] default optional enums
+########################################################################
+REGS_TMPL="""\
+########################################################################
+## address 5 Scratch
+########################################################################
+scratch 5[0:15] 0x0
+########################################################################
+## address 6 Rx Band Select
+########################################################################
+reg6_reserved0 6[0] 0x0
+rx_sw1 6[1:2] 1 cal_loopback, rx2, isolation, txrx
+rx_sw2_sw7 6[3] 0 lowband, highband
+rx_sw3 6[4:5] 0 rx_sw4, filter7, filter6, filter5
+rx_sw4_sw5 6[6:9] 1 filter2=1, filter1=2, filter4=4, filter3=8
+rx_sw6 6[10:11] 3 filter5, filter6, filter7, rx_sw5
+rx_gain_tbl_sel 6[12] 0 lowband, highband
+reg6_reserved1 6[13:15] 0x0
+########################################################################
+## address 7 Tx Band Select
+########################################################################
+reg7_reserved0 7[0:1] 0x0
+tx_sw1 7[2:3] 3 lowband_if, tx_sw2, cal_loopback, isolation
+tx_sw2 7[4:5] 0 tx_sw3, filter7, filter6, filter5
+tx_sw3_sw4 7[6:9] 1 filter2=1, filter1=2, filter4=4, filter3=8
+tx_sw5 7[10:11] 0 filter5, filter6, filter7, tx_sw4
+tx_gain_tbl_sel 7[12] 0 lowband, highband
+reg7_reserved1 7[13:15] 0x0
+########################################################################
+## address 8 Misc Switches
+########################################################################
+reg8_reserved0 8[0:2] 0x0
+cal_iso_sw 8[3] 0 isolation, cal_loopback
+tx_hb_lb_sel 8[4] 0 lowband, highband
+reg8_reserved1 8[5] 0
+tx_lo_input_sel 8[6] 0 internal, external
+rx_hb_lb_sel 8[7] 0 lowband, highband
+reg8_reserved2 8[8] 0
+rx_lo_input_sel 8[9] 1 external, internal
+rx_demod_adj 8[10:11] 0 res_open=0, res_200_ohm=1, res_1500_ohm=2
+tx_lo_filter_sel 8[12:13] 3 0_9ghz_lpf, 5_85ghz_lpf, 2_25ghz_lpf, isolation
+rx_lo_filter_sel 8[14:15] 3 0_9ghz_lpf, 5_85ghz_lpf, 2_25ghz_lpf, isolation
+"""
+
+########################################################################
+# Template for methods in the body of the struct
+########################################################################
+BODY_TMPL="""\
+uint32_t get_reg(uint8_t addr){
+ uint32_t reg = 0;
+ switch(addr){
+ % for addr in range(5, 14+1):
+ case ${addr}:
+ % for reg in filter(lambda r: r.get_addr() == addr, regs):
+ reg |= (uint32_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()};
+ % endfor
+ break;
+ % endfor
+ }
+ return reg;
+}
+
+std::set<size_t> get_all_addrs()
+{
+ std::set<size_t> addrs;
+ % for reg in regs:
+ // Hopefully, compilers will optimize out this mess...
+ addrs.insert(${reg.get_addr()});
+ % endfor
+ return addrs;
+}
+"""
+
+if __name__ == '__main__':
+ import common; common.generate(
+ name='rhodium_cpld_regs',
+ regs_tmpl=REGS_TMPL,
+ body_tmpl=BODY_TMPL,
+ file=__file__,
+ )
+
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 <uhd/utils/math.hpp>
+
+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<double>(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<double>(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 <vector>
+#include <string>
+#include <cstddef>
+
+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<std::string> RHODIUM_RX_ANTENNAS = {
+ "TX/RX", "RX2", "CAL", "TERM"
+};
+
+static const std::vector<std::string> 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 <uhd/utils/math.hpp>
+#include <uhd/utils/log.hpp>
+#include <boost/format.hpp>
+#include <chrono>
+
+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<std::mutex> 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<std::mutex> l(_set_mutex);
+ return _read_reg_fn(addr);
+}
+
+void rhodium_cpld_ctrl::set_scratch(const uint16_t val)
+{
+ std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> l(_set_mutex);
+
+ auto freq_compare = fp_compare_epsilon<double>(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<std::mutex> l(_set_mutex);
+
+ auto freq_compare = fp_compare_epsilon<double>(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<std::mutex> 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<std::mutex> 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<size_t>();
+ 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 <uhd/types/serial.hpp>
+#include <uhd/types/direction.hpp>
+#include <mutex>
+#include <memory>
+
+//! 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<rhodium_cpld_ctrl>;
+ //! SPI write function: Can take a SPI transaction and clock it out
+ using write_spi_t = std::function<void(uint32_t)>;
+ //! SPI read function: Return SPI
+ using read_spi_t = std::function<uint32_t(uint32_t)>;
+
+ 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<void(uint32_t, uint32_t)>;
+ //! Write function: Send bits directly to CPLD
+ using write_raw_fn_t = std::function<void(uint32_t)>;
+ //! Read function: Return value given address
+ using read_reg_fn_t = std::function<uint32_t(uint32_t)>;
+
+ //! 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<uint32_t> _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 <uhd/utils/log.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace uhd::rfnoc;
+
+namespace {
+ const char* rx_band_to_log(rhodium_radio_ctrl_impl::rx_band rx_band)
+ {
+ switch (rx_band)
+ {
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_0:
+ return "0";
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_1:
+ return "1";
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_2:
+ return "2";
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_3:
+ return "3";
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_4:
+ return "4";
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_5:
+ return "5";
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_6:
+ return "6";
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_7:
+ return "7";
+ case rhodium_radio_ctrl_impl::rx_band::RX_BAND_INVALID:
+ return "INVALID";
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ }
+
+ const char* tx_band_to_log(rhodium_radio_ctrl_impl::tx_band tx_band)
+ {
+ switch (tx_band)
+ {
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_0:
+ return "0";
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_1:
+ return "1";
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_2:
+ return "2";
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_3:
+ return "3";
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_4:
+ return "4";
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_5:
+ return "5";
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_6:
+ return "6";
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_7:
+ return "7";
+ case rhodium_radio_ctrl_impl::tx_band::TX_BAND_INVALID:
+ return "INVALID";
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ }
+}
+
+void rhodium_radio_ctrl_impl::_update_rx_freq_switches(
+ const double freq
+) {
+ UHD_LOG_TRACE(unique_id(),
+ "Update all RX freq related switches. f=" << freq << " Hz, "
+ );
+ const auto band = _map_freq_to_rx_band(freq);
+ UHD_LOG_TRACE(unique_id(),
+ "Selected band " << rx_band_to_log(band));
+
+ // select values for lowband/highband switches
+ const bool is_lowband = (band == rx_band::RX_BAND_0);
+ auto rx_sw2_sw7 = is_lowband ?
+ rhodium_cpld_ctrl::RX_SW2_SW7_LOWBANDFILTERBANK :
+ rhodium_cpld_ctrl::RX_SW2_SW7_HIGHBANDFILTERBANK;
+ auto rx_hb_lb_sel = is_lowband ?
+ rhodium_cpld_ctrl::RX_HB_LB_SEL_LOWBAND :
+ rhodium_cpld_ctrl::RX_HB_LB_SEL_HIGHBAND;
+
+ // select values for filter bank switches
+ rhodium_cpld_ctrl::rx_sw3_t rx_sw3;
+ rhodium_cpld_ctrl::rx_sw4_sw5_t rx_sw4_sw5;
+ rhodium_cpld_ctrl::rx_sw6_t rx_sw6;
+ switch (band)
+ {
+ case rx_band::RX_BAND_0:
+ // Low band doesn't use the filter banks, use configuration for band 1
+ case rx_band::RX_BAND_1:
+ rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4;
+ rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ;
+ rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5;
+ break;
+ case rx_band::RX_BAND_2:
+ rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4;
+ rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0760X1100MHZ;
+ rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5;
+ break;
+ case rx_band::RX_BAND_3:
+ rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4;
+ rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1100X1410MHZ;
+ rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5;
+ break;
+ case rx_band::RX_BAND_4:
+ rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4;
+ rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1410X2050MHZ;
+ rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5;
+ break;
+ case rx_band::RX_BAND_5:
+ rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER2050X3000MHZ;
+ rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ;
+ rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER2050X3000MHZ;
+ break;
+ case rx_band::RX_BAND_6:
+ rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER3000X4500MHZ;
+ rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ;
+ rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER3000X4500MHZ;
+ break;
+ case rx_band::RX_BAND_7:
+ rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER4500X6000MHZ;
+ rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ;
+ rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER4500X6000MHZ;
+ break;
+ case rx_band::RX_BAND_INVALID:
+ throw uhd::runtime_error(str(boost::format(
+ "Cannot map RX frequency to band: %f") % freq));
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ // commit settings to cpld
+ _cpld->set_rx_switches(
+ rx_sw2_sw7,
+ rx_sw3,
+ rx_sw4_sw5,
+ rx_sw6,
+ rx_hb_lb_sel
+ );
+}
+
+void rhodium_radio_ctrl_impl::_update_tx_freq_switches(
+ const double freq
+){
+ UHD_LOG_TRACE(unique_id(),
+ "Update all TX freq related switches. f=" << freq << " Hz, "
+ );
+
+ const auto band = _map_freq_to_tx_band(freq);
+
+ UHD_LOG_TRACE(unique_id(),
+ "Selected band " << tx_band_to_log(band));
+
+ // select values for lowband/highband switches
+ const bool is_lowband = (band == tx_band::TX_BAND_0);
+ auto tx_hb_lb_sel = is_lowband ?
+ rhodium_cpld_ctrl::TX_HB_LB_SEL_LOWBAND :
+ rhodium_cpld_ctrl::TX_HB_LB_SEL_HIGHBAND;
+
+ // select values for filter bank switches
+ rhodium_cpld_ctrl::tx_sw2_t tx_sw2;
+ rhodium_cpld_ctrl::tx_sw3_sw4_t tx_sw3_sw4;
+ rhodium_cpld_ctrl::tx_sw5_t tx_sw5;
+ switch (band)
+ {
+ case tx_band::TX_BAND_0:
+ // Low band doesn't use the filter banks, use configuration for band 1
+ case tx_band::TX_BAND_1:
+ tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3;
+ tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ;
+ tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4;
+ break;
+ case tx_band::TX_BAND_2:
+ tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3;
+ tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1000MHZ;
+ tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4;
+ break;
+ case tx_band::TX_BAND_3:
+ tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3;
+ tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1350MHZ;
+ tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4;
+ break;
+ case tx_band::TX_BAND_4:
+ tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3;
+ tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1900MHZ;
+ tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4;
+ break;
+ case tx_band::TX_BAND_5:
+ tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP3000MHZ;
+ tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ;
+ tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP3000MHZ;
+ break;
+ case tx_band::TX_BAND_6:
+ tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP4100MHZ;
+ tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ;
+ tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP4100MHZ;
+ break;
+ case tx_band::TX_BAND_7:
+ tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP6000MHZ;
+ tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ;
+ tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP6000MHZ;
+ break;
+ case tx_band::TX_BAND_INVALID:
+ throw uhd::runtime_error(str(boost::format(
+ "Cannot map TX frequency to band: %f") % freq));
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ // commit settings to cpld
+ _cpld->set_tx_switches(
+ tx_sw2,
+ tx_sw3_sw4,
+ tx_sw5,
+ tx_hb_lb_sel
+ );
+
+ // 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 <uhdlib/utils/narrow.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/rfnoc/node_ctrl_base.hpp>
+#include <uhd/transport/chdr.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/types/eeprom.hpp>
+#include <uhd/exception.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/format.hpp>
+#include <sstream>
+#include <cmath>
+#include <cstdlib>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace uhd::rfnoc;
+using namespace uhd::math::fp_compare;
+
+namespace {
+ constexpr char RX_FE_CONNECTION_LOWBAND[] = "QI";
+ constexpr char RX_FE_CONNECTION_HIGHBAND[] = "IQ";
+ constexpr char TX_FE_CONNECTION_LOWBAND[] = "QI";
+ constexpr char TX_FE_CONNECTION_HIGHBAND[] = "IQ";
+
+ constexpr 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<device_addr_t>(tune_arg_path).get();
+ }
+}
+
+
+/******************************************************************************
+ * Structors
+ *****************************************************************************/
+UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(rhodium_radio_ctrl)
+{
+ UHD_LOG_TRACE(unique_id(), "Entering rhodium_radio_ctrl_impl ctor...");
+ const char radio_slot_name[] = {'A', 'B'};
+ _radio_slot = radio_slot_name[get_block_id().get_block_count()];
+ _rpc_prefix =
+ (_radio_slot == "A") ? "db_0_" : "db_1_";
+ UHD_LOG_TRACE(unique_id(), "Radio slot: " << _radio_slot);
+}
+
+rhodium_radio_ctrl_impl::~rhodium_radio_ctrl_impl()
+{
+ UHD_LOG_TRACE(unique_id(), "rhodium_radio_ctrl_impl::dtor() ");
+}
+
+
+/******************************************************************************
+ * API Calls
+ *****************************************************************************/
+double rhodium_radio_ctrl_impl::set_rate(double /* 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<std::string>(
+ "spur_dodging",
+ (_tree->access<std::string>(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<double>(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<size_t>(fe) == 0);
+ return 0;
+}
+
+std::string rhodium_radio_ctrl_impl::get_dboard_fe_from_chan(
+ const size_t chan,
+ const direction_t /* dir */
+) {
+ UHD_ASSERT_THROW(chan == 0);
+ return "0";
+}
+
+void rhodium_radio_ctrl_impl::set_rpc_client(
+ uhd::rpc_client::sptr rpcc,
+ const uhd::device_addr_t &block_args
+) {
+ _rpcc = rpcc;
+ _block_args = block_args;
+
+ // Get and verify the MCR before _init_peripherals, which will use this value
+ // Note: MCR gets set during the init() call (prior to this), which takes
+ // in arguments from the device args. So if block_args contains a
+ // master_clock_rate key, then it should better be whatever the device is
+ // configured to do.
+ _master_clock_rate = _rpcc->request_with_token<double>(_rpc_prefix + "get_master_clock_rate");
+ if (block_args.cast<double>("master_clock_rate", _master_clock_rate)
+ != _master_clock_rate) {
+ throw uhd::runtime_error(str(
+ boost::format("Master clock rate mismatch. Device returns %f MHz, "
+ "but should have been %f MHz.")
+ % (_master_clock_rate / 1e6)
+ % (block_args.cast<double>(
+ "master_clock_rate", _master_clock_rate) / 1e6)
+ ));
+ }
+ UHD_LOG_DEBUG(unique_id(),
+ "Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz.");
+ radio_ctrl_impl::set_rate(_master_clock_rate);
+
+ UHD_LOG_TRACE(unique_id(), "Checking for existence of Rhodium DB in slot " << _radio_slot);
+ const auto dboard_info = _rpcc->request<std::vector<std::map<std::string, std::string>>>("get_dboard_info");
+
+ // There is a bug that if only one DB is plugged into slot B the vector
+ // will only have 1 element but not be correlated to slot B at all.
+ // For now, we assume a 1 element array means the DB is in slot A.
+ if (dboard_info.size() <= get_block_id().get_block_count())
+ {
+ UHD_LOG_DEBUG(unique_id(), "No DB detected in slot " << _radio_slot);
+ // Name and master clock rate are needed for RFNoC init, so set the
+ // name now and let this function continue to set the MCR
+ _tree->subtree(fs_path("dboards") / _radio_slot / "tx_frontends" / "0")
+ ->create<std::string>("name").set("Unknown");
+ _tree->subtree(fs_path("dboards") / _radio_slot / "rx_frontends" / "0")
+ ->create<std::string>("name").set("Unknown");
+ }
+ else {
+ 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 <uhdlib/usrp/common/lmx2592.hpp>
+#include <uhdlib/usrp/cores/gpio_atr_3000.hpp>
+#include <uhdlib/rfnoc/rpc_block_ctrl.hpp>
+#include <uhdlib/rfnoc/radio_ctrl_impl.hpp>
+#include <uhdlib/usrp/cores/rx_frontend_core_3000.hpp>
+#include <uhdlib/usrp/cores/tx_frontend_core_200.hpp>
+#include <uhd/types/serial.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <uhd/usrp/gpio_defs.hpp>
+#include <mutex>
+
+namespace uhd {
+ namespace rfnoc {
+
+/*! \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<rhodium_radio_ctrl_impl> 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<std::string> get_tx_lo_names(const size_t chan);
+ std::vector<std::string> get_rx_lo_names(const size_t chan);
+ std::vector<std::string> get_tx_lo_sources(const std::string& name, const size_t chan);
+ std::vector<std::string> get_rx_lo_sources(const std::string& name, const size_t chan);
+ freq_range_t get_tx_lo_freq_range(const std::string& name, const size_t chan);
+ freq_range_t get_rx_lo_freq_range(const std::string& name, const size_t chan);
+
+ // 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<rhodium_cpld_ctrl> _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<direction_t,double> _desired_rf_freq = { {RX_DIRECTION, 2.44e9}, {TX_DIRECTION, 2.44e9} };
+ //! Frequency at which gain setting was last applied. The CPLD requires a new gain
+ // control write when switching between lowband and highband frequencies, so save
+ // the frequency when sending a gain control command.
+ 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<direction_t,std::string> _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 <uhdlib/usrp/cores/spi_core_3000.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/types/eeprom.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/transport/chdr.hpp>
+#include <vector>
+#include <string>
+
+using namespace uhd;
+using namespace uhd::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<std::string> RHODIUM_GP_OPTIONS = {
+ "default"
+ };
+
+ //! Returns the SPI config used by the CPLD
+ spi_config_t _get_cpld_spi_config() {
+ spi_config_t spi_config;
+ spi_config.use_custom_divider = true;
+ spi_config.divider = 10;
+ spi_config.mosi_edge = spi_config_t::EDGE_RISE;
+ spi_config.miso_edge = spi_config_t::EDGE_FALL;
+
+ return spi_config;
+ }
+
+ //! Returns the SPI config used by the TX LO
+ spi_config_t _get_tx_lo_spi_config() {
+ spi_config_t spi_config;
+ spi_config.use_custom_divider = true;
+ spi_config.divider = 10;
+ spi_config.mosi_edge = spi_config_t::EDGE_RISE;
+ spi_config.miso_edge = spi_config_t::EDGE_FALL;
+
+ return spi_config;
+ }
+
+ //! Returns the SPI config used by the RX LO
+ spi_config_t _get_rx_lo_spi_config() {
+ spi_config_t spi_config;
+ spi_config.use_custom_divider = true;
+ spi_config.divider = 10;
+ spi_config.mosi_edge = spi_config_t::EDGE_RISE;
+ spi_config.miso_edge = spi_config_t::EDGE_FALL;
+
+ return spi_config;
+ }
+
+ std::function<void(uint32_t)> _generate_write_spi(
+ uhd::spi_iface::sptr spi,
+ slave_select_t slave,
+ spi_config_t config
+ ) {
+ return [spi, slave, config](const uint32_t transaction) {
+ spi->write_spi(slave, config, transaction, 24);
+ };
+ }
+
+ std::function<uint32_t(uint32_t)> _generate_read_spi(
+ uhd::spi_iface::sptr spi,
+ slave_select_t slave,
+ spi_config_t config
+ ) {
+ return [spi, slave, config](const uint32_t transaction) {
+ return spi->read_spi(slave, config, transaction, 24);
+ };
+ }
+}
+
+void rhodium_radio_ctrl_impl::_init_defaults()
+{
+ UHD_LOG_TRACE(unique_id(), "Initializing defaults...");
+ const size_t num_rx_chans = get_output_ports().size();
+ const size_t num_tx_chans = get_input_ports().size();
+
+ UHD_LOG_TRACE(unique_id(),
+ "Num TX chans: " << num_tx_chans
+ << " Num RX chans: " << num_rx_chans);
+
+ for (size_t chan = 0; chan < num_rx_chans; chan++) {
+ radio_ctrl_impl::set_rx_frequency(RHODIUM_DEFAULT_FREQ, chan);
+ radio_ctrl_impl::set_rx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan);
+ radio_ctrl_impl::set_rx_antenna(RHODIUM_DEFAULT_RX_ANTENNA, chan);
+ radio_ctrl_impl::set_rx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan);
+ }
+
+ for (size_t chan = 0; chan < num_tx_chans; chan++) {
+ radio_ctrl_impl::set_tx_frequency(RHODIUM_DEFAULT_FREQ, chan);
+ radio_ctrl_impl::set_tx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan);
+ radio_ctrl_impl::set_tx_antenna(RHODIUM_DEFAULT_TX_ANTENNA, chan);
+ radio_ctrl_impl::set_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<size_t>("mtu/recv").get() - max_bytes_header)
+ / (2 * sizeof(int16_t));
+ UHD_LOG_DEBUG(unique_id(),
+ "Setting default spp to " << default_spp);
+ _tree->access<int>(get_arg_path("spp") / "value").set(default_spp);
+}
+
+void rhodium_radio_ctrl_impl::_init_peripherals()
+{
+ UHD_LOG_TRACE(unique_id(), "Initializing peripherals...");
+
+ UHD_LOG_TRACE(unique_id(), "Initializing SPI core...");
+ _spi = spi_core_3000::make(_get_ctrl(0),
+ regs::sr_addr(regs::SPI),
+ regs::rb_addr(regs::RB_SPI)
+ );
+
+ UHD_LOG_TRACE(unique_id(), "Initializing CPLD...");
+ _cpld = std::make_shared<rhodium_cpld_ctrl>(
+ _generate_write_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config()),
+ _generate_read_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config()));
+
+ UHD_LOG_TRACE(unique_id(), "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<std::string>(tx_fe_path / "name")
+ .set(str(boost::format("Rhodium")))
+ ;
+ subtree->create<std::string>(tx_fe_path / "connection")
+ .add_coerced_subscriber([this](const std::string& conn){
+ this->_set_tx_fe_connection(conn);
+ })
+ .set_publisher([this](){
+ return this->_get_tx_fe_connection();
+ })
+ ;
+ subtree->create<device_addr_t>(tx_fe_path / "tune_args")
+ .set(device_addr_t())
+ ;
+ // RX Standard attributes
+ subtree->create<std::string>(rx_fe_path / "name")
+ .set(str(boost::format("Rhodium")))
+ ;
+ subtree->create<std::string>(rx_fe_path / "connection")
+ .add_coerced_subscriber([this](const std::string& conn){
+ this->_set_rx_fe_connection(conn);
+ })
+ .set_publisher([this](){
+ return this->_get_rx_fe_connection();
+ })
+ ;
+ subtree->create<device_addr_t>(rx_fe_path / "tune_args")
+ .set(device_addr_t())
+ ;
+ // TX Antenna
+ subtree->create<std::string>(tx_fe_path / "antenna" / "value")
+ .add_coerced_subscriber([this, chan_idx](const std::string &ant){
+ this->set_tx_antenna(ant, chan_idx);
+ })
+ .set_publisher([this, chan_idx](){
+ return this->get_tx_antenna(chan_idx);
+ })
+ ;
+ subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options")
+ .set({RHODIUM_DEFAULT_TX_ANTENNA})
+ .add_coerced_subscriber([](const std::vector<std::string> &){
+ throw uhd::runtime_error(
+ "Attempting to update antenna options!");
+ })
+ ;
+ // RX Antenna
+ subtree->create<std::string>(rx_fe_path / "antenna" / "value")
+ .add_coerced_subscriber([this, chan_idx](const std::string &ant){
+ this->set_rx_antenna(ant, chan_idx);
+ })
+ .set_publisher([this, chan_idx](){
+ return this->get_rx_antenna(chan_idx);
+ })
+ ;
+ subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options")
+ .set(RHODIUM_RX_ANTENNAS)
+ .add_coerced_subscriber([](const std::vector<std::string> &){
+ throw uhd::runtime_error(
+ "Attempting to update antenna options!");
+ })
+ ;
+ // TX frequency
+ subtree->create<double>(tx_fe_path / "freq" / "value")
+ .set_coercer([this, chan_idx](const double freq){
+ return this->set_tx_frequency(freq, chan_idx);
+ })
+ .set_publisher([this, chan_idx](){
+ return this->get_tx_frequency(chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(tx_fe_path / "freq" / "range")
+ .set(meta_range_t(RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ, 1.0))
+ .add_coerced_subscriber([](const meta_range_t &){
+ throw uhd::runtime_error(
+ "Attempting to update freq range!");
+ })
+ ;
+ // RX frequency
+ subtree->create<double>(rx_fe_path / "freq" / "value")
+ .set_coercer([this, chan_idx](const double freq){
+ return this->set_rx_frequency(freq, chan_idx);
+ })
+ .set_publisher([this, chan_idx](){
+ return this->get_rx_frequency(chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(rx_fe_path / "freq" / "range")
+ .set(meta_range_t(RHODIUM_MIN_FREQ, RHODIUM_MAX_FREQ, 1.0))
+ .add_coerced_subscriber([](const meta_range_t &){
+ throw uhd::runtime_error(
+ "Attempting to update freq range!");
+ })
+ ;
+ // TX bandwidth
+ subtree->create<double>(tx_fe_path / "bandwidth" / "value")
+ .set_coercer([this, chan_idx](const double bw){
+ return this->set_tx_bandwidth(bw, chan_idx);
+ })
+ .set_publisher([this, chan_idx](){
+ return this->get_tx_bandwidth(chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range")
+ .set(meta_range_t(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<double>(rx_fe_path / "bandwidth" / "value")
+ .set_coercer([this, chan_idx](const double bw){
+ return this->set_rx_bandwidth(bw, chan_idx);
+ })
+ .set_publisher([this, chan_idx](){
+ return this->get_rx_bandwidth(chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range")
+ .set(meta_range_t(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<double>(tx_fe_path / "gains" / "all" / "value")
+ .set_coercer([this, chan_idx](const double gain){
+ return this->set_tx_gain(gain, chan_idx);
+ })
+ .set_publisher([this, chan_idx](){
+ return radio_ctrl_impl::get_tx_gain(chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(tx_fe_path / "gains" / "all" / "range")
+ .add_coerced_subscriber([](const meta_range_t &){
+ throw uhd::runtime_error(
+ "Attempting to update gain range!");
+ })
+ .set_publisher([](){
+ return rhodium_radio_ctrl_impl::_get_gain_range(TX_DIRECTION);
+ })
+ ;
+
+ subtree->create<std::vector<std::string>>(tx_fe_path / "gains/all/profile/options")
+ .set(RHODIUM_GP_OPTIONS);
+
+ subtree->create<std::string>(tx_fe_path / "gains/all/profile/value")
+ .set_coercer([this](const std::string& profile){
+ std::string return_profile = profile;
+ if (!uhd::has(RHODIUM_GP_OPTIONS, profile))
+ {
+ return_profile = "default";
+ }
+ _gain_profile[TX_DIRECTION] = return_profile;
+ return return_profile;
+ })
+ .set_publisher([this](){
+ return _gain_profile[TX_DIRECTION];
+ })
+ ;
+
+ // RX gains
+ subtree->create<double>(rx_fe_path / "gains" / "all" / "value")
+ .set_coercer([this, chan_idx](const double gain){
+ return this->set_rx_gain(gain, chan_idx);
+ })
+ .set_publisher([this, chan_idx](){
+ return radio_ctrl_impl::get_rx_gain(chan_idx);
+ })
+ ;
+
+ subtree->create<meta_range_t>(rx_fe_path / "gains" / "all" / "range")
+ .add_coerced_subscriber([](const meta_range_t &){
+ throw uhd::runtime_error(
+ "Attempting to update gain range!");
+ })
+ .set_publisher([](){
+ return rhodium_radio_ctrl_impl::_get_gain_range(RX_DIRECTION);
+ })
+ ;
+
+ subtree->create<std::vector<std::string> >(rx_fe_path / "gains/all/profile/options")
+ .set(RHODIUM_GP_OPTIONS);
+
+ subtree->create<std::string>(rx_fe_path / "gains/all/profile/value")
+ .set_coercer([this](const std::string& profile){
+ std::string return_profile = profile;
+ if (!uhd::has(RHODIUM_GP_OPTIONS, profile))
+ {
+ return_profile = "default";
+ }
+ _gain_profile[RX_DIRECTION] = return_profile;
+ return return_profile;
+ })
+ .set_publisher([this](){
+ return _gain_profile[RX_DIRECTION];
+ })
+ ;
+
+ // TX LO lock sensor
+ subtree->create<sensor_value_t>(tx_fe_path / "sensors" / "lo_locked")
+ .set(sensor_value_t("all_los", false, "locked", "unlocked"))
+ .add_coerced_subscriber([](const sensor_value_t &){
+ throw uhd::runtime_error(
+ "Attempting to write to sensor!");
+ })
+ .set_publisher([this](){
+ return sensor_value_t(
+ "all_los",
+ this->get_lo_lock_status(TX_DIRECTION),
+ "locked", "unlocked"
+ );
+ })
+ ;
+ // RX LO lock sensor
+ subtree->create<sensor_value_t>(rx_fe_path / "sensors" / "lo_locked")
+ .set(sensor_value_t("all_los", false, "locked", "unlocked"))
+ .add_coerced_subscriber([](const sensor_value_t &){
+ throw uhd::runtime_error(
+ "Attempting to write to sensor!");
+ })
+ .set_publisher([this](){
+ return sensor_value_t(
+ "all_los",
+ this->get_lo_lock_status(RX_DIRECTION),
+ "locked", "unlocked"
+ );
+ })
+ ;
+ //LO Specific
+ //RX LO
+ //RX LO1 Frequency
+ subtree->create<double>(rx_fe_path / "los"/RHODIUM_LO1/"freq/value")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_freq(RHODIUM_LO1, chan_idx);
+ })
+ .set_coercer([this,chan_idx](const double freq){
+ return this->set_rx_lo_freq(freq, RHODIUM_LO1, chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(rx_fe_path / "los"/RHODIUM_LO1/"freq/range")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_freq_range(RHODIUM_LO1, chan_idx);
+ })
+ ;
+ //RX LO1 Source
+ subtree->create<std::vector<std::string>>(rx_fe_path / "los"/RHODIUM_LO1/"source/options")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_sources(RHODIUM_LO1, chan_idx);
+ })
+ ;
+ subtree->create<std::string>(rx_fe_path / "los"/RHODIUM_LO1/"source/value")
+ .add_coerced_subscriber([this,chan_idx](std::string src){
+ this->set_rx_lo_source(src, RHODIUM_LO1,chan_idx);
+ })
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_source(RHODIUM_LO1, chan_idx);
+ })
+ ;
+ //RX LO1 Export
+ subtree->create<bool>(rx_fe_path / "los"/RHODIUM_LO1/"export")
+ .add_coerced_subscriber([this,chan_idx](bool enabled){
+ this->set_rx_lo_export_enabled(enabled, RHODIUM_LO1, chan_idx);
+ })
+ ;
+ //RX LO1 Gain
+ subtree->create<double>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "value")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_gain(RHODIUM_LO1, chan_idx);
+ })
+ .set_coercer([this,chan_idx](const double gain){
+ return this->set_rx_lo_gain(gain, RHODIUM_LO1, chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "range")
+ .set_publisher([](){
+ return rhodium_radio_ctrl_impl::_get_lo_gain_range();
+ })
+ .add_coerced_subscriber([](const meta_range_t &){
+ throw uhd::runtime_error("Attempting to update LO gain range!");
+ })
+ ;
+ //RX LO1 Output Power
+ subtree->create<double>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "value")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_power(RHODIUM_LO1, chan_idx);
+ })
+ .set_coercer([this,chan_idx](const double gain){
+ return this->set_rx_lo_power(gain, RHODIUM_LO1, chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(rx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "range")
+ .set_publisher([](){
+ return rhodium_radio_ctrl_impl::_get_lo_power_range();
+ })
+ .add_coerced_subscriber([](const meta_range_t &){
+ throw uhd::runtime_error("Attempting to update LO output power range!");
+ })
+ ;
+ //RX LO2 Frequency
+ subtree->create<double>(rx_fe_path / "los"/RHODIUM_LO2/"freq/value")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_freq(RHODIUM_LO2, chan_idx);
+ })
+ .set_coercer([this,chan_idx](double freq){
+ return this->set_rx_lo_freq(freq, RHODIUM_LO2, chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(rx_fe_path / "los"/RHODIUM_LO2/"freq/range")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_freq_range(RHODIUM_LO2, chan_idx);
+ })
+ ;
+ //RX LO2 Source
+ subtree->create<std::vector<std::string>>(rx_fe_path / "los"/RHODIUM_LO2/"source/options")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_sources(RHODIUM_LO2, chan_idx);
+ })
+ ;
+ subtree->create<std::string>(rx_fe_path / "los"/RHODIUM_LO2/"source/value")
+ .add_coerced_subscriber([this,chan_idx](std::string src){
+ this->set_rx_lo_source(src, RHODIUM_LO2, chan_idx);
+ })
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_source(RHODIUM_LO2, chan_idx);
+ })
+ ;
+ //RX LO2 Export
+ subtree->create<bool>(rx_fe_path / "los"/RHODIUM_LO2/"export")
+ .add_coerced_subscriber([this,chan_idx](bool enabled){
+ this->set_rx_lo_export_enabled(enabled, RHODIUM_LO2, chan_idx);
+ });
+ //TX LO
+ //TX LO1 Frequency
+ subtree->create<double>(tx_fe_path / "los"/RHODIUM_LO1/"freq/value ")
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_freq(RHODIUM_LO1, chan_idx);
+ })
+ .set_coercer([this,chan_idx](double freq){
+ return this->set_tx_lo_freq(freq, RHODIUM_LO1, chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(tx_fe_path / "los"/RHODIUM_LO1/"freq/range")
+ .set_publisher([this,chan_idx](){
+ return this->get_rx_lo_freq_range(RHODIUM_LO1, chan_idx);
+ })
+ ;
+ //TX LO1 Source
+ subtree->create<std::vector<std::string>>(tx_fe_path / "los"/RHODIUM_LO1/"source/options")
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_sources(RHODIUM_LO1, chan_idx);
+ })
+ ;
+ subtree->create<std::string>(tx_fe_path / "los"/RHODIUM_LO1/"source/value")
+ .add_coerced_subscriber([this,chan_idx](std::string src){
+ this->set_tx_lo_source(src, RHODIUM_LO1, chan_idx);
+ })
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_source(RHODIUM_LO1, chan_idx);
+ })
+ ;
+ //TX LO1 Export
+ subtree->create<bool>(tx_fe_path / "los"/RHODIUM_LO1/"export")
+ .add_coerced_subscriber([this,chan_idx](bool enabled){
+ this->set_tx_lo_export_enabled(enabled, RHODIUM_LO1, chan_idx);
+ })
+ ;
+ //TX LO1 Gain
+ subtree->create<double>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "value")
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_gain(RHODIUM_LO1, chan_idx);
+ })
+ .set_coercer([this,chan_idx](const double gain){
+ return this->set_tx_lo_gain(gain, RHODIUM_LO1, chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_GAIN / "range")
+ .set_publisher([](){
+ return rhodium_radio_ctrl_impl::_get_lo_gain_range();
+ })
+ .add_coerced_subscriber([](const meta_range_t &){
+ throw uhd::runtime_error("Attempting to update LO gain range!");
+ })
+ ;
+ //TX LO1 Output Power
+ subtree->create<double>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "value")
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_power(RHODIUM_LO1, chan_idx);
+ })
+ .set_coercer([this,chan_idx](const double gain){
+ return this->set_tx_lo_power(gain, RHODIUM_LO1, chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(tx_fe_path / "los" /RHODIUM_LO1/ "gains" / RHODIUM_LO_POWER / "range")
+ .set_publisher([](){
+ return rhodium_radio_ctrl_impl::_get_lo_power_range();
+ })
+ .add_coerced_subscriber([](const meta_range_t &){
+ throw uhd::runtime_error("Attempting to update LO output power range!");
+ })
+ ;
+ //TX LO2 Frequency
+ subtree->create<double>(tx_fe_path / "los"/RHODIUM_LO2/"freq/value")
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_freq(RHODIUM_LO2, chan_idx);
+ })
+ .set_coercer([this,chan_idx](double freq){
+ return this->set_tx_lo_freq(freq, RHODIUM_LO2, chan_idx);
+ })
+ ;
+ subtree->create<meta_range_t>(tx_fe_path / "los"/RHODIUM_LO2/"freq/range")
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_freq_range(RHODIUM_LO2,chan_idx);
+ })
+ ;
+ //TX LO2 Source
+ subtree->create<std::vector<std::string>>(tx_fe_path / "los"/RHODIUM_LO2/"source/options")
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_sources(RHODIUM_LO2, chan_idx);
+ })
+ ;
+ subtree->create<std::string>(tx_fe_path / "los"/RHODIUM_LO2/"source/value")
+ .add_coerced_subscriber([this,chan_idx](std::string src){
+ this->set_tx_lo_source(src, RHODIUM_LO2, chan_idx);
+ })
+ .set_publisher([this,chan_idx](){
+ return this->get_tx_lo_source(RHODIUM_LO2, chan_idx);
+ })
+ ;
+ //TX LO2 Export
+ subtree->create<bool>(tx_fe_path / "los"/RHODIUM_LO2/"export")
+ .add_coerced_subscriber([this,chan_idx](bool enabled){
+ this->set_tx_lo_export_enabled(enabled, RHODIUM_LO2, chan_idx);
+ })
+ ;
+}
+
+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<eeprom_map_t>(_root_path / "eeprom")
+ .set(eeprom_map_t());
+
+ _tree->create<int>("rx_codecs" / _radio_slot / "gains");
+ _tree->create<int>("tx_codecs" / _radio_slot / "gains");
+ _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("ad9695-625");
+ _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("dac37j82");
+
+ // TODO remove this dirty hack
+ if (not _tree->exists("tick_rate"))
+ {
+ _tree->create<double>("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<std::vector<std::string>>(
+ this->_rpc_prefix + "get_sensors", trx);
+ UHD_LOG_TRACE(unique_id(),
+ "Chan " << chan_idx << ": Found "
+ << sensor_list.size() << " " << trx << " sensors.");
+ for (const auto &sensor_name : sensor_list) {
+ UHD_LOG_TRACE(unique_id(),
+ "Adding " << trx << " sensor " << sensor_name);
+ _tree->create<sensor_value_t>(fe_path / "sensors" / sensor_name)
+ .add_coerced_subscriber([](const sensor_value_t &){
+ throw uhd::runtime_error(
+ "Attempting to write to sensor!");
+ })
+ .set_publisher([this, trx, sensor_name, chan_idx](){
+ return sensor_value_t(
+ this->_rpcc->request_with_token<sensor_value_t::sensor_map_t>(
+ this->_rpc_prefix + "get_sensor",
+ trx, sensor_name, chan_idx)
+ );
+ })
+ ;
+ }
+}
+
diff --git a/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp b/host/lib/usrp/dboard/rhodium/rhodium_radio_ctrl_lo.cpp
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 <uhdlib/utils/narrow.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/exception.hpp>
+#include <boost/format.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace uhd::rfnoc;
+
+namespace {
+ constexpr int NUM_THRESHOLDS = 13;
+ constexpr std::array<double, NUM_THRESHOLDS> FREQ_THRESHOLDS =
+ {0.45e9, 0.5e9, 1e9, 1.5e9, 2e9, 2.5e9, 3e9, 3.55e9, 4e9, 4.5e9, 5e9, 5.5e9, 6e9};
+ constexpr std::array<int, NUM_THRESHOLDS> LMX_GAIN_VALUES =
+ {18, 18, 17, 17, 17, 16, 12, 11, 11, 11, 10, 13, 18};
+ constexpr std::array<int, NUM_THRESHOLDS> 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<double, double> MCR_TO_LOWBAND_IF = {
+ {200e6, 1200e6},
+ {245.76e6, 1228.8e6},
+ {250e6, 1500e6},
+ };
+
+ // validation helpers
+
+ std::vector<std::string> _get_lo_names()
+ {
+ return { RHODIUM_LO1, RHODIUM_LO2 };
+ }
+
+ void _validate_lo_name(const std::string& name, const std::string& function_name)
+ {
+ if (!uhd::has(_get_lo_names(), name)) {
+ throw uhd::value_error(str(boost::format(
+ "%s was called with an invalid LO name: %s")
+ % function_name
+ % name));
+ }
+ }
+
+ // object agnostic helpers
+ std::vector<std::string> _get_lo_sources(const std::string& name)
+ {
+ if (name == RHODIUM_LO1) {
+ return { "internal", "external" };
+ } else {
+ return { "internal" };
+ }
+ }
+}
+
+/******************************************************************************
+ * Property Getters
+ *****************************************************************************/
+
+std::vector<std::string> rhodium_radio_ctrl_impl::get_tx_lo_names(
+ const size_t chan
+) {
+ UHD_LOG_TRACE(unique_id(), "get_tx_lo_names(chan=" << chan << ")");
+ UHD_ASSERT_THROW(chan == 0);
+
+ return _get_lo_names();
+}
+
+std::vector<std::string> rhodium_radio_ctrl_impl::get_rx_lo_names(
+ const size_t chan
+) {
+ UHD_LOG_TRACE(unique_id(), "get_rx_lo_names(chan=" << chan << ")");
+ UHD_ASSERT_THROW(chan == 0);
+
+ return _get_lo_names();
+}
+
+std::vector<std::string> rhodium_radio_ctrl_impl::get_tx_lo_sources(
+ const std::string& name,
+ const size_t chan
+) {
+ UHD_LOG_TRACE(unique_id(), "get_tx_lo_sources(name=" << name << ", chan=" << chan << ")");
+ UHD_ASSERT_THROW(chan == 0);
+ _validate_lo_name(name, "get_tx_lo_sources");
+
+ return _get_lo_sources(name);
+}
+
+std::vector<std::string> rhodium_radio_ctrl_impl::get_rx_lo_sources(
+ const std::string& name,
+ const size_t chan
+) {
+ UHD_LOG_TRACE(unique_id(), "get_rx_lo_sources(name=" << name << ", chan=" << chan << ")");
+ UHD_ASSERT_THROW(chan == 0);
+ _validate_lo_name(name, "get_rx_lo_sources");
+
+ return _get_lo_sources(name);
+}
+
+freq_range_t rhodium_radio_ctrl_impl::_get_lo_freq_range(const std::string &name) const
+{
+ if (name == RHODIUM_LO1) {
+ return freq_range_t{ RHODIUM_LO1_MIN_FREQ, RHODIUM_LO1_MAX_FREQ };
+ } else {
+ // 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<uint32_t>(_get_lo_power_range().clip(power, true));
+
+ _lo_ctrl->set_output_power(lmx2592_iface::RF_OUTPUT_A, coerced_power);
+ return coerced_power;
+}
+
+double rhodium_radio_ctrl_impl::set_tx_lo_power(
+ const double power,
+ const std::string& name,
+ const size_t chan
+) {
+ UHD_LOG_TRACE(unique_id(), "set_tx_lo_power(power=" << power << ", name=" << name << ", chan=" << chan << ")");
+ UHD_ASSERT_THROW(chan == 0);
+ _validate_lo_name(name, "set_tx_lo_power");
+
+ if (name == 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<int>(std::round(gain_at_freq));
+}
+
+unsigned int rhodium_radio_ctrl_impl::_get_lo_power_setting(double freq) {
+ int index = 0;
+ while (freq > FREQ_THRESHOLDS[index+1]) {
+ index++;
+ }
+
+ const double freq_low = FREQ_THRESHOLDS[index];
+ const double freq_high = FREQ_THRESHOLDS[index+1];
+ const double power_low = LMX_GAIN_VALUES[index];
+ const double power_high = LMX_GAIN_VALUES[index+1];
+
+
+ const double slope = (power_high - power_low) / (freq_high - freq_low);
+ const double power_at_freq = power_low + (freq - freq_low) * slope;
+
+ UHD_LOG_TRACE(unique_id(), "Interpolated LMX Power is " << power_at_freq);
+ return uhd::narrow_cast<unsigned int>(std::lround(power_at_freq));
+}