diff options
author | Martin Braun <martin.braun@ettus.com> | 2017-10-20 16:52:01 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2017-12-22 15:04:03 -0800 |
commit | a5452a42fe35f67e477279df13085fc609cbf6f1 (patch) | |
tree | 6e18d2175934bbef023d7183ec677507fec843e4 /host/lib/usrp | |
parent | 4087126476e11bae11f203f33be13c795bd67d35 (diff) | |
download | uhd-a5452a42fe35f67e477279df13085fc609cbf6f1.tar.gz uhd-a5452a42fe35f67e477279df13085fc609cbf6f1.tar.bz2 uhd-a5452a42fe35f67e477279df13085fc609cbf6f1.zip |
mg: Add control object for CPLD
Diffstat (limited to 'host/lib/usrp')
-rw-r--r-- | host/lib/usrp/dboard/magnesium/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.cpp | 333 | ||||
-rw-r--r-- | host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.hpp | 300 |
3 files changed, 634 insertions, 0 deletions
diff --git a/host/lib/usrp/dboard/magnesium/CMakeLists.txt b/host/lib/usrp/dboard/magnesium/CMakeLists.txt index 07677b8c9..a0578d7b0 100644 --- a/host/lib/usrp/dboard/magnesium/CMakeLists.txt +++ b/host/lib/usrp/dboard/magnesium/CMakeLists.txt @@ -18,6 +18,7 @@ IF(ENABLE_MPMD) LIST(APPEND MAGNESIUM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_cpld_ctrl.cpp ) LIBUHD_APPEND_SOURCES(${MAGNESIUM_SOURCES}) ENDIF(ENABLE_MPMD) diff --git a/host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.cpp b/host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.cpp new file mode 100644 index 000000000..18402e764 --- /dev/null +++ b/host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.cpp @@ -0,0 +1,333 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0 +// + +#include "magnesium_cpld_ctrl.hpp" +#include <uhd/utils/log.hpp> +#include <boost/format.hpp> +#include <chrono> + +namespace { + //! Address of the CPLD scratch register + const uint8_t CPLD_REGS_SCRATCH = 0x0040; + const uint8_t CPLD_REGS_RESET = 0x0041; +} + +magnesium_cpld_ctrl::magnesium_cpld_ctrl( + write_spi_t write_fn, + read_spi_t read_fn +) +{ + _write_fn = [write_fn](const uint8_t addr, const uint32_t data){ + UHD_LOG_TRACE("MG_CPLD", + str(boost::format("Writing to CPLD: 0x%X -> 0x%4X") + % uint32_t(addr) % data)); + const uint32_t spi_transaction = 0 + | ((addr & 0x7F) << 16) + | data + ; + write_fn(spi_transaction); + }; + _read_fn = [read_fn](const uint8_t addr){ + UHD_LOG_TRACE("MG_CPLD", "Reading from addr " << addr); + UHD_LOG_TRACE("MG_CPLD", + str(boost::format("Reading from CPLD address 0x%X") + % uint32_t(addr))); + const uint32_t spi_transaction = (1<<23) + | ((addr & 0x7F) << 16) + ; + return read_fn(spi_transaction); + }; + + reset(); + _loopback_test(); +} + +/****************************************************************************** + * API + *****************************************************************************/ +void magnesium_cpld_ctrl::reset() +{ + std::lock_guard<std::mutex> l(_set_mutex); + UHD_LOG_TRACE("MG_CPLD", "Resetting CPLD to default state"); + // Note: We won't use _regs and commit() here, because the reset will screw + // up the state of the CPLD w.r.t. to the local cache anyway. + _write_fn(CPLD_REGS_RESET, 1); + _write_fn(CPLD_REGS_RESET, 0); + // Now make sure the local state and the CPLD state are the same by pushing + // our local changes out: + _regs = magnesium_cpld_regs_t(); + commit(true); +} + +uint16_t magnesium_cpld_ctrl::get_reg(const uint8_t addr) +{ + std::lock_guard<std::mutex> l(_set_mutex); + return _read_fn(addr); +} + +void magnesium_cpld_ctrl::set_scratch(const uint16_t val) +{ + std::lock_guard<std::mutex> l(_set_mutex); + _regs.scratch = val; + commit(); +} + +uint16_t magnesium_cpld_ctrl::get_scratch() +{ + return get_reg(CPLD_REGS_SCRATCH); +} + +void magnesium_cpld_ctrl::set_tx_switches( + const chan_sel_t chan, + const sw_trx_t trx_sw, + const tx_sw1_t tx_sw1, + const tx_sw2_t tx_sw2, + const tx_sw3_t tx_sw3, + const lowband_mixer_path_sel_t select_lowband_mixer_path, + const bool enb_lowband_mixer, + const atr_state_t atr_state +) { + std::lock_guard<std::mutex> l(_set_mutex); + if (chan == CHAN1 or chan == BOTH) { + if (atr_state == IDLE or atr_state == ANY) { + _regs.ch1_idle_sw_trx = magnesium_cpld_regs_t::ch1_idle_sw_trx_t(trx_sw); + _regs.ch1_idle_tx_sw1 = magnesium_cpld_regs_t::ch1_idle_tx_sw1_t(tx_sw1); + _regs.ch1_idle_tx_sw2 = magnesium_cpld_regs_t::ch1_idle_tx_sw2_t(tx_sw2); + _regs.ch1_idle_tx_sw3 = magnesium_cpld_regs_t::ch1_idle_tx_sw3_t(tx_sw3); + _regs.ch1_idle_tx_lowband_mixer_path_select = magnesium_cpld_regs_t::ch1_idle_tx_lowband_mixer_path_select_t(select_lowband_mixer_path); + _regs.ch1_idle_tx_mixer_en = enb_lowband_mixer; + } + if (atr_state == ON or atr_state == ANY) { + _regs.ch1_on_tx_sw1 = magnesium_cpld_regs_t::ch1_on_tx_sw1_t(tx_sw1); + _regs.ch1_on_tx_sw2 = magnesium_cpld_regs_t::ch1_on_tx_sw2_t(tx_sw2); + _regs.ch1_on_tx_sw3 = magnesium_cpld_regs_t::ch1_on_tx_sw3_t(tx_sw3); + _regs.ch1_on_tx_lowband_mixer_path_select = + magnesium_cpld_regs_t::ch1_on_tx_lowband_mixer_path_select_t(select_lowband_mixer_path); + _regs.ch1_on_tx_mixer_en = enb_lowband_mixer; + } + } + if (chan == CHAN2 or chan == BOTH) { + if (atr_state == IDLE or atr_state == ANY) { + _regs.ch2_idle_tx_sw1 = magnesium_cpld_regs_t::ch2_idle_tx_sw1_t(tx_sw1); + _regs.ch2_idle_tx_sw2 = magnesium_cpld_regs_t::ch2_idle_tx_sw2_t(tx_sw1); + _regs.ch2_idle_tx_sw3 = magnesium_cpld_regs_t::ch2_idle_tx_sw3_t(tx_sw1); + _regs.ch2_idle_tx_lowband_mixer_path_select = + magnesium_cpld_regs_t::ch2_idle_tx_lowband_mixer_path_select_t(select_lowband_mixer_path); + _regs.ch2_idle_tx_mixer_en = enb_lowband_mixer; + } + if (atr_state == ON or atr_state == ANY) { + _regs.ch2_on_tx_sw1 = magnesium_cpld_regs_t::ch2_on_tx_sw1_t(tx_sw1); + _regs.ch2_on_tx_sw2 = magnesium_cpld_regs_t::ch2_on_tx_sw2_t(tx_sw2); + _regs.ch2_on_tx_sw3 = magnesium_cpld_regs_t::ch2_on_tx_sw3_t(tx_sw3); + _regs.ch2_on_tx_lowband_mixer_path_select = + magnesium_cpld_regs_t::ch2_on_tx_lowband_mixer_path_select_t(select_lowband_mixer_path); + _regs.ch2_on_tx_mixer_en = enb_lowband_mixer; + } + } + + commit(); +} + +void magnesium_cpld_ctrl::set_rx_switches( + const chan_sel_t chan, + const rx_sw2_t rx_sw2, + const rx_sw3_t rx_sw3, + const rx_sw4_t rx_sw4, + const rx_sw5_t rx_sw5, + const rx_sw6_t rx_sw6, + const lowband_mixer_path_sel_t select_lowband_mixer_path, + const bool enb_lowband_mixer, + const atr_state_t atr_state +) { + std::lock_guard<std::mutex> l(_set_mutex); + if (chan == CHAN1 or chan == BOTH) { + if (atr_state == IDLE or atr_state == ANY) { + _regs.ch1_idle_rx_sw2 = magnesium_cpld_regs_t::ch1_idle_rx_sw2_t(rx_sw2); + _regs.ch1_idle_rx_sw3 = magnesium_cpld_regs_t::ch1_idle_rx_sw3_t(rx_sw3); + _regs.ch1_idle_rx_sw4 = magnesium_cpld_regs_t::ch1_idle_rx_sw4_t(rx_sw4); + _regs.ch1_idle_rx_sw5 = magnesium_cpld_regs_t::ch1_idle_rx_sw5_t(rx_sw5); + _regs.ch1_idle_rx_sw6 = magnesium_cpld_regs_t::ch1_idle_rx_sw6_t(rx_sw6); + _regs.ch1_idle_rx_loband_mixer_path_sel = magnesium_cpld_regs_t::ch1_idle_rx_loband_mixer_path_sel_t(select_lowband_mixer_path); + _regs.ch1_idle_rx_mixer_en = enb_lowband_mixer; + } + if (atr_state == ON or atr_state == ANY) { + _regs.ch1_on_rx_sw2 = magnesium_cpld_regs_t::ch1_on_rx_sw2_t(rx_sw2); + _regs.ch1_on_rx_sw3 = magnesium_cpld_regs_t::ch1_on_rx_sw3_t(rx_sw3); + _regs.ch1_on_rx_sw4 = magnesium_cpld_regs_t::ch1_on_rx_sw4_t(rx_sw4); + _regs.ch1_on_rx_sw5 = magnesium_cpld_regs_t::ch1_on_rx_sw5_t(rx_sw5); + _regs.ch1_on_rx_sw6 = magnesium_cpld_regs_t::ch1_on_rx_sw6_t(rx_sw6); + _regs.ch1_on_rx_loband_mixer_path_sel = magnesium_cpld_regs_t::ch1_on_rx_loband_mixer_path_sel_t(select_lowband_mixer_path); + _regs.ch1_on_rx_mixer_en = enb_lowband_mixer; + } + } + if (chan == CHAN2 or chan == BOTH) { + if (atr_state == IDLE or atr_state == ANY) { + _regs.ch2_idle_rx_sw2 = magnesium_cpld_regs_t::ch2_idle_rx_sw2_t(rx_sw2); + _regs.ch2_idle_rx_sw3 = magnesium_cpld_regs_t::ch2_idle_rx_sw3_t(rx_sw3); + _regs.ch2_idle_rx_sw4 = magnesium_cpld_regs_t::ch2_idle_rx_sw4_t(rx_sw4); + _regs.ch2_idle_rx_sw5 = magnesium_cpld_regs_t::ch2_idle_rx_sw5_t(rx_sw5); + _regs.ch2_idle_rx_sw6 = magnesium_cpld_regs_t::ch2_idle_rx_sw6_t(rx_sw6); + _regs.ch2_idle_rx_loband_mixer_path_sel = + magnesium_cpld_regs_t::ch2_idle_rx_loband_mixer_path_sel_t(select_lowband_mixer_path); + _regs.ch2_idle_rx_mixer_en = enb_lowband_mixer; + } + if (atr_state == ON or atr_state == ANY) { + _regs.ch2_on_rx_sw2 = magnesium_cpld_regs_t::ch2_on_rx_sw2_t(rx_sw2); + _regs.ch2_on_rx_sw3 = magnesium_cpld_regs_t::ch2_on_rx_sw3_t(rx_sw3); + _regs.ch2_on_rx_sw4 = magnesium_cpld_regs_t::ch2_on_rx_sw4_t(rx_sw4); + _regs.ch2_on_rx_sw5 = magnesium_cpld_regs_t::ch2_on_rx_sw5_t(rx_sw5); + _regs.ch2_on_rx_sw6 = magnesium_cpld_regs_t::ch2_on_rx_sw6_t(rx_sw6); + _regs.ch2_on_rx_loband_mixer_path_sel = magnesium_cpld_regs_t::ch2_on_rx_loband_mixer_path_sel_t(select_lowband_mixer_path); + _regs.ch2_on_rx_mixer_en = enb_lowband_mixer; + } + } + commit(); +} + +void magnesium_cpld_ctrl::set_tx_atr_bits( + const chan_sel_t chan, + const atr_state_t atr_state, + const bool tx_led, + const sw_trx_t trx_sw, + const bool tx_pa_enb, + const bool tx_amp_enb, + const bool tx_myk_en +) { + std::lock_guard<std::mutex> l(_set_mutex); + if (chan == CHAN1 or chan == BOTH) { + if (atr_state == IDLE or atr_state == ANY) { + _regs.ch1_idle_tx_led = tx_led; + _regs.ch1_idle_sw_trx = magnesium_cpld_regs_t::ch1_idle_sw_trx_t(trx_sw); + _regs.ch1_idle_tx_pa_en = tx_pa_enb; + _regs.ch1_idle_tx_amp_en = tx_amp_enb; + _regs.ch1_idle_tx_myk_en = tx_myk_en; + } + if (atr_state == ON or atr_state == ANY) { + _regs.ch1_on_tx_led = tx_led; + _regs.ch1_on_sw_trx = magnesium_cpld_regs_t::ch1_on_sw_trx_t(trx_sw); + _regs.ch1_on_tx_pa_en = tx_pa_enb; + _regs.ch1_on_tx_amp_en = tx_amp_enb; + _regs.ch1_on_tx_myk_en = tx_myk_en; + } + } + if (chan == CHAN2 or chan == BOTH) { + if (atr_state == IDLE or atr_state == ANY) { + _regs.ch2_idle_tx_led = tx_led; + _regs.ch2_idle_sw_trx = magnesium_cpld_regs_t::ch2_idle_sw_trx_t(trx_sw); + _regs.ch2_idle_tx_pa_en = tx_pa_enb; + _regs.ch2_idle_tx_amp_en = tx_amp_enb; + _regs.ch2_idle_tx_myk_en = tx_myk_en; + } + if (atr_state == ON or atr_state == ANY) { + _regs.ch2_on_tx_led = tx_led; + _regs.ch2_on_sw_trx = magnesium_cpld_regs_t::ch2_on_sw_trx_t(trx_sw); + _regs.ch2_on_tx_pa_en = tx_pa_enb; + _regs.ch2_on_tx_amp_en = tx_amp_enb; + _regs.ch2_on_tx_myk_en = tx_myk_en; + } + } + commit(); +} + +void magnesium_cpld_ctrl::set_rx_atr_bits( + const chan_sel_t chan, + const atr_state_t atr_state, + const rx_sw1_t rx_sw1, + const bool rx_led, + const bool rx2_led, + const bool rx_lna1_enb, + const bool rx_lna2_enb, + const bool rx_amp_enb, + const bool rx_myk_en +) { + std::lock_guard<std::mutex> l(_set_mutex); + if (chan == CHAN1 or chan == BOTH) { + if (atr_state == IDLE or atr_state == ANY) { + _regs.ch1_idle_rx_sw1 = magnesium_cpld_regs_t::ch1_idle_rx_sw1_t(rx_sw1); + _regs.ch1_idle_rx_led = rx_led; + _regs.ch1_idle_rx2_led = rx2_led; + _regs.ch1_idle_rx_lna1_en = rx_lna1_enb; + _regs.ch1_idle_rx_lna2_en = rx_lna2_enb; + _regs.ch1_idle_rx_amp_en = rx_amp_enb; + _regs.ch1_idle_rx_myk_en = rx_myk_en; + } + if (atr_state == ON or atr_state == ANY) { + _regs.ch1_on_rx_sw1 = magnesium_cpld_regs_t::ch1_on_rx_sw1_t(rx_sw1); + _regs.ch1_on_rx_led = rx_led; + _regs.ch1_on_rx2_led = rx2_led; + _regs.ch1_on_rx_lna1_en = rx_lna1_enb; + _regs.ch1_on_rx_lna2_en = rx_lna2_enb; + _regs.ch1_on_rx_amp_en = rx_amp_enb; + _regs.ch1_on_rx_myk_en = rx_myk_en; + } + } + if (chan == CHAN2 or chan == BOTH) { + if (atr_state == IDLE or atr_state == ANY) { + _regs.ch2_idle_rx_sw1 = magnesium_cpld_regs_t::ch2_idle_rx_sw1_t(rx_sw1); + _regs.ch2_idle_rx_led = rx_led; + _regs.ch2_idle_rx2_led = rx2_led; + _regs.ch2_idle_rx_lna1_en = rx_lna1_enb; + _regs.ch2_idle_rx_lna2_en = rx_lna2_enb; + _regs.ch2_idle_rx_amp_en = rx_amp_enb; + _regs.ch2_idle_rx_myk_en = rx_myk_en; + } + if (atr_state == ON or atr_state == ANY) { + _regs.ch2_on_rx_sw1 = magnesium_cpld_regs_t::ch2_on_rx_sw1_t(rx_sw1); + _regs.ch2_on_rx_led = rx_led; + _regs.ch2_on_rx2_led = rx2_led; + _regs.ch2_on_rx_lna1_en = rx_lna1_enb; + _regs.ch2_on_rx_lna2_en = rx_lna2_enb; + _regs.ch2_on_rx_amp_en = rx_amp_enb; + _regs.ch2_on_rx_myk_en = rx_myk_en; + } + } + + commit(); +} + + +/****************************************************************************** + * Private methods + *****************************************************************************/ +void magnesium_cpld_ctrl::_loopback_test() +{ + UHD_LOG_TRACE("MG_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("MG_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("MG_CPLD", "CPLD scratch loopback test passed!"); +} + + +void magnesium_cpld_ctrl::commit(const bool save_all) +{ + UHD_LOGGER_TRACE("MG_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_fn(addr, _regs.get_reg(addr)); + } + _regs.save_state(); + UHD_LOG_TRACE("MG_CPLD", + "Storing cache complete: " \ + "Updated " << changed_addrs.size() << " registers."); +} + diff --git a/host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.hpp b/host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.hpp new file mode 100644 index 000000000..d9000401e --- /dev/null +++ b/host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.hpp @@ -0,0 +1,300 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0 +// + +#ifndef INCLUDED_LIBUHD_MAGNESIUM_CPLD_CTRL_HPP +#define INCLUDED_LIBUHD_MAGNESIUM_CPLD_CTRL_HPP + +#include "adf4351_regs.hpp" +#include <uhd/types/serial.hpp> +#include "magnesium_cpld_regs.hpp" +#include <mutex> +#include <memory> + +//! Controls the CPLD on a Magnesium daughterboard +// +// Setters are thread-safe through lock guards. This lets a CPLD control object +// be shared by multiple owners. +class magnesium_cpld_ctrl +{ +public: + /************************************************************************** + * Types + *************************************************************************/ + using sptr = std::shared_ptr<magnesium_cpld_ctrl>; + //! SPI write functor: Can take a SPI transaction and clock it out + using write_spi_t = std::function<void(uint32_t)>; + //! SPI read functor: Return SPI + using read_spi_t = std::function<uint32_t(uint32_t)>; + + //! ATR state: The CPLD has 2 states for RX and TX each, not like the radio + // which has 4 states (one for every RX/TX state combo). + enum atr_state_t { + IDLE, + ON, + ANY + }; + + //! Channel select: One CPLD controls both channels on a daughterboard + enum chan_sel_t { + CHAN1, + CHAN2, + BOTH + }; + + enum tx_sw1_t { + TX_SW1_SHUTDOWNTXSW1 = 0, + TX_SW1_FROMTXFILTERLP1700MHZ = 1, + TX_SW1_FROMTXFILTERLP3400MHZ = 2, + TX_SW1_FROMTXFILTERLP0800MHZ = 3 + }; + + enum tx_sw2_t { + TX_SW2_TOTXFILTERLP3400MHZ = 1, + TX_SW2_TOTXFILTERLP1700MHZ = 2, + TX_SW2_TOTXFILTERLP0800MHZ = 4, + TX_SW2_TOTXFILTERLP6400MHZ = 8 + }; + + enum tx_sw3_t { + TX_SW3_TOTXFILTERBANKS = 0, + TX_SW3_BYPASSPATHTOTRXSW = 1 + }; + + enum sw_trx_t { + SW_TRX_FROMLOWERFILTERBANKTXSW1 = 0, + SW_TRX_FROMTXUPPERFILTERBANKLP6400MHZ = 1, + SW_TRX_RXCHANNELPATH = 2, + SW_TRX_BYPASSPATHTOTXSW3 = 3 + }; + + enum rx_sw1_t { + RX_SW1_TXRXINPUT = 0, + RX_SW1_RXLOCALINPUT = 1, + RX_SW1_TRXSWITCHOUTPUT = 2, + RX_SW1_RX2INPUT = 3 + }; + + enum rx_sw2_t { + RX_SW2_SHUTDOWNSW2 = 0, + RX_SW2_LOWERFILTERBANKTOSWITCH3 = 1, + RX_SW2_BYPASSPATHTOSWITCH6 = 2, + RX_SW2_UPPERFILTERBANKTOSWITCH4 = 3 + }; + + enum rx_sw3_t { + RX_SW3_FILTER2100X2850MHZ = 0, + RX_SW3_FILTER0490LPMHZ = 1, + RX_SW3_FILTER1600X2250MHZ = 2, + RX_SW3_FILTER0440X0530MHZ = 4, + RX_SW3_FILTER0650X1000MHZ = 5, + RX_SW3_FILTER1100X1575MHZ = 6, + RX_SW3_SHUTDOWNSW3 = 7 + }; + + enum rx_sw4_t { + RX_SW4_FILTER2100X2850MHZFROM = 1, + RX_SW4_FILTER1600X2250MHZFROM = 2, + RX_SW4_FILTER2700HPMHZ = 4 + }; + + enum rx_sw5_t { + RX_SW5_FILTER0440X0530MHZFROM = 1, + RX_SW5_FILTER1100X1575MHZFROM = 2, + RX_SW5_FILTER0490LPMHZFROM = 4, + RX_SW5_FILTER0650X1000MHZFROM = 8 + }; + + enum rx_sw6_t { + RX_SW6_LOWERFILTERBANKFROMSWITCH5 = 1, + RX_SW6_UPPERFILTERBANKFROMSWITCH4 = 2, + RX_SW6_BYPASSPATHFROMSWITCH2 = 4 + }; + + enum lowband_mixer_path_sel_t { + LOWBAND_MIXER_PATH_SEL_BYPASS = 0, + LOWBAND_MIXER_PATH_SEL_LOBAND = 1 + }; + + + /*! Constructor. + * + * \param write_spi_fn SPI write functor + * \param read_spi_fn SPI read functor + */ + magnesium_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 reg (no effect, for debugging only) + 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 + * + * Note: The TRX switch is also a frequency-dependent setting, but it's + * also tied to ATR state. For that reason, its configuration is done by + * set_tx_atr_bits(). + * + * \param chan Which channel do these settings apply to? Use BOTH to set + * both channels at once. + * \param tx_sw1 Filter bank switch 1 + * \param tx_sw2 Filter bank switch 2 + * \param tx_sw3 Filter bank switch 3 + * \param select_lowband_mixer_path Enable the lowband mixer path + * \param enb_lowband_mixer Enable the actual lowband mixer + * \param atr_state If IDLE, only update the idle register. If ON, only + * enable the on register. If ANY, update both. + */ + void set_tx_switches( + const chan_sel_t chan, + const sw_trx_t trx_sw, + const tx_sw1_t tx_sw1, + const tx_sw2_t tx_sw2, + const tx_sw3_t tx_sw3, + const lowband_mixer_path_sel_t select_lowband_mixer_path, + const bool enb_lowband_mixer, + const atr_state_t atr_state = ANY + ); + + /*! Frequency-related settings, receive side + * + * Note: RX switch 1 is on set_rx_atr_bits(). + * + * \param chan Which channel do these settings apply to? Use BOTH to set + * both channels at once. + * \param rx_sw2 Filter bank switch 2 + * \param rx_sw3 Filter bank switch 3 + * \param rx_sw4 Filter bank switch 4 + * \param rx_sw5 Filter bank switch 5 + * \param rx_sw6 Filter bank switch 6 + * \param select_lowband_mixer_path Enable the lowband mixer path + * \param enb_lowband_mixer Enable the actual lowband mixer + * \param atr_state If IDLE, only update the idle register. If ON, only + * enable the on register. If ANY, update both. + */ + void set_rx_switches( + const chan_sel_t chan, + const rx_sw2_t rx_sw2, + const rx_sw3_t rx_sw3, + const rx_sw4_t rx_sw4, + const rx_sw5_t rx_sw5, + const rx_sw6_t rx_sw6, + const lowband_mixer_path_sel_t select_lowband_mixer_path, + const bool enb_lowband_mixer, + const atr_state_t atr_state = ANY + ); + + /*! ATR settings: LEDs, PAs, LNAs, ... for TX side + * + * Note on the tx_myk_enb bits: The AD9371 requires those pins to stay + * high for longer than we can guarantee with out clock-cycle accurate + * TX timing, so let's keep it turned on all the time. + * + * \param chan Which channel do these settings apply to? Use BOTH to set + * both channels at once. + * \param atr_state TX state for which these settings apply. + * \param tx_led State of the TX LED for this ATR state (on or off) + * \param trx_sw State of the TRX switch for this ATR state + * \param tx_pa_enb State of the TX PA for this ATR state (on or off) + * \param tx_amp_enb State of the TX amp for this ATR state (on or off) + * \param tx_myk_enb State of the AD9371 TX enable pin for this ATR state + */ + void set_tx_atr_bits( + const chan_sel_t chan, + const atr_state_t atr_state, + const bool tx_led, + const sw_trx_t trx_sw, + const bool tx_pa_enb, + const bool tx_amp_enb, + const bool tx_myk_enb + ); + + /*! ATR settings: LEDs, PAs, LNAs, ... for RX side + * + * Note on the rx_myk_enb bits: The AD9371 requires those pins to stay + * high for longer than we can guarantee with out clock-cycle accurate + * RX timing, so let's keep it turned on all the time. + * + * \param chan Which channel do these settings apply to? Use BOTH to set + * both channels at once. + * \param atr_state TX state for which these settings apply. + * \param rx_sw1 Filter bank sw1 of RX path + * \param rx_led State of the RX LED for this ATR state (on or off). This + * is the LED on the TX/RX port. + * \param rx2_led State of the RX LED for this ATR state (on or off). This + * is the LED on the RX2 port. + * \param rx_lna1_enb State of RX LNA 1 for this ATR state (on or off). + * \param rx_lna2_enb State of RX LNA 2 for this ATR state (on or off). + * \param rx_amp_enb State of RX amp for this ATR state (on or off). + * \param rx_myk_enb State of the AD9371 RX enable pin for this ATR state + */ + void set_rx_atr_bits( + const chan_sel_t chan, + const atr_state_t atr_state, + const rx_sw1_t rx_sw1, + const bool rx_led, + const bool rx2_led, + const bool rx_lna1_enb, + const bool rx_lna2_enb, + const bool rx_amp_enb, + const bool rx_myk_en + ); + +private: + //! Write functor: Take address / data pair, craft SPI transaction + using write_fn_t = std::function<void(uint32_t, uint32_t)>; + //! Read functor: Return value given address + using read_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 functor for regs pokes + write_fn_t _write_fn; + //! Read functor for regs peeks + read_fn_t _read_fn; + + //! Current state of the CPLD registers (for write operations only) + magnesium_cpld_regs_t _regs; + + //! Lock access to setters + std::mutex _set_mutex; +}; + +#endif /* INCLUDED_LIBUHD_MAGNESIUM_CPLD_CTRL_HPP */ +// vim: sw=4 et: |