diff options
Diffstat (limited to 'host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp')
-rw-r--r-- | host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp | 931 |
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 |