diff options
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: | 
