aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
diff options
context:
space:
mode:
authorLars Amsel <lars.amsel@ni.com>2021-06-04 08:27:50 +0200
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-10 12:01:53 -0500
commit2a575bf9b5a4942f60e979161764b9e942699e1e (patch)
tree2f0535625c30025559ebd7494a4b9e7122550a73 /host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
parente17916220cc955fa219ae37f607626ba88c4afe3 (diff)
downloaduhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.gz
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.bz2
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.zip
uhd: Add support for the USRP X410
Co-authored-by: Lars Amsel <lars.amsel@ni.com> Co-authored-by: Michael Auchter <michael.auchter@ni.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Cristina Fuentes <cristina.fuentes-curiel@ni.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Co-authored-by: Lane Kolbly <lane.kolbly@ni.com> Co-authored-by: Max Köhler <max.koehler@ni.com> Co-authored-by: Andrew Lynch <andrew.lynch@ni.com> Co-authored-by: Grant Meyerhoff <grant.meyerhoff@ni.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Thomas Vogel <thomas.vogel@ni.com>
Diffstat (limited to 'host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp')
-rw-r--r--host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp931
1 files changed, 931 insertions, 0 deletions
diff --git a/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp b/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
new file mode 100644
index 000000000..8899f2a18
--- /dev/null
+++ b/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
@@ -0,0 +1,931 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/utils/log.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp>
+#include <chrono>
+#include <map>
+#include <thread>
+
+namespace {
+//! The time we need to wait after sending a SPI command
+const uhd::time_spec_t SPI_THROTTLE_TIME = uhd::time_spec_t(2e-6);
+} // namespace
+
+namespace uhd { namespace usrp { namespace zbx {
+
+// clang-format off
+const std::unordered_map<size_t, std::unordered_map<zbx_cpld_ctrl::dsa_type, zbx_cpld_regs_t::zbx_cpld_field_t>>
+ RX_DSA_CPLD_MAP
+{
+ {0, {
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA1},
+ {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA2},
+ {zbx_cpld_ctrl::dsa_type::DSA3A, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA3_A},
+ {zbx_cpld_ctrl::dsa_type::DSA3B, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA3_B}
+ }},
+ {1, {
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA1},
+ {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA2},
+ {zbx_cpld_ctrl::dsa_type::DSA3A, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA3_A},
+ {zbx_cpld_ctrl::dsa_type::DSA3B, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA3_B}
+ }}
+};
+
+const std::unordered_map<size_t, std::unordered_map<zbx_cpld_ctrl::dsa_type, zbx_cpld_regs_t::zbx_cpld_field_t>>
+ TX_DSA_CPLD_MAP
+{
+ {0, {
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::TX0_DSA1},
+ {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::TX0_DSA2}
+ }},
+ {1, {
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::TX1_DSA1},
+ {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::TX1_DSA2}
+ }}
+};
+// clang-format on
+
+
+const std::unordered_map<std::string, zbx_cpld_ctrl::dsa_type> zbx_cpld_ctrl::dsa_map{
+ {ZBX_GAIN_STAGE_DSA1, zbx_cpld_ctrl::dsa_type::DSA1},
+ {ZBX_GAIN_STAGE_DSA2, zbx_cpld_ctrl::dsa_type::DSA2},
+ {ZBX_GAIN_STAGE_DSA3A, zbx_cpld_ctrl::dsa_type::DSA3A},
+ {ZBX_GAIN_STAGE_DSA3B, zbx_cpld_ctrl::dsa_type::DSA3B}};
+
+zbx_cpld_ctrl::zbx_cpld_ctrl(poke_fn_type&& poke_fn,
+ peek_fn_type&& peek_fn,
+ sleep_fn_type&& sleep_fn,
+ const std::string& log_id)
+ : _poke32(std::move(poke_fn))
+ , _peek32(std::move(peek_fn))
+ , _sleep(std::move(sleep_fn))
+ , _lo_spi_offset(_regs.get_addr("SPI_READY"))
+ , _log_id(log_id)
+{
+ UHD_LOG_TRACE(_log_id, "Entering CPLD ctor...");
+ // Reset and stash the regs state. We can't assume the defaults in
+ // gen_zbx_cpld_regs.py match what's on the hardware.
+ commit(NO_CHAN, true);
+ _regs.save_state();
+}
+
+void zbx_cpld_ctrl::set_scratch(const uint32_t value)
+{
+ _regs.SCRATCH = value;
+ commit(NO_CHAN);
+}
+
+uint32_t zbx_cpld_ctrl::get_scratch()
+{
+ return _peek32(_regs.get_addr("SCRATCH"));
+}
+
+void zbx_cpld_ctrl::set_atr_mode(
+ const size_t channel, const atr_mode_target target, const atr_mode mode)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ if (target == atr_mode_target::DSA) {
+ if (channel == 0) {
+ _regs.RF0_DSA_OPTION = static_cast<zbx_cpld_regs_t::RF0_DSA_OPTION_t>(mode);
+ } else {
+ _regs.RF1_DSA_OPTION = static_cast<zbx_cpld_regs_t::RF1_DSA_OPTION_t>(mode);
+ }
+ } else {
+ if (channel == 0) {
+ _regs.RF0_OPTION = static_cast<zbx_cpld_regs_t::RF0_OPTION_t>(mode);
+ } else {
+ _regs.RF1_OPTION = static_cast<zbx_cpld_regs_t::RF1_OPTION_t>(mode);
+ }
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_sw_config(
+ const size_t channel, const atr_mode_target target, const uint8_t rf_config)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ // clang-format off
+ static const std::map<std::pair<size_t, atr_mode_target>, zbx_cpld_regs_t::zbx_cpld_field_t>
+ mode_map{
+ {{0, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF0_CONFIG },
+ {{1, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF1_CONFIG },
+ {{0, atr_mode_target::DSA }, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF0_DSA_CONFIG},
+ {{1, atr_mode_target::DSA }, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF1_DSA_CONFIG}
+ };
+ // clang-format on
+ _regs.set_field(mode_map.at({channel, target}), rf_config);
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+uint8_t zbx_cpld_ctrl::get_current_config(
+ const size_t channel, const atr_mode_target target)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ const uint16_t addr = _regs.get_addr("CURRENT_RF0_CONFIG");
+ const uint32_t config_reg = _peek32(addr);
+ _regs.set_reg(addr, config_reg);
+ _regs.save_state();
+ // clang-format off
+ static const std::map<std::pair<size_t, atr_mode_target>, zbx_cpld_regs_t::zbx_cpld_field_t>
+ mode_map{
+ {{0, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF0_CONFIG },
+ {{1, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF1_CONFIG },
+ {{0, atr_mode_target::DSA}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF0_DSA_CONFIG},
+ {{1, atr_mode_target::DSA}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF1_DSA_CONFIG}
+ };
+ // clang-format on
+ return _regs.get_field(mode_map.at({channel, target}));
+}
+
+void zbx_cpld_ctrl::set_tx_gain_switches(
+ const size_t channel, const uint8_t idx, const tx_dsa_type& dsa_steps)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+
+ UHD_LOG_TRACE(_log_id,
+ "Set TX DSA for channel " << channel << ": DSA1=" << dsa_steps[0] << ", DSA2="
+ << dsa_steps[1] << ", AMP=" << dsa_steps[2]);
+ if (channel == 0) {
+ _regs.TX0_DSA1[idx] = dsa_steps[0];
+ _regs.TX0_DSA2[idx] = dsa_steps[1];
+ } else if (channel == 1) {
+ _regs.TX1_DSA1[idx] = dsa_steps[0];
+ _regs.TX1_DSA2[idx] = dsa_steps[1];
+ }
+ // Correct amp path gets configured by switch_tx_antenna_switches()
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_gain_switches(
+ const size_t channel, const uint8_t idx, const rx_dsa_type& dsa_steps)
+{
+ UHD_LOG_TRACE(_log_id,
+ "Setting RX DSA for channel "
+ << channel << ": DSA1=" << dsa_steps[0] << ", DSA2=" << dsa_steps[1]
+ << ", DSA3A=" << dsa_steps[2] << ", DSA3B=" << dsa_steps[3]);
+ if (channel == 0) {
+ _regs.RX0_DSA1[idx] = dsa_steps[0];
+ _regs.RX0_DSA2[idx] = dsa_steps[1];
+ _regs.RX0_DSA3_A[idx] = dsa_steps[2];
+ _regs.RX0_DSA3_B[idx] = dsa_steps[3];
+ } else if (channel == 1) {
+ _regs.RX1_DSA1[idx] = dsa_steps[0];
+ _regs.RX1_DSA2[idx] = dsa_steps[1];
+ _regs.RX1_DSA3_A[idx] = dsa_steps[2];
+ _regs.RX1_DSA3_B[idx] = dsa_steps[3];
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_gain_switches(
+ const size_t channel, const uint8_t idx, const uint8_t table_idx)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+ UHD_LOG_TRACE(_log_id,
+ "Setting RX DSA for channel " << channel << " from table index " << table_idx);
+ if (channel == 0) {
+ _regs.RX0_TABLE_SELECT[idx] = table_idx;
+ } else {
+ _regs.RX1_TABLE_SELECT[idx] = table_idx;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_tx_gain_switches(
+ const size_t channel, const uint8_t idx, const uint8_t table_idx)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+ UHD_LOG_TRACE(_log_id,
+ "Setting TX DSA for channel " << channel << " from table index " << table_idx);
+ if (channel == 0) {
+ _regs.TX0_TABLE_SELECT[idx] = table_idx;
+ } else {
+ _regs.TX1_TABLE_SELECT[idx] = table_idx;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+uint8_t zbx_cpld_ctrl::set_tx_dsa(
+ const size_t channel, const uint8_t idx, const dsa_type tx_dsa, const uint8_t att)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ UHD_ASSERT_THROW(tx_dsa == dsa_type::DSA1 || tx_dsa == dsa_type::DSA2);
+ const uint8_t att_coerced = std::min(att, ZBX_TX_DSA_MAX_ATT);
+ _regs.set_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), att_coerced, idx);
+ commit(channel == 0 ? CHAN0 : CHAN1);
+ return att_coerced;
+}
+
+uint8_t zbx_cpld_ctrl::set_rx_dsa(
+ const size_t channel, const uint8_t idx, const dsa_type rx_dsa, const uint8_t att)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ const uint8_t att_coerced = std::min(att, ZBX_RX_DSA_MAX_ATT);
+ _regs.set_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), att_coerced, idx);
+ commit(channel == 0 ? CHAN0 : CHAN1);
+ return att_coerced;
+}
+
+uint8_t zbx_cpld_ctrl::get_tx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type tx_dsa,
+ const bool update_cache)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ UHD_ASSERT_THROW(tx_dsa == dsa_type::DSA1 || tx_dsa == dsa_type::DSA2);
+ if (update_cache) {
+ update_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), idx);
+ }
+ return _regs.get_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), idx);
+}
+
+uint8_t zbx_cpld_ctrl::get_rx_dsa(const size_t channel,
+ const uint8_t idx,
+ const dsa_type rx_dsa,
+ const bool update_cache)
+{
+ UHD_ASSERT_THROW(channel == 0 || channel == 1);
+ if (update_cache) {
+ update_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), idx);
+ }
+ return _regs.get_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), idx);
+}
+
+void zbx_cpld_ctrl::set_tx_antenna_switches(
+ const size_t channel, const uint8_t idx, const std::string& antenna, const tx_amp amp)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+ UHD_ASSERT_THROW(
+ amp == tx_amp::BYPASS || amp == tx_amp::LOWBAND || amp == tx_amp::HIGHBAND);
+
+ // Antenna settings: TX/RX, CAL_LOOPBACK
+ if (channel == 0) {
+ if (antenna == ANTENNA_TXRX) {
+ // clang-format off
+ static const std::map<tx_amp,
+ std::pair<zbx_cpld_regs_t::TX0_ANT_11_t, zbx_cpld_regs_t::TX0_ANT_10_t>> amp_map{
+ {tx_amp::BYPASS, {zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP, zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP}},
+ {tx_amp::LOWBAND, {zbx_cpld_regs_t::TX0_ANT_11_LOWBAND_AMP, zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP}},
+ {tx_amp::HIGHBAND, {zbx_cpld_regs_t::TX0_ANT_11_HIGHBAND_AMP, zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP}}
+ };
+ // clang-format on
+ std::tie(_regs.TX0_ANT_11[idx], _regs.TX0_ANT_10[idx]) = amp_map.at(amp);
+ } else if (antenna == ANTENNA_CAL_LOOPBACK) {
+ _regs.TX0_ANT_10[idx] = zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK;
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_CAL_LOOPBACK;
+ _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP;
+ } else {
+ UHD_LOG_WARNING(_log_id,
+ "ZBX Radio: TX Antenna setting not recognized: \"" << antenna.c_str()
+ << "\"");
+ }
+ } else {
+ // Antenna settings: TX/RX, CAL_LOOPBACK
+ if (antenna == ANTENNA_TXRX) {
+ // clang-format off
+ static const std::map<tx_amp,
+ std::pair<zbx_cpld_regs_t::TX1_ANT_11_t, zbx_cpld_regs_t::TX1_ANT_10_t>> amp_map{
+ {tx_amp::BYPASS, {zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP, zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP}},
+ {tx_amp::LOWBAND, {zbx_cpld_regs_t::TX1_ANT_11_LOWBAND_AMP, zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP}},
+ {tx_amp::HIGHBAND, {zbx_cpld_regs_t::TX1_ANT_11_HIGHBAND_AMP, zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP}}
+ };
+ // clang-format on
+ std::tie(_regs.TX1_ANT_11[idx], _regs.TX1_ANT_10[idx]) = amp_map.at(amp);
+ } else if (antenna == ANTENNA_CAL_LOOPBACK) {
+ _regs.TX1_ANT_10[idx] = zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK;
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_CAL_LOOPBACK;
+ _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP;
+ } else {
+ UHD_LOG_WARNING(_log_id,
+ "ZBX Radio: TX Antenna setting not recognized: \"" << antenna << "\"");
+ }
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_antenna_switches(
+ const size_t channel, const uint8_t idx, const std::string& antenna)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+
+ // Antenna settings: RX2, TX/RX, CAL_LOOPBACK, TERMINATION
+ if (channel == 0) {
+ if (antenna == ANTENNA_TXRX) {
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_TX_RX;
+ _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_TX_RX;
+ } else if (antenna == ANTENNA_CAL_LOOPBACK) {
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_CAL_LOOPBACK;
+ _regs.TX0_ANT_10[idx] = zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK;
+ _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP;
+ } else if (antenna == ANTENNA_TERMINATION) {
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_TERMINATION;
+ } else if (antenna == ANTENNA_RX) {
+ _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_RX2;
+ } else {
+ UHD_LOG_WARNING(_log_id,
+ "ZBX Radio: RX Antenna setting not recognized: \"" << antenna << "\"");
+ }
+ } else {
+ if (antenna == ANTENNA_TXRX) {
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_TX_RX;
+ _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_TX_RX;
+ } else if (antenna == ANTENNA_CAL_LOOPBACK) {
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_CAL_LOOPBACK;
+ _regs.TX1_ANT_10[idx] = zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK;
+ _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP;
+ } else if (antenna == ANTENNA_TERMINATION) {
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_TERMINATION;
+ } else if (antenna == ANTENNA_RX) {
+ _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_RX2;
+ } else {
+ UHD_LOG_WARNING(_log_id,
+ "ZBX Radio: RX Antenna setting not recognized: \"" << antenna << "\"");
+ }
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+tx_amp zbx_cpld_ctrl::get_tx_amp_settings(
+ const size_t channel, const uint8_t idx, const bool update_cache)
+{
+ if (channel == 0) {
+ if (update_cache) {
+ update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_10, idx);
+ update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_11, idx);
+ }
+ if ((_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP
+ && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP)
+ || (_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_HIGHBAND_AMP
+ && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP)
+ || (_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_LOWBAND_AMP
+ && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP)) {
+ UHD_LOG_WARNING(
+ _log_id, "Detected inconsistency in the TX amp switch settings.");
+ }
+ // clang-format off
+ static const std::map<zbx_cpld_regs_t::TX0_ANT_10_t, tx_amp> amp_map{
+ {zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP , tx_amp::BYPASS },
+ {zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK, tx_amp::BYPASS },
+ {zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP , tx_amp::LOWBAND },
+ {zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP, tx_amp::HIGHBAND}
+ };
+ // clang-format on
+ return amp_map.at(_regs.TX0_ANT_10[idx]);
+ }
+ if (channel == 1) {
+ if (update_cache) {
+ update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_10, idx);
+ update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_11, idx);
+ }
+ if ((_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP
+ && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP)
+ || (_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_HIGHBAND_AMP
+ && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP)
+ || (_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_LOWBAND_AMP
+ && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP)) {
+ UHD_LOG_WARNING(
+ _log_id, "Detected inconsistency in the TX amp switch settings.");
+ }
+ // clang-format off
+ static const std::map<zbx_cpld_regs_t::TX1_ANT_10_t, tx_amp> amp_map{
+ {zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP , tx_amp::BYPASS },
+ {zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK, tx_amp::BYPASS },
+ {zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP , tx_amp::LOWBAND },
+ {zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP, tx_amp::HIGHBAND}
+ };
+ // clang-format on
+ return amp_map.at(_regs.TX1_ANT_10[idx]);
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+void zbx_cpld_ctrl::set_rx_rf_filter(
+ const size_t channel, const uint8_t idx, const uint8_t rf_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && rf_fir < 4);
+
+ if (rf_fir == 0) {
+ if (channel == 0) {
+ _regs.RX0_4[idx] = zbx_cpld_regs_t::RX0_4_HIGHBAND;
+ _regs.RX0_2[idx] = zbx_cpld_regs_t::RX0_2_HIGHBAND;
+ } else {
+ _regs.RX1_4[idx] = zbx_cpld_regs_t::RX1_4_HIGHBAND;
+ _regs.RX1_2[idx] = zbx_cpld_regs_t::RX1_2_HIGHBAND;
+ }
+ } else {
+ // Clang-format likes to "staircase" multiple tertiary statements, it's much
+ // easier to read lined up
+ // clang-format off
+ if (channel == 0) {
+ _regs.RX0_4[idx] = zbx_cpld_regs_t::RX0_4_LOWBAND;
+ _regs.RX0_2[idx] = zbx_cpld_regs_t::RX0_2_LOWBAND;
+ _regs.RX0_RF_11[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX0_RF_11_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::RX0_RF_11_RF_2
+ : zbx_cpld_regs_t::RX0_RF_11_RF_3;
+ _regs.RX0_RF_3[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX0_RF_3_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::RX0_RF_3_RF_2
+ : zbx_cpld_regs_t::RX0_RF_3_RF_3;
+ } else {
+ _regs.RX1_4[idx] = zbx_cpld_regs_t::RX1_4_LOWBAND;
+ _regs.RX1_2[idx] = zbx_cpld_regs_t::RX1_2_LOWBAND;
+ _regs.RX1_RF_11[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX1_RF_11_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::RX1_RF_11_RF_2
+ : zbx_cpld_regs_t::RX1_RF_11_RF_3;
+ _regs.RX1_RF_3[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX1_RF_3_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::RX1_RF_3_RF_2
+ : zbx_cpld_regs_t::RX1_RF_3_RF_3;
+ }
+ // clang-format on
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_if1_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if1_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if1_fir != 0 && if1_fir < 5);
+
+ // Clang-format likes to "staircase" multiple tertiary statements, it's much
+ // easier to read lined up
+ // clang-format off
+ if (channel == 0) {
+ _regs.RX0_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_2
+ : if1_fir == 3 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_3
+ : zbx_cpld_regs_t::RX0_IF1_5_FILTER_4;
+
+ _regs.RX0_IF1_6[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_2
+ : if1_fir == 3 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_3
+ : zbx_cpld_regs_t::RX0_IF1_6_FILTER_4;
+ } else {
+ _regs.RX1_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_2
+ : if1_fir == 3 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_3
+ : zbx_cpld_regs_t::RX1_IF1_5_FILTER_4;
+
+ _regs.RX1_IF1_6[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_2
+ : if1_fir == 3 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_3
+ : zbx_cpld_regs_t::RX1_IF1_6_FILTER_4;
+ }
+ // clang-format on
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_rx_if2_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if2_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if2_fir != 0 && if2_fir < 3);
+
+ if (channel == 0) {
+ _regs.RX0_IF2_7_8[idx] = if2_fir == 1 ? zbx_cpld_regs_t::RX0_IF2_7_8_FILTER_1
+ : zbx_cpld_regs_t::RX0_IF2_7_8_FILTER_2;
+ } else {
+ _regs.RX1_IF2_7_8[idx] = if2_fir == 1 ? zbx_cpld_regs_t::RX1_IF2_7_8_FILTER_1
+ : zbx_cpld_regs_t::RX1_IF2_7_8_FILTER_2;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_tx_rf_filter(
+ const size_t channel, const uint8_t idx, const uint8_t rf_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && rf_fir < 4);
+
+ if (rf_fir == 0) {
+ if (channel == 0) {
+ _regs.TX0_RF_9[idx] = zbx_cpld_regs_t::TX0_RF_9_HIGHBAND;
+ _regs.TX0_7[idx] = zbx_cpld_regs_t::TX0_7_HIGHBAND;
+ } else {
+ _regs.TX1_RF_9[idx] = zbx_cpld_regs_t::TX1_RF_9_HIGHBAND;
+ _regs.TX1_7[idx] = zbx_cpld_regs_t::TX1_7_HIGHBAND;
+ }
+ } else {
+ // Clang-format likes to "staircase" multiple tertiary statements, it's much
+ // easier to read lined up
+ // clang-format off
+ if (channel == 0) {
+ _regs.TX0_RF_9[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX0_RF_9_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::TX0_RF_9_RF_2
+ : zbx_cpld_regs_t::TX0_RF_9_RF_3;
+
+ _regs.TX0_RF_8[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX0_RF_8_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::TX0_RF_8_RF_2
+ : zbx_cpld_regs_t::TX0_RF_8_RF_3;
+ _regs.TX0_7[idx] = zbx_cpld_regs_t::TX0_7_LOWBAND;
+ } else {
+ _regs.TX1_RF_9[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX1_RF_9_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::TX1_RF_9_RF_2
+ : zbx_cpld_regs_t::TX1_RF_9_RF_3;
+
+ _regs.TX1_RF_8[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX1_RF_8_RF_1
+ : rf_fir == 2 ? zbx_cpld_regs_t::TX1_RF_8_RF_2
+ : zbx_cpld_regs_t::TX1_RF_8_RF_3;
+ _regs.TX1_7[idx] = zbx_cpld_regs_t::TX1_7_LOWBAND;
+ }
+ // clang-format on
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_tx_if1_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if1_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if1_fir != 0 && if1_fir < 7);
+
+ if (if1_fir < 4) {
+ // Clang-format likes to "staircase" multiple tertiary statements, it's much
+ // easier to read lined up
+ // clang-format off
+ if (channel == 0) {
+ _regs.TX0_IF1_6[idx] = zbx_cpld_regs_t::TX0_IF1_6_FILTER_0_3;
+ _regs.TX0_IF1_3[idx] = zbx_cpld_regs_t::TX0_IF1_3_FILTER_0_3;
+ _regs.TX0_IF1_4[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX0_IF1_4_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::TX0_IF1_4_FILTER_2
+ : zbx_cpld_regs_t::TX0_IF1_4_FILTER_3;
+
+ _regs.TX0_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX0_IF1_5_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::TX0_IF1_5_FILTER_2
+ : zbx_cpld_regs_t::TX0_IF1_5_FILTER_3;
+ } else {
+ _regs.TX1_IF1_6[idx] = zbx_cpld_regs_t::TX1_IF1_6_FILTER_0_3;
+ _regs.TX1_IF1_3[idx] = zbx_cpld_regs_t::TX1_IF1_3_FILTER_0_3;
+ _regs.TX1_IF1_4[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX1_IF1_4_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::TX1_IF1_4_FILTER_2
+ : zbx_cpld_regs_t::TX1_IF1_4_FILTER_3;
+
+ _regs.TX1_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX1_IF1_5_FILTER_1
+ : if1_fir == 2 ? zbx_cpld_regs_t::TX1_IF1_5_FILTER_2
+ : zbx_cpld_regs_t::TX1_IF1_5_FILTER_3;
+ }
+ } else {
+ if (channel == 0) {
+ _regs.TX0_IF1_4[idx] = zbx_cpld_regs_t::TX0_IF1_4_TERMINATION;
+ _regs.TX0_IF1_5[idx] = zbx_cpld_regs_t::TX0_IF1_5_TERMINATION;
+ _regs.TX0_IF1_3[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX0_IF1_3_FILTER_4
+ : if1_fir == 5 ? zbx_cpld_regs_t::TX0_IF1_3_FILTER_5
+ : zbx_cpld_regs_t::TX0_IF1_3_FILTER_6;
+
+ _regs.TX0_IF1_6[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX0_IF1_6_FILTER_4
+ : if1_fir == 5 ? zbx_cpld_regs_t::TX0_IF1_6_FILTER_5
+ : zbx_cpld_regs_t::TX0_IF1_6_FILTER_6;
+ } else {
+ _regs.TX1_IF1_4[idx] = zbx_cpld_regs_t::TX1_IF1_4_TERMINATION;
+ _regs.TX1_IF1_5[idx] = zbx_cpld_regs_t::TX1_IF1_5_TERMINATION;
+ _regs.TX1_IF1_3[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX1_IF1_3_FILTER_4
+ : if1_fir == 5 ? zbx_cpld_regs_t::TX1_IF1_3_FILTER_5
+ : zbx_cpld_regs_t::TX1_IF1_3_FILTER_6;
+
+ _regs.TX1_IF1_6[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX1_IF1_6_FILTER_4
+ : if1_fir == 5 ? zbx_cpld_regs_t::TX1_IF1_6_FILTER_5
+ : zbx_cpld_regs_t::TX1_IF1_6_FILTER_6;
+ }
+ // clang-format on
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+void zbx_cpld_ctrl::set_tx_if2_filter(
+ const size_t channel, const uint8_t idx, const uint8_t if2_fir)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if2_fir != 0 && if2_fir < 3);
+
+ if (channel == 0) {
+ _regs.TX0_IF2_1_2[idx] = if2_fir == 1 ? zbx_cpld_regs_t::TX0_IF2_1_2_FILTER_1
+ : zbx_cpld_regs_t::TX0_IF2_1_2_FILTER_2;
+ } else {
+ _regs.TX1_IF2_1_2[idx] = if2_fir == 1 ? zbx_cpld_regs_t::TX1_IF2_1_2_FILTER_1
+ : zbx_cpld_regs_t::TX1_IF2_1_2_FILTER_2;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+/******************************************************************************
+ * LED control
+ *****************************************************************************/
+void zbx_cpld_ctrl::set_leds(const size_t channel,
+ const uint8_t idx,
+ const bool rx,
+ const bool trx_rx,
+ const bool trx_tx)
+{
+ UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS);
+ if (channel == 0) {
+ _regs.RX0_RX_LED[idx] = rx ? zbx_cpld_regs_t::RX0_RX_LED_ENABLE
+ : zbx_cpld_regs_t::RX0_RX_LED_DISABLE;
+ _regs.RX0_TRX_LED[idx] = trx_rx ? zbx_cpld_regs_t::RX0_TRX_LED_ENABLE
+ : zbx_cpld_regs_t::RX0_TRX_LED_DISABLE;
+ _regs.TX0_TRX_LED[idx] = trx_tx ? zbx_cpld_regs_t::TX0_TRX_LED_ENABLE
+ : zbx_cpld_regs_t::TX0_TRX_LED_DISABLE;
+ } else {
+ _regs.RX1_RX_LED[idx] = rx ? zbx_cpld_regs_t::RX1_RX_LED_ENABLE
+ : zbx_cpld_regs_t::RX1_RX_LED_DISABLE;
+ _regs.RX1_TRX_LED[idx] = trx_rx ? zbx_cpld_regs_t::RX1_TRX_LED_ENABLE
+ : zbx_cpld_regs_t::RX1_TRX_LED_DISABLE;
+ _regs.TX1_TRX_LED[idx] = trx_tx ? zbx_cpld_regs_t::TX1_TRX_LED_ENABLE
+ : zbx_cpld_regs_t::TX1_TRX_LED_DISABLE;
+ }
+ commit(channel == 0 ? CHAN0 : CHAN1);
+}
+
+/******************************************************************************
+ * LO control
+ *****************************************************************************/
+void zbx_cpld_ctrl::lo_poke16(const zbx_lo_t lo, const uint8_t addr, const uint16_t data)
+{
+ _lo_spi_transact(lo, addr, data, spi_xact_t::WRITE, true);
+ // We always sleep here, in the assumption that the next poke to the CPLD is
+ // also a
+ // SPI transaction.
+ // Note that this causes minor inefficiencies when stacking SPI writes with
+ // other, non-SPI pokes (because the last SPI poke will still be followed by
+ // a sleep, which isn't even necessary). If this becomes an issue, this
+ // function can be changed to include a flag as an argument whether or not
+ // to throttle.
+}
+
+uint16_t zbx_cpld_ctrl::lo_peek16(const zbx_lo_t lo, const uint8_t addr)
+{
+ _lo_spi_transact(lo, addr, 0, spi_xact_t::READ, true);
+ // Now poll the LO_SPI_READY register until we have good return value
+ const auto timeout = std::chrono::steady_clock::now()
+ + std::chrono::milliseconds(ZBX_LO_LOCK_TIMEOUT_MS);
+ while (std::chrono::steady_clock::now() < timeout) {
+ _regs.set_reg(_lo_spi_offset, _peek32(_lo_spi_offset));
+ if (_regs.DATA_VALID) {
+ break;
+ }
+ }
+
+ // Mark this register clean again
+ _regs.save_state();
+ if (!_regs.DATA_VALID) {
+ const std::string err_msg =
+ "Unable to read back from LO SPI! Transaction timed out after "
+ + std::to_string(ZBX_LO_LOCK_TIMEOUT_MS) + " ms.";
+ UHD_LOG_ERROR(_log_id, err_msg);
+ throw uhd::io_error(err_msg);
+ }
+ // The read worked. Now we run some sanity checks to make sure we got what
+ // we expected
+ UHD_ASSERT_THROW(_regs.ADDRESS == addr);
+ UHD_ASSERT_THROW(_regs.LO_SELECT == zbx_cpld_regs_t::LO_SELECT_t(lo));
+ // All good, return the read value
+ return _regs.DATA;
+}
+
+bool zbx_cpld_ctrl::lo_spi_ready()
+{
+ return _peek32(_lo_spi_offset) & (1 << 30);
+}
+
+void zbx_cpld_ctrl::set_lo_source(
+ const size_t idx, const zbx_lo_t lo, const zbx_lo_source_t lo_source)
+{
+ // LO source is either internal or external
+ const bool internal = lo_source == zbx_lo_source_t::internal;
+ switch (lo) {
+ case zbx_lo_t::TX0_LO1:
+ _regs.TX0_LO_14[idx] = internal ? zbx_cpld_regs_t::TX0_LO_14_INTERNAL
+ : zbx_cpld_regs_t::TX0_LO_14_EXTERNAL;
+ break;
+ case zbx_lo_t::TX0_LO2:
+ _regs.TX0_LO_13[idx] = internal ? zbx_cpld_regs_t::TX0_LO_13_INTERNAL
+ : zbx_cpld_regs_t::TX0_LO_13_EXTERNAL;
+ break;
+ case zbx_lo_t::TX1_LO1:
+ _regs.TX1_LO_14[idx] = internal ? zbx_cpld_regs_t::TX1_LO_14_INTERNAL
+ : zbx_cpld_regs_t::TX1_LO_14_EXTERNAL;
+ break;
+ case zbx_lo_t::TX1_LO2:
+ _regs.TX1_LO_13[idx] = internal ? zbx_cpld_regs_t::TX1_LO_13_INTERNAL
+ : zbx_cpld_regs_t::TX1_LO_13_EXTERNAL;
+ break;
+ case zbx_lo_t::RX0_LO1:
+ _regs.RX0_LO_9[idx] = internal ? zbx_cpld_regs_t::RX0_LO_9_INTERNAL
+ : zbx_cpld_regs_t::RX0_LO_9_EXTERNAL;
+ break;
+ case zbx_lo_t::RX0_LO2:
+ _regs.RX0_LO_10[idx] = internal ? zbx_cpld_regs_t::RX0_LO_10_INTERNAL
+ : zbx_cpld_regs_t::RX0_LO_10_EXTERNAL;
+ break;
+ case zbx_lo_t::RX1_LO1:
+ _regs.RX1_LO_9[idx] = internal ? zbx_cpld_regs_t::RX1_LO_9_INTERNAL
+ : zbx_cpld_regs_t::RX1_LO_9_EXTERNAL;
+ break;
+ case zbx_lo_t::RX1_LO2:
+ _regs.RX1_LO_10[idx] = internal ? zbx_cpld_regs_t::RX1_LO_10_INTERNAL
+ : zbx_cpld_regs_t::RX1_LO_10_EXTERNAL;
+ break;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ if (lo == zbx_lo_t::TX0_LO1 || lo == zbx_lo_t::TX0_LO2 || lo == zbx_lo_t::RX0_LO1
+ || lo == zbx_lo_t::RX0_LO2) {
+ commit(CHAN0);
+ } else {
+ commit(CHAN1);
+ }
+}
+
+zbx_lo_source_t zbx_cpld_ctrl::get_lo_source(const size_t idx, zbx_lo_t lo)
+{
+ switch (lo) {
+ case zbx_lo_t::TX0_LO1:
+ return _regs.TX0_LO_14[idx] == zbx_cpld_regs_t::TX0_LO_14_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::TX0_LO2:
+ return _regs.TX0_LO_13[idx] == zbx_cpld_regs_t::TX0_LO_13_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::TX1_LO1:
+ return _regs.TX1_LO_14[idx] == zbx_cpld_regs_t::TX1_LO_14_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::TX1_LO2:
+ return _regs.TX1_LO_13[idx] == zbx_cpld_regs_t::TX1_LO_13_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::RX0_LO1:
+ return _regs.RX0_LO_9[idx] == zbx_cpld_regs_t::RX0_LO_9_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::RX0_LO2:
+ return _regs.RX0_LO_10[idx] == zbx_cpld_regs_t::RX0_LO_10_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::RX1_LO1:
+ return _regs.RX1_LO_9[idx] == zbx_cpld_regs_t::RX1_LO_9_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ case zbx_lo_t::RX1_LO2:
+ return _regs.RX1_LO_10[idx] == zbx_cpld_regs_t::RX1_LO_10_INTERNAL
+ ? zbx_lo_source_t::internal
+ : zbx_lo_source_t::external;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+void zbx_cpld_ctrl::pulse_lo_sync(const size_t ref_chan, const std::vector<zbx_lo_t>& los)
+{
+ if (_regs.BYPASS_SYNC_REGISTER == zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_ENABLE) {
+ const std::string err_msg = "Cannot pulse LO SYNC when bypass is enabled!";
+ UHD_LOG_ERROR(_log_id, err_msg);
+ throw uhd::runtime_error(_log_id + err_msg);
+ }
+ // Assert a 1 for all LOs to be sync'd
+ static const std::unordered_map<zbx_lo_t, zbx_cpld_regs_t::zbx_cpld_field_t>
+ lo_pulse_map{{
+ {zbx_lo_t::TX0_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX0_LO1_SYNC},
+ {zbx_lo_t::TX0_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX0_LO2_SYNC},
+ {zbx_lo_t::TX1_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX1_LO1_SYNC},
+ {zbx_lo_t::TX1_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX1_LO2_SYNC},
+ {zbx_lo_t::RX0_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX0_LO1_SYNC},
+ {zbx_lo_t::RX0_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX0_LO2_SYNC},
+ {zbx_lo_t::RX1_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX1_LO1_SYNC},
+ {zbx_lo_t::RX1_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX1_LO2_SYNC},
+ }};
+ for (const auto lo : los) {
+ _regs.set_field(lo_pulse_map.at(lo), 1);
+ }
+ commit(ref_chan == 0 ? CHAN0 : CHAN1);
+ // The bits are strobed, they self-clear. We reflect that here by resetting
+ // them without another commit:
+ for (const auto lo_it : lo_pulse_map) {
+ _regs.set_field(lo_it.second, 0);
+ }
+ _regs.save_state();
+}
+
+void zbx_cpld_ctrl::set_lo_sync_bypass(const bool enable)
+{
+ _regs.BYPASS_SYNC_REGISTER = enable ? zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_ENABLE
+ : zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_DISABLE;
+ commit(NO_CHAN);
+}
+
+void zbx_cpld_ctrl::update_tx_dsa_settings(
+ const std::vector<uint32_t>& dsa1_table, const std::vector<uint32_t>& dsa2_table)
+{
+ write_register_vector("TX0_TABLE_DSA1", dsa1_table);
+ write_register_vector("TX0_TABLE_DSA2", dsa2_table);
+ write_register_vector("TX1_TABLE_DSA1", dsa1_table);
+ write_register_vector("TX1_TABLE_DSA2", dsa2_table);
+ commit(NO_CHAN);
+}
+
+void zbx_cpld_ctrl::update_rx_dsa_settings(const std::vector<uint32_t>& dsa1_table,
+ const std::vector<uint32_t>& dsa2_table,
+ const std::vector<uint32_t>& dsa3a_table,
+ const std::vector<uint32_t>& dsa3b_table)
+{
+ write_register_vector("RX0_TABLE_DSA1", dsa1_table);
+ write_register_vector("RX0_TABLE_DSA2", dsa2_table);
+ write_register_vector("RX0_TABLE_DSA3_A", dsa3a_table);
+ write_register_vector("RX0_TABLE_DSA3_B", dsa3b_table);
+ write_register_vector("RX1_TABLE_DSA1", dsa1_table);
+ write_register_vector("RX1_TABLE_DSA2", dsa2_table);
+ write_register_vector("RX1_TABLE_DSA3_A", dsa3a_table);
+ write_register_vector("RX1_TABLE_DSA3_B", dsa3b_table);
+ commit(NO_CHAN);
+}
+
+/******************************************************************************
+ * Private methods
+ *****************************************************************************/
+void zbx_cpld_ctrl::_lo_spi_transact(const zbx_lo_t lo,
+ const uint8_t addr,
+ const uint16_t data,
+ const spi_xact_t xact_type,
+ const bool throttle)
+{
+ // Look up the channel based on the LO, so we can load the correct command
+ // time for the poke
+ const chan_t chan = (lo == zbx_lo_t::TX0_LO1 || lo == zbx_lo_t::TX0_LO2
+ || lo == zbx_lo_t::RX0_LO1 || lo == zbx_lo_t::RX0_LO2)
+ ? CHAN0
+ : CHAN1;
+ // Note: For SPI transactions, we can't also be lugging around other
+ // registers. This means that we assume that the state of _regs is clean.
+ _regs.ADDRESS = addr;
+ _regs.DATA = data;
+ _regs.READ_FLAG = (xact_type == spi_xact_t::WRITE) ? zbx_cpld_regs_t::READ_FLAG_WRITE
+ : zbx_cpld_regs_t::READ_FLAG_READ;
+ _regs.LO_SELECT = zbx_cpld_regs_t::LO_SELECT_t(lo);
+ _regs.START_TRANSACTION = zbx_cpld_regs_t::START_TRANSACTION_ENABLE;
+ _poke32(_lo_spi_offset, _regs.get_reg(_lo_spi_offset), chan);
+ _regs.START_TRANSACTION = zbx_cpld_regs_t::START_TRANSACTION_DISABLE;
+ _regs.save_state();
+ // Write complete. Now we need to send a sleep to throttle the SPI
+ // transactions:
+ if (throttle) {
+ _sleep(SPI_THROTTLE_TIME);
+ }
+}
+
+void zbx_cpld_ctrl::write_register_vector(
+ const std::string& reg_addr_name, const std::vector<uint32_t>& values)
+{
+ UHD_LOG_DEBUG(
+ _log_id, "Write " << values.size() << " values to register " << reg_addr_name);
+ zbx_cpld_regs_t::zbx_cpld_field_t type = _regs.get_field_type(reg_addr_name);
+ if (values.size() > _regs.get_array_size(type)) {
+ const std::string err_msg = "Number of values passed for register vector("
+ + std::to_string(values.size())
+ + ") exceeds size of register ("
+ + std::to_string(_regs.get_array_size(type)) + ")";
+ UHD_LOG_ERROR(_log_id, err_msg);
+ throw uhd::runtime_error(err_msg);
+ }
+ for (size_t i = 0; i < values.size(); i++) {
+ _regs.set_field(type, values[i], i);
+ }
+}
+
+void zbx_cpld_ctrl::commit(const chan_t chan, const bool save_all)
+{
+ UHD_LOG_TRACE(_log_id,
+ "Storing register cache " << (save_all ? "completely" : "selectively")
+ << " to CPLD...");
+ const auto changed_addrs = save_all ? _regs.get_all_addrs()
+ : _regs.get_changed_addrs<size_t>();
+ for (const auto addr : changed_addrs) {
+ _poke32(addr, _regs.get_reg(addr), save_all ? NO_CHAN : chan);
+ }
+ _regs.save_state();
+ UHD_LOG_TRACE(_log_id,
+ "Storing cache complete: "
+ "Updated "
+ << changed_addrs.size() << " registers.");
+}
+
+void zbx_cpld_ctrl::update_field(
+ const zbx_cpld_regs_t::zbx_cpld_field_t field, const size_t idx)
+{
+ const uint16_t addr = _regs.get_addr(field) + 4 * idx;
+ const uint32_t chip_val = _peek32(addr);
+ _regs.set_reg(addr, chip_val);
+ const auto changed_addrs = _regs.get_changed_addrs<size_t>();
+ // If this is the only change in our register stack, then we call save_state()
+ // because we don't want to write this value we just read from the CPLD back
+ // to it. However, if there are other changes queued up, we'll have to wait
+ // until the next commit() call. If this is not desired, we need to update
+ // the regmap code to selectively save state.
+ if (changed_addrs.empty()
+ || (changed_addrs.size() == 1 && changed_addrs.count(addr))) {
+ _regs.save_state();
+ } else {
+ UHD_LOG_DEBUG(_log_id,
+ "Not saving register state after calling update_field(). This may "
+ "cause unnecessary writes in the future.");
+ }
+}
+
+}}} // namespace uhd::usrp::zbx