aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2017-10-20 16:52:01 -0700
committerMartin Braun <martin.braun@ettus.com>2017-12-22 15:04:03 -0800
commita5452a42fe35f67e477279df13085fc609cbf6f1 (patch)
tree6e18d2175934bbef023d7183ec677507fec843e4 /host/lib/usrp
parent4087126476e11bae11f203f33be13c795bd67d35 (diff)
downloaduhd-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.txt1
-rw-r--r--host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.cpp333
-rw-r--r--host/lib/usrp/dboard/magnesium/magnesium_cpld_ctrl.hpp300
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: