From 890a8e10d65dd35e2ad3449bf3a3dce79ccb6083 Mon Sep 17 00:00:00 2001
From: Ashish Chaudhari <ashish@ettus.com>
Date: Tue, 19 Jul 2016 09:24:46 -0700
Subject: Added TwinRX daughterboard control.

---
 host/lib/usrp/dboard/CMakeLists.txt                |   4 +
 host/lib/usrp/dboard/db_twinrx.cpp                 | 337 ++++++++
 host/lib/usrp/dboard/twinrx/table_to_cpp.py        |  26 +
 host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp        | 643 +++++++++++++++
 host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp        | 101 +++
 host/lib/usrp/dboard/twinrx/twinrx_experts.cpp     | 637 +++++++++++++++
 host/lib/usrp/dboard/twinrx/twinrx_experts.hpp     | 630 +++++++++++++++
 host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp | 860 +++++++++++++++++++++
 host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp |  83 ++
 host/lib/usrp/dboard/twinrx/twinrx_io.hpp          | 524 +++++++++++++
 10 files changed, 3845 insertions(+)
 create mode 100644 host/lib/usrp/dboard/db_twinrx.cpp
 create mode 100644 host/lib/usrp/dboard/twinrx/table_to_cpp.py
 create mode 100644 host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp
 create mode 100644 host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp
 create mode 100644 host/lib/usrp/dboard/twinrx/twinrx_experts.cpp
 create mode 100644 host/lib/usrp/dboard/twinrx/twinrx_experts.hpp
 create mode 100644 host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp
 create mode 100644 host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp
 create mode 100644 host/lib/usrp/dboard/twinrx/twinrx_io.hpp

(limited to 'host/lib/usrp')

diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt
index 6cebecdbf..183496734 100644
--- a/host/lib/usrp/dboard/CMakeLists.txt
+++ b/host/lib/usrp/dboard/CMakeLists.txt
@@ -39,5 +39,9 @@ LIBUHD_APPEND_SOURCES(
     ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/db_e3x0.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_ctrl.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_experts.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_gain_tables.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/db_twinrx.cpp
 )
 
diff --git a/host/lib/usrp/dboard/db_twinrx.cpp b/host/lib/usrp/dboard/db_twinrx.cpp
new file mode 100644
index 000000000..e960f134f
--- /dev/null
+++ b/host/lib/usrp/dboard/db_twinrx.cpp
@@ -0,0 +1,337 @@
+//
+// Copyright 2014-15 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "twinrx/twinrx_experts.hpp"
+#include "twinrx/twinrx_ctrl.hpp"
+#include "twinrx/twinrx_io.hpp"
+#include <expert_factory.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/static.hpp>
+#include "dboard_ctor_args.hpp"
+#include <boost/assign/list_of.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/mutex.hpp>
+//#include <fstream>    //Needed for _expert->to_dot() below
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace uhd::usrp::dboard::twinrx;
+using namespace uhd::experts;
+
+static const dboard_id_t TWINRX_V100_000_ID(0x91);
+
+/*!
+ * twinrx_rcvr_fe is the dbaord class (dboard_base) that
+ * represents each front-end of a TwinRX board. UHD will
+ * create and hold two instances of this class per TwinRX
+ * dboard.
+ *
+ */
+class twinrx_rcvr_fe : public rx_dboard_base
+{
+public:
+    twinrx_rcvr_fe(
+        ctor_args_t args,
+        expert_container::sptr expert,
+        twinrx_ctrl::sptr ctrl
+    ) :
+        rx_dboard_base(args), _expert(expert), _ctrl(ctrl),
+        _ch_name(dboard_ctor_args_t::cast(args).sd_name)
+    {
+        //---------------------------------------------------------
+        // Add user-visible, channel specific properties to front-end tree
+        //---------------------------------------------------------
+
+        //Generic
+        get_rx_subtree()->create<std::string>("name")
+            .set("TwinRX RX" + _ch_name);
+        get_rx_subtree()->create<bool>("use_lo_offset")
+            .set(false);
+        get_rx_subtree()->create<std::string>("connection")
+            .set(_ch_name == "0" ? "II" : "QQ");    //Ch->ADC port mapping
+        static const double BW = 80e6;
+        get_rx_subtree()->create<double>("bandwidth/value")
+            .set(BW);
+        get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+            .set(freq_range_t(BW, BW));
+
+        //Frequency Specific
+        get_rx_subtree()->create<meta_range_t>("freq/range")
+            .set(freq_range_t(10e6, 6.0e9));
+        expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(),
+            "freq/value", prepend_ch("freq/desired", _ch_name), prepend_ch("freq/coerced", _ch_name),
+            1.0e9, AUTO_RESOLVE_ON_READ_WRITE);
+        get_rx_subtree()->create<device_addr_t>("tune_args")
+            .set(device_addr_t());
+
+        static const double DEFAULT_IF_FREQ = 150e6;
+        meta_range_t if_freq_range;
+        if_freq_range.push_back(range_t(-DEFAULT_IF_FREQ-(BW/2), -DEFAULT_IF_FREQ+(BW/2)));
+        if_freq_range.push_back(range_t( DEFAULT_IF_FREQ-(BW/2),  DEFAULT_IF_FREQ+(BW/2)));
+        get_rx_subtree()->create<meta_range_t>("if_freq/range")
+            .set(if_freq_range);
+        expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(),
+            "if_freq/value", prepend_ch("if_freq/desired", _ch_name), prepend_ch("if_freq/coerced", _ch_name),
+            DEFAULT_IF_FREQ, AUTO_RESOLVE_ON_WRITE);
+
+        //LO Specific
+        get_rx_subtree()->create<meta_range_t>("los/LO1/freq/range")
+            .set(freq_range_t(2.0e9, 6.8e9));
+        expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(),
+            "los/LO1/freq/value", prepend_ch("los/LO1/freq/desired", _ch_name), prepend_ch("los/LO1/freq/coerced", _ch_name),
+            0.0, AUTO_RESOLVE_ON_READ_WRITE);
+        get_rx_subtree()->create<meta_range_t>("los/LO2/freq/range")
+            .set(freq_range_t(1.0e9, 3.0e9));
+        expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(),
+            "los/LO2/freq/value", prepend_ch("los/LO2/freq/desired", _ch_name), prepend_ch("los/LO2/freq/coerced", _ch_name),
+            0.0, AUTO_RESOLVE_ON_READ_WRITE);
+        get_rx_subtree()->create<std::vector<std::string> >("los/all/source/options")
+            .set(boost::assign::list_of("internal")("external")("companion")("disabled"));
+        expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(),
+            "los/all/source/value", prepend_ch("los/all/source", _ch_name),
+            "internal", AUTO_RESOLVE_ON_WRITE);
+        expert_factory::add_prop_node<bool>(_expert, get_rx_subtree(),
+            "los/all/export", prepend_ch("los/all/export", _ch_name),
+            false, AUTO_RESOLVE_ON_WRITE);
+
+        //Gain Specific
+        get_rx_subtree()->create<meta_range_t>("gains/all/range")
+            .set(gain_range_t(0, 95, double(1.0)));
+        expert_factory::add_prop_node<double>(_expert, get_rx_subtree(),
+            "gains/all/value", prepend_ch("gain", _ch_name),
+            0.0, AUTO_RESOLVE_ON_WRITE);
+        get_rx_subtree()->create<std::vector<std::string> >("gains/all/profile/options")
+            .set(boost::assign::list_of("low-noise")("low-distortion")("default"));
+        expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(),
+            "gains/all/profile/value", prepend_ch("gain_profile", _ch_name),
+            "default", AUTO_RESOLVE_ON_WRITE);
+
+        //Antenna Specific
+        get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+            .set(boost::assign::list_of("RX1")("RX2"));
+        expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(),
+            "antenna/value", prepend_ch("antenna", _ch_name),
+            (_ch_name == "0" ? "RX1" : "RX2"), AUTO_RESOLVE_ON_WRITE);
+        expert_factory::add_prop_node<bool>(_expert, get_rx_subtree(),
+            "enabled", prepend_ch("enabled", _ch_name),
+            false, AUTO_RESOLVE_ON_WRITE);
+
+        //Readback
+        get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+            .set_publisher(boost::bind(&twinrx_rcvr_fe::get_lo_locked, this));
+
+        //---------------------------------------------------------
+        // Add internal channel-specific data nodes to expert
+        //---------------------------------------------------------
+        expert_factory::add_data_node<lo_inj_side_t>(_expert,
+            prepend_ch("ch/LO1/inj_side", _ch_name), INJ_LOW_SIDE);
+        expert_factory::add_data_node<lo_inj_side_t>(_expert,
+            prepend_ch("ch/LO2/inj_side", _ch_name), INJ_LOW_SIDE);
+        expert_factory::add_data_node<twinrx_ctrl::signal_path_t>(_expert,
+            prepend_ch("ch/signal_path", _ch_name), twinrx_ctrl::PATH_LOWBAND);
+        expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(_expert,
+            prepend_ch("ch/lb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1);
+        expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(_expert,
+            prepend_ch("ch/hb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1);
+        expert_factory::add_data_node<bool>(_expert,
+            prepend_ch("ch/lb_preamp_presel", _ch_name), false);
+        expert_factory::add_data_node<bool>(_expert,
+            prepend_ch("ant/lb_preamp_presel", _ch_name), false);
+        expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(_expert,
+            prepend_ch("ch/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS);
+        expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(_expert,
+            prepend_ch("ant/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS);
+        expert_factory::add_data_node<bool>(_expert,
+            prepend_ch("ch/preamp2", _ch_name), false);
+        expert_factory::add_data_node<bool>(_expert,
+            prepend_ch("ant/preamp2", _ch_name), false);
+        expert_factory::add_data_node<boost::uint8_t>(_expert,
+            prepend_ch("ch/input_atten", _ch_name), 0);
+        expert_factory::add_data_node<boost::uint8_t>(_expert,
+            prepend_ch("ant/input_atten", _ch_name), 0);
+        expert_factory::add_data_node<boost::uint8_t>(_expert,
+            prepend_ch("ch/lb_atten", _ch_name), 0);
+        expert_factory::add_data_node<boost::uint8_t>(_expert,
+            prepend_ch("ch/hb_atten", _ch_name), 0);
+        expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(_expert,
+            prepend_ch("ch/LO1/source", _ch_name), twinrx_ctrl::LO_INTERNAL);
+        expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(_expert,
+            prepend_ch("ch/LO2/source", _ch_name), twinrx_ctrl::LO_INTERNAL);
+        expert_factory::add_data_node<lo_synth_mapping_t>(_expert,
+            prepend_ch("synth/LO1/mapping", _ch_name), MAPPING_NONE);
+        expert_factory::add_data_node<lo_synth_mapping_t>(_expert,
+            prepend_ch("synth/LO2/mapping", _ch_name), MAPPING_NONE);
+
+    }
+
+    virtual ~twinrx_rcvr_fe(void)
+    {
+    }
+
+    sensor_value_t get_lo_locked()
+    {
+        bool locked = true;
+        twinrx_ctrl::channel_t ch = (_ch_name == "0") ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2;
+        locked &= _ctrl->read_lo1_locked(ch);
+        locked &= _ctrl->read_lo2_locked(ch);
+        return sensor_value_t("LO", locked, "locked", "unlocked");
+    }
+
+private:
+    expert_container::sptr _expert;
+    twinrx_ctrl::sptr      _ctrl;
+    const std::string      _ch_name;
+};
+
+/*!
+ * twinrx_rcvr is the top-level container for each
+ * TwinRX board. UHD will hold one instance of this
+ * class per TwinRX dboard. This class is responsible
+ * for owning all the control classes for the board.
+ *
+ */
+class twinrx_rcvr : public rx_dboard_base
+{
+public:
+    typedef boost::shared_ptr<twinrx_rcvr> sptr;
+
+    twinrx_rcvr(ctor_args_t args) : rx_dboard_base(args)
+    {
+        _db_iface = get_iface();
+        twinrx_gpio::sptr gpio_iface = boost::make_shared<twinrx_gpio>(_db_iface);
+        twinrx_cpld_regmap::sptr cpld_regs = boost::make_shared<twinrx_cpld_regmap>();
+        cpld_regs->initialize(*gpio_iface, false);
+        _ctrl = twinrx_ctrl::make(_db_iface, gpio_iface, cpld_regs);
+        _expert = expert_factory::create_container("twinrx_expert");
+    }
+
+    virtual ~twinrx_rcvr(void)
+    {
+    }
+
+    inline expert_container::sptr get_expert() {
+        return _expert;
+    }
+
+    inline twinrx_ctrl::sptr get_ctrl() {
+        return _ctrl;
+    }
+
+    virtual void initialize()
+    {
+        //---------------------------------------------------------
+        // Add internal channel-agnostic data nodes to expert
+        //---------------------------------------------------------
+        expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(_expert,
+            "com/LO1/export_source", twinrx_ctrl::LO_EXPORT_DISABLED);
+        expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(_expert,
+            "com/LO2/export_source", twinrx_ctrl::LO_EXPORT_DISABLED);
+        expert_factory::add_data_node<twinrx_ctrl::antenna_mapping_t>(_expert,
+            "com/ant_mapping", twinrx_ctrl::ANTX_NATIVE);
+        expert_factory::add_data_node<twinrx_ctrl::cal_mode_t>(_expert,
+            "com/cal_mode", twinrx_ctrl::CAL_DISABLED);
+        expert_factory::add_data_node<bool>(_expert,
+            "com/synth/LO1/hopping_enabled", false);
+        expert_factory::add_data_node<bool>(_expert,
+            "com/synth/LO2/hopping_enabled", false);
+
+        //---------------------------------------------------------
+        // Add workers to expert
+        //---------------------------------------------------------
+        //Channel (front-end) specific
+        BOOST_FOREACH(const std::string& fe, _fe_names) {
+            expert_factory::add_worker_node<twinrx_freq_path_expert>(_expert, _expert->node_retriever(), fe);
+            expert_factory::add_worker_node<twinrx_freq_coercion_expert>(_expert, _expert->node_retriever(), fe);
+            expert_factory::add_worker_node<twinrx_chan_gain_expert>(_expert, _expert->node_retriever(), fe);
+            expert_factory::add_worker_node<twinrx_nyquist_expert>(_expert, _expert->node_retriever(), fe, _db_iface);
+        }
+
+        //Channel (front-end) agnostic
+        expert_factory::add_worker_node<twinrx_lo_config_expert>(_expert, _expert->node_retriever());
+        expert_factory::add_worker_node<twinrx_lo_mapping_expert>(_expert, _expert->node_retriever(), STAGE_LO1);
+        expert_factory::add_worker_node<twinrx_lo_mapping_expert>(_expert, _expert->node_retriever(), STAGE_LO2);
+        expert_factory::add_worker_node<twinrx_antenna_expert>(_expert, _expert->node_retriever());
+        expert_factory::add_worker_node<twinrx_ant_gain_expert>(_expert, _expert->node_retriever());
+        expert_factory::add_worker_node<twinrx_settings_expert>(_expert, _expert->node_retriever(), _ctrl);
+
+        /*//Expert debug code
+        std::ofstream dot_file("/tmp/twinrx.dot", std::ios::out);
+        dot_file << _expert->to_dot();
+        dot_file.close();
+        */
+
+        _expert->debug_audit();
+        _expert->resolve_all(true);
+    }
+
+    static dboard_base::sptr make_twinrx_fe(dboard_base::ctor_args_t args)
+    {
+        const dboard_ctor_args_t& db_args = dboard_ctor_args_t::cast(args);
+        sptr container = boost::dynamic_pointer_cast<twinrx_rcvr>(db_args.rx_container);
+        if (container) {
+            dboard_base::sptr fe = dboard_base::sptr(
+                new twinrx_rcvr_fe(args, container->get_expert(), container->get_ctrl()));
+            container->add_twinrx_fe(db_args.sd_name);
+            return fe;
+        } else {
+            throw uhd::assertion_error("error creating twinrx frontend");
+        }
+    }
+
+protected:
+    inline void add_twinrx_fe(const std::string& name) {
+        _fe_names.push_back(name);
+    }
+
+private:
+    typedef std::map<std::string, dboard_base::sptr> twinrx_fe_map_t;
+
+    dboard_iface::sptr          _db_iface;
+    twinrx_ctrl::sptr           _ctrl;
+    std::vector<std::string>    _fe_names;
+    expert_container::sptr      _expert;
+};
+
+/*!
+ * Initialization Sequence for each TwinRX board:
+ * - make_twinrx_container is called which creates an instance of twinrx_rcvr
+ * - twinrx_rcvr::make_twinrx_fe is called with channel "0" which creates an instance of twinrx_rcvr_fe
+ * - twinrx_rcvr::make_twinrx_fe is called with channel "1" which creates an instance of twinrx_rcvr_fe
+ * - twinrx_rcvr::initialize is called with finishes the init sequence
+ *
+ */
+static dboard_base::sptr make_twinrx_container(dboard_base::ctor_args_t args)
+{
+    return dboard_base::sptr(new twinrx_rcvr(args));
+}
+
+UHD_STATIC_BLOCK(reg_twinrx_dboards)
+{
+    dboard_manager::register_dboard_restricted(
+        TWINRX_V100_000_ID,
+        &twinrx_rcvr::make_twinrx_fe,
+        "TwinRX v1.0",
+        boost::assign::list_of("0")("1"),
+        &make_twinrx_container
+    );
+}
diff --git a/host/lib/usrp/dboard/twinrx/table_to_cpp.py b/host/lib/usrp/dboard/twinrx/table_to_cpp.py
new file mode 100644
index 000000000..ea0e3bf66
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/table_to_cpp.py
@@ -0,0 +1,26 @@
+#!/usr/bin/python
+
+import sys
+
+if len(sys.argv) != 3:
+    print 'Usage: ' + sys.argv[0] + ' <table filename> <table name>'
+    sys.exit(1)
+
+with open(sys.argv[1], 'rb') as f:
+    print ('static const std::vector<twinrx_gain_config_t> ' +
+           sys.argv[2] + ' = boost::assign::list_of')
+    i = -1
+    for line in f.readlines():
+        if (i != -1):
+            row = line.strip().split(',')
+            print ('    ( twinrx_gain_config_t( %s, %6.1f, %s, %s, %s, %s ) )' % (
+                str(i).rjust(5),
+                float(row[0].rjust(6)),
+                row[1].rjust(6), row[2].rjust(6),
+                ('true' if int(row[3]) == 1 else 'false').rjust(5),
+                ('true' if int(row[4]) == 1 else 'false').rjust(5)))
+        else:
+            print ('    //                      %s, %s, %s, %s, %s, %s' % (
+                'Index', '  Gain', 'Atten1', 'Atten2', ' Amp1', ' Amp2'))
+        i += 1
+    print ';'
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp
new file mode 100644
index 000000000..172992f0a
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp
@@ -0,0 +1,643 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <boost/thread/thread.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include "twinrx_ctrl.hpp"
+#include "adf435x.hpp"
+#include "adf5355.hpp"
+
+using namespace uhd;
+using namespace usrp;
+using namespace dboard::twinrx;
+typedef twinrx_cpld_regmap rm;
+
+static boost::uint32_t bool2bin(bool x) { return x ? 1 : 0; }
+
+static const double TWINRX_DESIRED_REFERENCE_FREQ = 50e6;
+
+class twinrx_ctrl_impl : public twinrx_ctrl {
+public:
+    twinrx_ctrl_impl(
+        dboard_iface::sptr db_iface,
+        twinrx_gpio::sptr gpio_iface,
+        twinrx_cpld_regmap::sptr cpld_regmap
+    ) : _db_iface(db_iface), _gpio_iface(gpio_iface), _cpld_regs(cpld_regmap)
+    {
+
+        //Turn on switcher and wait for power good
+        _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 1);
+        size_t timeout_ms = 100;
+        while (_gpio_iface->get_field(twinrx_gpio::FIELD_SWPS_PWR_GOOD) == 0) {
+            boost::this_thread::sleep(boost::posix_time::microsec(1000));
+            if (--timeout_ms == 0) {
+                throw uhd::runtime_error("power supply failure");
+            }
+        }
+        //Initialize synthesizer objects
+        _lo1_iface[size_t(CH1)] = adf5355_iface::make(
+                boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1));
+        _lo1_iface[size_t(CH2)] = adf5355_iface::make(
+                boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1));
+
+        _lo2_iface[size_t(CH1)] = adf435x_iface::make_adf4351(
+                boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1));
+        _lo2_iface[size_t(CH2)] = adf435x_iface::make_adf4351(
+                boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1));
+
+        // Assert synthesizer chip enables
+        _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH1, 1);
+        _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH2, 1);
+        _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH1, 1);
+        _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH2, 1);
+
+        //Initialize default state
+        set_chan_enabled(BOTH, false, false);
+        set_preamp1(BOTH, PREAMP_BYPASS, false);
+        set_preamp2(BOTH, false, false);
+        set_lb_preamp_preselector(BOTH, false, false);
+        set_signal_path(BOTH, PATH_LOWBAND, false);
+        set_lb_preselector(BOTH, PRESEL_PATH3, false);
+        set_hb_preselector(BOTH, PRESEL_PATH1, false);
+        set_input_atten(BOTH, 31, false);
+        set_lb_atten(BOTH, 31, false);
+        set_hb_atten(BOTH, 31, false);
+        set_lo1_source(BOTH, LO_INTERNAL, false);
+        set_lo2_source(BOTH, LO_INTERNAL, false);
+        set_lo1_export_source(LO_EXPORT_DISABLED, false);
+        set_lo2_export_source(LO_EXPORT_DISABLED, false);
+        set_antenna_mapping(ANTX_NATIVE, false);
+        set_crossover_cal_mode(CAL_DISABLED, false);
+        commit();
+
+        //Initialize clocks and LO
+        bool found_rate = false;
+        BOOST_FOREACH(double rate, _db_iface->get_clock_rates(dboard_iface::UNIT_TX)) {
+            found_rate |= uhd::math::frequencies_are_equal(rate, TWINRX_DESIRED_REFERENCE_FREQ);
+        }
+        BOOST_FOREACH(double rate, _db_iface->get_clock_rates(dboard_iface::UNIT_RX)) {
+            found_rate |= uhd::math::frequencies_are_equal(rate, TWINRX_DESIRED_REFERENCE_FREQ);
+        }
+        if (not found_rate) {
+            throw uhd::runtime_error("TwinRX not supported on this motherboard");
+        }
+        _db_iface->set_clock_rate(dboard_iface::UNIT_TX, TWINRX_DESIRED_REFERENCE_FREQ);
+        _db_iface->set_clock_rate(dboard_iface::UNIT_RX, TWINRX_DESIRED_REFERENCE_FREQ);
+
+        _db_iface->set_clock_enabled(dboard_iface::UNIT_TX, true);
+        _db_iface->set_clock_enabled(dboard_iface::UNIT_RX, true);
+        for (size_t i = 0; i < NUM_CHANS; i++) {
+            _config_lo1_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2);
+            _config_lo2_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2);
+            _lo1_iface[i]->set_output_power(adf5355_iface::OUTPUT_POWER_5DBM);
+            _lo1_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ);
+            _lo1_iface[i]->set_muxout_mode(adf5355_iface::MUXOUT_DLD);
+            _lo1_iface[i]->set_frequency(3e9, 1.0e3);
+            _lo2_iface[i]->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED);
+            _lo2_iface[i]->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM);
+            _lo2_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ);
+            _lo2_iface[i]->set_muxout_mode(adf435x_iface::MUXOUT_DLD);
+            _lo1_iface[i]->commit();
+            _lo2_iface[i]->commit();
+        }
+        _config_lo1_route(LO_CONFIG_NONE);
+        _config_lo2_route(LO_CONFIG_NONE);
+    }
+
+    ~twinrx_ctrl_impl()
+    {
+        UHD_SAFE_CALL(
+            boost::lock_guard<boost::mutex> lock(_mutex);
+            _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 0);
+        )
+    }
+
+    void commit()
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        _commit();
+    }
+
+    void set_chan_enabled(channel_t ch, bool enabled, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::AMP_LO1_EN_CH1, bool2bin(enabled));
+            _cpld_regs->if0_reg3.set(rm::if0_reg3_t::IF1_IF2_EN_CH1, bool2bin(enabled));
+            _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH1, bool2bin(enabled));
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::AMP_LO1_EN_CH2, bool2bin(enabled));
+            _cpld_regs->if0_reg4.set(rm::if0_reg4_t::IF1_IF2_EN_CH2, bool2bin(enabled));
+            _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH2, bool2bin(enabled));
+        }
+        if (commit) _commit();
+    }
+
+    void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::SWPA1_CTL_CH1, bool2bin(value==PREAMP_HIGHBAND));
+            _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::SWPA2_CTRL_CH1, bool2bin(value==PREAMP_BYPASS));
+            _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::HB_PREAMP_EN_CH1, bool2bin(value==PREAMP_HIGHBAND));
+            _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::LB_PREAMP_EN_CH1, bool2bin(value==PREAMP_LOWBAND));
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SWPA1_CTRL_CH2, bool2bin(value==PREAMP_HIGHBAND));
+            _cpld_regs->rf2_reg5.set(rm::rf2_reg5_t::SWPA2_CTRL_CH2, bool2bin(value==PREAMP_BYPASS));
+            _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::HB_PREAMP_EN_CH2, bool2bin(value==PREAMP_HIGHBAND));
+            _cpld_regs->rf2_reg6.set(rm::rf2_reg6_t::LB_PREAMP_EN_CH2, bool2bin(value==PREAMP_LOWBAND));
+        }
+        if (commit) _commit();
+    }
+
+    void set_preamp2(channel_t ch, bool enabled, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SWPA4_CTRL_CH1, bool2bin(not enabled));
+            _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::PREAMP2_EN_CH1, bool2bin(enabled));
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SWPA4_CTRL_CH2, bool2bin(not enabled));
+            _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::PREAMP2_EN_CH2, bool2bin(enabled));
+        }
+        if (commit) _commit();
+    }
+
+    void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SWPA3_CTRL_CH1, bool2bin(not enabled));
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::SWPA3_CTRL_CH2, bool2bin(not enabled));
+        }
+        if (commit) _commit();
+    }
+
+    void set_signal_path(channel_t ch, signal_path_t path, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::SW11_CTRL_CH1, bool2bin(path==PATH_LOWBAND));
+            _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::SW12_CTRL_CH1, bool2bin(path==PATH_LOWBAND));
+            _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::HB_PRESEL_PGA_EN_CH1, bool2bin(path==PATH_HIGHBAND));
+            _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW6_CTRL_CH1, bool2bin(path==PATH_LOWBAND));
+            _cpld_regs->if0_reg3.set(rm::if0_reg3_t::SW13_CTRL_CH1, bool2bin(path==PATH_LOWBAND));
+            _cpld_regs->if0_reg2.set(rm::if0_reg2_t::AMP_LB_IF1_EN_CH1, bool2bin(path==PATH_LOWBAND));
+            _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_HB_IF1_EN_CH1, bool2bin(path==PATH_HIGHBAND));
+            _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::AMP_HB_EN_CH1, bool2bin(path==PATH_HIGHBAND));
+            _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::AMP_LB_EN_CH1, bool2bin(path==PATH_LOWBAND));
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW11_CTRL_CH2, bool2bin(path==PATH_LOWBAND));
+            _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW12_CTRL_CH2, bool2bin(path==PATH_LOWBAND));
+            _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::HB_PRESEL_PGA_EN_CH2, bool2bin(path==PATH_HIGHBAND));
+            _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW6_CTRL_CH2, bool2bin(path==PATH_HIGHBAND));
+            _cpld_regs->if0_reg6.set(rm::if0_reg6_t::SW13_CTRL_CH2, bool2bin(path==PATH_HIGHBAND));
+            _cpld_regs->if0_reg2.set(rm::if0_reg2_t::AMP_LB_IF1_EN_CH2, bool2bin(path==PATH_LOWBAND));
+            _cpld_regs->if0_reg6.set(rm::if0_reg6_t::AMP_HB_IF1_EN_CH2, bool2bin(path==PATH_HIGHBAND));
+            _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::AMP_HB_EN_CH2, bool2bin(path==PATH_HIGHBAND));
+            _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::AMP_LB_EN_CH2, bool2bin(path==PATH_LOWBAND));
+        }
+        if (commit) _commit();
+    }
+
+    void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        boost::uint32_t sw7val = 0, sw8val = 0;
+        switch (path) {
+            case PRESEL_PATH1: sw7val = 3; sw8val = 1; break;
+            case PRESEL_PATH2: sw7val = 2; sw8val = 0; break;
+            case PRESEL_PATH3: sw7val = 0; sw8val = 2; break;
+            case PRESEL_PATH4: sw7val = 1; sw8val = 3; break;
+            default: UHD_THROW_INVALID_CODE_PATH();
+        }
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW7_CTRL_CH1, sw7val);
+            _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW8_CTRL_CH1, sw8val);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW7_CTRL_CH2, sw7val);
+            _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW8_CTRL_CH2, sw8val);
+        }
+        if (commit) _commit();
+    }
+
+    void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        boost::uint32_t sw9ch1val = 0, sw10ch1val = 0, sw9ch2val = 0, sw10ch2val = 0;
+        switch (path) {
+            case PRESEL_PATH1: sw9ch1val = 3; sw10ch1val = 0; sw9ch2val = 0; sw10ch2val = 3; break;
+            case PRESEL_PATH2: sw9ch1val = 1; sw10ch1val = 2; sw9ch2val = 1; sw10ch2val = 1; break;
+            case PRESEL_PATH3: sw9ch1val = 2; sw10ch1val = 1; sw9ch2val = 2; sw10ch2val = 2; break;
+            case PRESEL_PATH4: sw9ch1val = 0; sw10ch1val = 3; sw9ch2val = 3; sw10ch2val = 0; break;
+            default: UHD_THROW_INVALID_CODE_PATH();
+        }
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW9_CTRL_CH1, sw9ch1val);
+            _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW10_CTRL_CH1, sw10ch1val);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW9_CTRL_CH2, sw9ch2val);
+            _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW10_CTRL_CH2, sw10ch2val);
+        }
+        if (commit) _commit();
+
+    }
+
+    void set_input_atten(channel_t ch, boost::uint8_t atten, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf0_reg0.set(rm::rf0_reg0_t::ATTEN_IN_CH1, atten&0x1F);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf0_reg4.set(rm::rf0_reg4_t::ATTEN_IN_CH2, atten&0x1F);
+        }
+        if (commit) _commit();
+    }
+
+    void set_lb_atten(channel_t ch, boost::uint8_t atten, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf2_reg0.set(rm::rf2_reg0_t::ATTEN_LB_CH1, atten&0x1F);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf2_reg4.set(rm::rf2_reg4_t::ATTEN_LB_CH2, atten&0x1F);
+        }
+        if (commit) _commit();
+    }
+
+    void set_hb_atten(channel_t ch, boost::uint8_t atten, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf1_reg0.set(rm::rf1_reg0_t::ATTEN_HB_CH1, atten&0x1F);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf1_reg4.set(rm::rf1_reg4_t::ATTEN_HB_CH2, atten&0x1F);
+        }
+        if (commit) _commit();
+    }
+
+    void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW14_CTRL_CH2, bool2bin(source!=LO_COMPANION));
+            _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW15_CTRL_CH1, bool2bin(source==LO_EXTERNAL));
+            _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW16_CTRL_CH1, bool2bin(source!=LO_INTERNAL));
+            _lo1_src[size_t(CH1)] = source;
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW14_CTRL_CH1, bool2bin(source==LO_COMPANION));
+            _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW15_CTRL_CH2, bool2bin(source!=LO_INTERNAL));
+            _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::SW16_CTRL_CH2, bool2bin(source==LO_INTERNAL));
+            _lo1_src[size_t(CH2)] = source;
+        }
+        if (commit) _commit();
+    }
+
+    void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (ch == CH1 or ch == BOTH) {
+            _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW19_CTRL_CH2, bool2bin(source==LO_COMPANION));
+            _cpld_regs->if0_reg1.set(rm::if0_reg1_t::SW20_CTRL_CH1, bool2bin(source==LO_COMPANION));
+            _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW21_CTRL_CH1, bool2bin(source==LO_INTERNAL));
+            _lo2_src[size_t(CH1)] = source;
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW19_CTRL_CH1, bool2bin(source==LO_EXTERNAL));
+            _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW20_CTRL_CH2, bool2bin(source==LO_INTERNAL||source==LO_DISABLED));
+            _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW21_CTRL_CH2, bool2bin(source==LO_INTERNAL));
+            _lo2_src[size_t(CH2)] = source;
+        }
+        if (commit) _commit();
+    }
+
+    void set_lo1_export_source(lo_export_source_t source, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        //SW22 may conflict with the cal switch but this attr takes priority and we assume
+        //that the cal switch is disabled (by disabling it!)
+        _set_cal_mode(CAL_DISABLED, source);
+        _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW23_CTRL, bool2bin(source!=LO_CH1_SYNTH));
+        _lo1_export = source;
+
+        if (commit) _commit();
+    }
+
+    void set_lo2_export_source(lo_export_source_t source, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        _cpld_regs->if0_reg7.set(rm::if0_reg7_t::SW24_CTRL_CH2, bool2bin(source==LO_CH2_SYNTH));
+        _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW25_CTRL, bool2bin(source!=LO_CH1_SYNTH));
+        _cpld_regs->if0_reg3.set(rm::if0_reg3_t::SW24_CTRL_CH1, bool2bin(source!=LO_CH1_SYNTH));
+        _lo2_export = source;
+
+        if (commit) _commit();
+    }
+
+    void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+
+        enum switch_path_t { CONNECT, TERM, EXPORT, IMPORT, SWAP };
+        switch_path_t path1, path2;
+
+        switch (mapping) {
+        case ANTX_NATIVE:
+            path1 = CONNECT; path2 = CONNECT; break;
+        case ANT1_SHARED:
+            path1 = EXPORT;  path2 = IMPORT;  break;
+        case ANT2_SHARED:
+            path1 = IMPORT;  path2 = EXPORT;  break;
+        case ANTX_SWAPPED:
+            path1 = SWAP;    path2 = SWAP;    break;
+        default:
+            path1 = TERM;    path2 = TERM;    break;
+        }
+
+        _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW3_CTRL_CH1, bool2bin(path1==EXPORT||path1==SWAP));
+        _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW4_CTRL_CH1, bool2bin(!(path1==IMPORT||path1==SWAP)));
+        _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW5_CTRL_CH1, bool2bin(path1==CONNECT));
+        _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW3_CTRL_CH2, bool2bin(path2==EXPORT||path2==SWAP));
+        _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW4_CTRL_CH2, bool2bin(path2==IMPORT||path2==SWAP));
+        _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW5_CTRL_CH2, bool2bin(path2==CONNECT));
+
+        if (commit) _commit();
+    }
+
+    void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        if (_lo1_export == LO_CH1_SYNTH && cal_mode == CAL_CH2) {
+            throw uhd::runtime_error("cannot enable cal crossover on CH2 when LO1 in CH1 is exported");
+        }
+        if (_lo1_export == LO_CH2_SYNTH && cal_mode == CAL_CH1) {
+            throw uhd::runtime_error("cannot enable cal crossover on CH1 when LO1 in CH2 is exported");
+        }
+        _set_cal_mode(cal_mode, _lo1_export);
+
+        if (commit) _commit();
+    }
+
+    double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        static const double RESOLUTION = 1e3;
+
+        double coerced_freq = 0.0;
+        if (ch == CH1 or ch == BOTH) {
+            coerced_freq = _lo1_iface[size_t(CH1)]->set_frequency(freq, RESOLUTION, false);
+            _lo1_freq[size_t(CH1)] = tune_freq_t(freq);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            coerced_freq = _lo1_iface[size_t(CH2)]->set_frequency(freq, RESOLUTION, false);
+            _lo1_freq[size_t(CH2)] = tune_freq_t(freq);
+        }
+
+        if (commit) _commit();
+        return coerced_freq;
+    }
+
+    double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        static const double PRESCALER_THRESH = 3.6e9;
+
+        double coerced_freq = 0.0;
+        if (ch == CH1 or ch == BOTH) {
+            _lo2_iface[size_t(CH1)]->set_prescaler(freq > PRESCALER_THRESH ?
+                adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
+            coerced_freq = _lo2_iface[size_t(CH1)]->set_frequency(freq, false, false);
+            _lo2_freq[size_t(CH1)] = tune_freq_t(freq);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            _lo2_iface[size_t(CH2)]->set_prescaler(freq > PRESCALER_THRESH ?
+                adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
+            coerced_freq = _lo2_iface[size_t(CH2)]->set_frequency(freq, false, false);
+            _lo2_freq[size_t(CH2)] = tune_freq_t(freq);
+        }
+
+        if (commit) _commit();
+        return coerced_freq;
+    }
+
+    bool read_lo1_locked(channel_t ch)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+
+        bool locked = true;
+        if (ch == CH1 or ch == BOTH) {
+            locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH1) == 1);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH2) == 1);
+        }
+        return locked;
+    }
+
+    bool read_lo2_locked(channel_t ch)
+    {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+
+        bool locked = true;
+        if (ch == CH1 or ch == BOTH) {
+            locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH1) == 1);
+        }
+        if (ch == CH2 or ch == BOTH) {
+            locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH2) == 1);
+        }
+        return locked;
+    }
+
+private:    //Functions
+    void _set_cal_mode(cal_mode_t cal_mode, lo_export_source_t lo1_export_src)
+    {
+        _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW17_CTRL_CH1, bool2bin(cal_mode!=CAL_CH1));
+        _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::SW17_CTRL_CH2, bool2bin(cal_mode!=CAL_CH2));
+        _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW18_CTRL_CH1, bool2bin(cal_mode!=CAL_CH1));
+        _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW18_CTRL_CH2, bool2bin(cal_mode!=CAL_CH2));
+        _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW22_CTRL_CH1, bool2bin((lo1_export_src!=LO_CH1_SYNTH)||(cal_mode==CAL_CH1)));
+        _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW22_CTRL_CH2, bool2bin((lo1_export_src!=LO_CH2_SYNTH)||(cal_mode==CAL_CH2)));
+    }
+
+    void _config_lo1_route(lo_config_route_t source)
+    {
+        //Route SPI LEs through CPLD (will not assert them)
+        _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH1, bool2bin(source==LO_CONFIG_CH1||source==LO_CONFIG_BOTH));
+        _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH2, bool2bin(source==LO_CONFIG_CH2||source==LO_CONFIG_BOTH));
+        _cpld_regs->rf0_reg2.flush();
+    }
+
+    void _config_lo2_route(lo_config_route_t source)
+    {
+        //Route SPI LEs through CPLD (will not assert them)
+        _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH1, bool2bin(source==LO_CONFIG_CH1||source==LO_CONFIG_BOTH));
+        _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH2, bool2bin(source==LO_CONFIG_CH2||source==LO_CONFIG_BOTH));
+        _cpld_regs->if0_reg2.flush();
+    }
+
+    void _write_lo_spi(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> &regs)
+    {
+        BOOST_FOREACH(boost::uint32_t reg, regs) {
+             spi_config_t spi_config = spi_config_t(spi_config_t::EDGE_RISE);
+             spi_config.use_custom_divider = true;
+             spi_config.divider = 67;
+            _db_iface->write_spi(unit, spi_config, reg, 32);
+        }
+    }
+
+    void _commit()
+    {
+        //Commit everything except the LO synthesizers
+        _cpld_regs->flush();
+
+        // Disable unused LO synthesizers
+        _lo1_enable[size_t(CH1)] = _lo1_src[size_t(CH1)] == LO_INTERNAL  ||
+                                   _lo1_src[size_t(CH2)] == LO_COMPANION ||
+                                   _lo1_export == LO_CH1_SYNTH;
+
+        _lo1_enable[size_t(CH2)] = _lo1_src[size_t(CH2)] == LO_INTERNAL  ||
+                                   _lo1_src[size_t(CH1)] == LO_COMPANION ||
+                                   _lo1_export == LO_CH2_SYNTH;
+        _lo2_enable[size_t(CH1)] = _lo2_src[size_t(CH1)] == LO_INTERNAL  ||
+                                   _lo2_src[size_t(CH2)] == LO_COMPANION ||
+                                   _lo2_export == LO_CH1_SYNTH;
+
+        _lo2_enable[size_t(CH2)] = _lo2_src[size_t(CH2)] == LO_INTERNAL  ||
+                                   _lo2_src[size_t(CH1)] == LO_COMPANION ||
+                                   _lo2_export == LO_CH2_SYNTH;
+
+        _lo1_iface[size_t(CH1)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH1)].get());
+        _lo1_iface[size_t(CH2)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH2)].get());
+
+        _lo2_iface[size_t(CH1)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH1)].get());
+        _lo2_iface[size_t(CH2)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH2)].get());
+
+        //Commit LO1 frequency
+        // Commit Channel 1's settings to both channels simultaneously if the frequency is the same.
+        bool simultaneous_commit_lo1 = _lo1_freq[size_t(CH1)].is_dirty() and
+                                       _lo1_freq[size_t(CH2)].is_dirty() and
+                                       _lo1_freq[size_t(CH1)].get() == _lo1_freq[size_t(CH2)].get() and
+                                       _lo1_enable[size_t(CH1)].get() == _lo1_enable[size_t(CH2)].get();
+
+        if (simultaneous_commit_lo1) {
+            _config_lo1_route(LO_CONFIG_BOTH);
+            //Only commit one of the channels. The route LO_CONFIG_BOTH
+            //will ensure that the LEs for both channels are enabled
+            _lo1_iface[size_t(CH1)]->commit();
+            _lo1_freq[size_t(CH1)].mark_clean();
+            _lo1_freq[size_t(CH2)].mark_clean();
+            _lo1_enable[size_t(CH1)].mark_clean();
+            _lo1_enable[size_t(CH2)].mark_clean();
+            _config_lo1_route(LO_CONFIG_NONE);
+        } else {
+            if (_lo1_freq[size_t(CH1)].is_dirty() || _lo1_enable[size_t(CH1)].is_dirty()) {
+                _config_lo1_route(LO_CONFIG_CH1);
+                _lo1_iface[size_t(CH1)]->commit();
+                _lo1_freq[size_t(CH1)].mark_clean();
+                _lo1_enable[size_t(CH1)].mark_clean();
+                _config_lo1_route(LO_CONFIG_NONE);
+            }
+            if (_lo1_freq[size_t(CH2)].is_dirty() || _lo1_enable[size_t(CH2)].is_dirty()) {
+                _config_lo1_route(LO_CONFIG_CH2);
+                _lo1_iface[size_t(CH2)]->commit();
+                _lo1_freq[size_t(CH2)].mark_clean();
+                _lo1_enable[size_t(CH2)].mark_clean();
+                _config_lo1_route(LO_CONFIG_NONE);
+            }
+        }
+
+        //Commit LO2 frequency
+        bool simultaneous_commit_lo2 = _lo2_freq[size_t(CH1)].is_dirty() and
+                                       _lo2_freq[size_t(CH2)].is_dirty() and
+                                       _lo2_freq[size_t(CH1)].get() == _lo2_freq[size_t(CH2)].get() and
+                                       _lo2_enable[size_t(CH1)].get() == _lo2_enable[size_t(CH2)].get();
+
+        if (simultaneous_commit_lo2) {
+            _config_lo2_route(LO_CONFIG_BOTH);
+            //Only commit one of the channels. The route LO_CONFIG_BOTH
+            //will ensure that the LEs for both channels are enabled
+            _lo2_iface[size_t(CH1)]->commit();
+            _lo2_freq[size_t(CH1)].mark_clean();
+            _lo2_freq[size_t(CH2)].mark_clean();
+            _lo2_enable[size_t(CH1)].mark_clean();
+            _lo2_enable[size_t(CH2)].mark_clean();
+            _config_lo2_route(LO_CONFIG_NONE);
+        } else {
+            if (_lo2_freq[size_t(CH1)].is_dirty() || _lo2_enable[size_t(CH1)].is_dirty()) {
+                _config_lo2_route(LO_CONFIG_CH1);
+                _lo2_iface[size_t(CH1)]->commit();
+                _lo2_freq[size_t(CH1)].mark_clean();
+                _lo2_enable[size_t(CH1)].mark_clean();
+                _config_lo2_route(LO_CONFIG_NONE);
+            }
+            if (_lo2_freq[size_t(CH2)].is_dirty() || _lo2_enable[size_t(CH2)].is_dirty()) {
+                _config_lo2_route(LO_CONFIG_CH2);
+                _lo2_iface[size_t(CH2)]->commit();
+                _lo2_freq[size_t(CH2)].mark_clean();
+                _lo2_enable[size_t(CH2)].mark_clean();
+                _config_lo2_route(LO_CONFIG_NONE);
+            }
+        }
+    }
+
+private:    //Members
+    static const size_t NUM_CHANS = 2;
+
+    struct tune_freq_t : public uhd::math::fp_compare::fp_compare_delta<double> {
+        tune_freq_t() : uhd::math::fp_compare::fp_compare_delta<double>(
+            0.0, uhd::math::FREQ_COMPARISON_DELTA_HZ) {}
+
+        tune_freq_t(double freq) : uhd::math::fp_compare::fp_compare_delta<double>(
+            freq, uhd::math::FREQ_COMPARISON_DELTA_HZ) {}
+    };
+
+    boost::mutex                _mutex;
+    dboard_iface::sptr          _db_iface;
+    twinrx_gpio::sptr           _gpio_iface;
+    twinrx_cpld_regmap::sptr    _cpld_regs;
+    adf5355_iface::sptr         _lo1_iface[NUM_CHANS];
+    adf435x_iface::sptr         _lo2_iface[NUM_CHANS];
+    lo_source_t                 _lo1_src[NUM_CHANS];
+    lo_source_t                 _lo2_src[NUM_CHANS];
+    dirty_tracked<tune_freq_t>  _lo1_freq[NUM_CHANS];
+    dirty_tracked<tune_freq_t>  _lo2_freq[NUM_CHANS];
+    dirty_tracked<bool>         _lo1_enable[NUM_CHANS];
+    dirty_tracked<bool>         _lo2_enable[NUM_CHANS];
+    lo_export_source_t          _lo1_export;
+    lo_export_source_t          _lo2_export;
+};
+
+twinrx_ctrl::sptr twinrx_ctrl::make(
+    dboard_iface::sptr db_iface,
+    twinrx_gpio::sptr gpio_iface,
+    twinrx_cpld_regmap::sptr cpld_regmap
+) {
+    return sptr(new twinrx_ctrl_impl(db_iface, gpio_iface, cpld_regmap));
+}
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp
new file mode 100644
index 000000000..521e27ae9
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp
@@ -0,0 +1,101 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_DBOARD_TWINRX_CTRL_HPP
+#define INCLUDED_DBOARD_TWINRX_CTRL_HPP
+
+#include <boost/noncopyable.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include "twinrx_io.hpp"
+
+namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
+
+class twinrx_ctrl : public boost::noncopyable {
+public:
+    typedef boost::shared_ptr<twinrx_ctrl> sptr;
+
+    static sptr make(
+        dboard_iface::sptr db_iface,
+        twinrx_gpio::sptr gpio_iface,
+        twinrx_cpld_regmap::sptr cpld_regmap);
+
+    virtual ~twinrx_ctrl() {}
+
+    enum channel_t { CH1 = 0, CH2 = 1, BOTH = 2};
+
+    enum preamp_state_t { PREAMP_LOWBAND, PREAMP_HIGHBAND, PREAMP_BYPASS };
+
+    enum signal_path_t { PATH_LOWBAND, PATH_HIGHBAND };
+
+    enum preselector_path_t { PRESEL_PATH1, PRESEL_PATH2, PRESEL_PATH3, PRESEL_PATH4 };
+
+    enum lo_source_t { LO_INTERNAL, LO_EXTERNAL, LO_COMPANION, LO_DISABLED };
+
+    enum lo_export_source_t { LO_CH1_SYNTH, LO_CH2_SYNTH, LO_EXPORT_DISABLED };
+
+    enum antenna_mapping_t { ANTX_NATIVE, ANT1_SHARED, ANT2_SHARED, ANTX_SWAPPED, ANTX_DISABLED };
+
+    enum lo_config_route_t { LO_CONFIG_CH1, LO_CONFIG_CH2, LO_CONFIG_BOTH, LO_CONFIG_NONE };
+
+    enum cal_mode_t { CAL_DISABLED, CAL_CH1, CAL_CH2 };
+
+    virtual void commit() = 0;
+
+    virtual void set_chan_enabled(channel_t ch, bool enabled, bool commit = true) = 0;
+
+    virtual void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true) = 0;
+
+    virtual void set_preamp2(channel_t ch, bool enabled, bool commit = true) = 0;
+
+    virtual void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true) = 0;
+
+    virtual void set_signal_path(channel_t ch, signal_path_t path, bool commit = true) = 0;
+
+    virtual void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true) = 0;
+
+    virtual void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true) = 0;
+
+    virtual void set_input_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0;
+
+    virtual void set_lb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0;
+
+    virtual void set_hb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0;
+
+    virtual void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true) = 0;
+
+    virtual void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true) = 0;
+
+    virtual void set_lo1_export_source(lo_export_source_t source, bool commit = true) = 0;
+
+    virtual void set_lo2_export_source(lo_export_source_t source, bool commit = true) = 0;
+
+    virtual void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true) = 0;
+
+    virtual void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true) = 0;
+
+    virtual double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true) = 0;
+
+    virtual double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true) = 0;
+
+    virtual bool read_lo1_locked(channel_t ch) = 0;
+
+    virtual bool read_lo2_locked(channel_t ch) = 0;
+};
+
+}}}} //namespaces
+
+#endif /* INCLUDED_DBOARD_TWINRX_CTRL_HPP */
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp b/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp
new file mode 100644
index 000000000..3d04098e9
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp
@@ -0,0 +1,637 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "twinrx_experts.hpp"
+#include "twinrx_gain_tables.hpp"
+#include <uhd/utils/math.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/dict.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/math/special_functions/round.hpp>
+
+using namespace uhd::experts;
+using namespace uhd::math;
+using namespace uhd::usrp::dboard::twinrx;
+
+/*!---------------------------------------------------------
+ * twinrx_freq_path_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_freq_path_expert::resolve()
+{
+    //Lowband/highband switch point
+    static const double LB_HB_THRESHOLD_FREQ    = 1.8e9;
+    static const double LB_TARGET_IF1_FREQ      = 2.345e9;
+    static const double HB_TARGET_IF1_FREQ      = 1.25e9;
+    static const double INJ_SIDE_THRESHOLD_FREQ = 5.1e9;
+
+    static const double FIXED_LO1_THRESHOLD_FREQ= 50e6;
+
+    //Preselector filter switch point
+    static const double LB_FILT1_THRESHOLD_FREQ = 0.5e9;
+    static const double LB_FILT2_THRESHOLD_FREQ = 0.8e9;
+    static const double LB_FILT3_THRESHOLD_FREQ = 1.2e9;
+    static const double LB_FILT4_THRESHOLD_FREQ = 1.8e9;
+    static const double HB_FILT1_THRESHOLD_FREQ = 3.0e9;
+    static const double HB_FILT2_THRESHOLD_FREQ = 4.1e9;
+    static const double HB_FILT3_THRESHOLD_FREQ = 5.1e9;
+    static const double HB_FILT4_THRESHOLD_FREQ = 6.0e9;
+
+    static const double LB_PREAMP_PRESEL_THRESHOLD_FREQ = 0.8e9;
+
+    //Misc
+    static const double INST_BANDWIDTH           = 80e6;
+    static const double MANUAL_LO_HYSTERESIS_PPM = 1.0;
+
+    static const freq_range_t FREQ_RANGE(10e6, 6e9);
+    rf_freq_abs_t rf_freq(FREQ_RANGE.clip(_rf_freq_d));
+
+    // Choose low-band vs high-band depending on frequency
+    _signal_path = (rf_freq > LB_HB_THRESHOLD_FREQ) ?
+        twinrx_ctrl::PATH_HIGHBAND : twinrx_ctrl::PATH_LOWBAND;
+    if (_signal_path == twinrx_ctrl::PATH_LOWBAND) {
+        // Choose low-band preselector filter
+        if (rf_freq < LB_FILT1_THRESHOLD_FREQ) {
+            _lb_presel = twinrx_ctrl::PRESEL_PATH1;
+        } else if (rf_freq < LB_FILT2_THRESHOLD_FREQ) {
+            _lb_presel = twinrx_ctrl::PRESEL_PATH2;
+        } else if (rf_freq < LB_FILT3_THRESHOLD_FREQ) {
+            _lb_presel = twinrx_ctrl::PRESEL_PATH3;
+        } else if (rf_freq < LB_FILT4_THRESHOLD_FREQ) {
+            _lb_presel = twinrx_ctrl::PRESEL_PATH4;
+        } else {
+            _lb_presel = twinrx_ctrl::PRESEL_PATH4;
+        }
+    } else if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) {
+        // Choose high-band preselector filter
+        if (rf_freq < HB_FILT1_THRESHOLD_FREQ) {
+            _hb_presel = twinrx_ctrl::PRESEL_PATH1;
+        } else if (rf_freq < HB_FILT2_THRESHOLD_FREQ) {
+            _hb_presel = twinrx_ctrl::PRESEL_PATH2;
+        } else if (rf_freq < HB_FILT3_THRESHOLD_FREQ) {
+            _hb_presel = twinrx_ctrl::PRESEL_PATH3;
+        } else if (rf_freq < HB_FILT4_THRESHOLD_FREQ) {
+            _hb_presel = twinrx_ctrl::PRESEL_PATH4;
+        } else {
+            _hb_presel = twinrx_ctrl::PRESEL_PATH4;
+        }
+    } else {
+        UHD_THROW_INVALID_CODE_PATH();
+    }
+
+    //Choose low-band preamp preselector
+    _lb_preamp_presel = (rf_freq > LB_PREAMP_PRESEL_THRESHOLD_FREQ);
+
+    //Choose LO frequencies
+    const double target_if1_freq = (_signal_path == twinrx_ctrl::PATH_HIGHBAND) ?
+        HB_TARGET_IF1_FREQ : LB_TARGET_IF1_FREQ;
+    const double target_if2_freq = _if_freq_d;
+
+    // LO1
+    double lo1_freq_ideal = 0.0, lo2_freq_ideal = 0.0;
+    if (rf_freq <= FIXED_LO1_THRESHOLD_FREQ) {
+        //LO1 Freq static
+        lo1_freq_ideal = target_if1_freq + FIXED_LO1_THRESHOLD_FREQ;
+    } else if (rf_freq <= INJ_SIDE_THRESHOLD_FREQ) {
+        //High-side LO1 Injection
+        lo1_freq_ideal = rf_freq.get() + target_if1_freq;
+    } else {
+        //Low-side LO1 Injection
+        lo1_freq_ideal = rf_freq.get() - target_if1_freq;
+    }
+
+    if (_lo1_freq_d.get_author() == experts::AUTHOR_USER) {
+        if (_lo1_freq_d.is_dirty()) { //Are we here because the LO frequency was set?
+            // The user explicitly requested to set the LO freq so don't touch it!
+        } else {
+            // Something else changed which may cause the LO frequency to update.
+            // Only commit if the frequency is stale. If the user's value is stale
+            // reset the author to expert.
+            if (rf_freq_ppm_t(lo1_freq_ideal, MANUAL_LO_HYSTERESIS_PPM) != _lo1_freq_d.get()) {
+                _lo1_freq_d = lo1_freq_ideal;    //Reset author
+            }
+        }
+    } else {
+        // The LO frequency was never set by the user. Let the expert take care of it
+        _lo1_freq_d = lo1_freq_ideal;    //Reset author
+    }
+
+    // LO2
+    lo_inj_side_t lo2_inj_side_ideal = _compute_lo2_inj_side(
+        lo1_freq_ideal, target_if1_freq, target_if2_freq, INST_BANDWIDTH);
+    if (lo2_inj_side_ideal == INJ_HIGH_SIDE) {
+        lo2_freq_ideal = target_if1_freq + target_if2_freq;
+    } else {
+        lo2_freq_ideal = target_if1_freq - target_if2_freq;
+    }
+
+    if (_lo2_freq_d.get_author() == experts::AUTHOR_USER) {
+        if (_lo2_freq_d.is_dirty()) { //Are we here because the LO frequency was set?
+            // The user explicitly requested to set the LO freq so don't touch it!
+        } else {
+            // Something else changed which may cause the LO frequency to update.
+            // Only commit if the frequency is stale. If the user's value is stale
+            // reset the author to expert.
+            if (rf_freq_ppm_t(lo2_freq_ideal, MANUAL_LO_HYSTERESIS_PPM) != _lo2_freq_d.get()) {
+                _lo2_freq_d = lo2_freq_ideal;    //Reset author
+            }
+        }
+    } else {
+        // The LO frequency was never set by the user. Let the expert take care of it
+        _lo2_freq_d = lo2_freq_ideal;    //Reset author
+    }
+
+    // Determine injection side using the final LO frequency
+    _lo1_inj_side = (_lo1_freq_d > rf_freq.get())   ? INJ_HIGH_SIDE : INJ_LOW_SIDE;
+    _lo2_inj_side = (_lo2_freq_d > target_if1_freq) ? INJ_HIGH_SIDE : INJ_LOW_SIDE;
+}
+
+lo_inj_side_t twinrx_freq_path_expert::_compute_lo2_inj_side(
+    double lo1_freq, double if1_freq,  double if2_freq, double bandwidth
+) {
+    static const int MAX_SPUR_ORDER = 5;
+    for (int ord = MAX_SPUR_ORDER; ord >= 1; ord--) {
+        // Check high-side injection first
+        if (not _has_mixer_spurs(lo1_freq, if1_freq + if2_freq, if2_freq, bandwidth, ord)) {
+            return INJ_HIGH_SIDE;
+        }
+        // Check low-side injection second
+        if (not _has_mixer_spurs(lo1_freq, if1_freq - if2_freq, if2_freq, bandwidth, ord)) {
+            return INJ_LOW_SIDE;
+        }
+    }
+    // If we reached here, then there are spurs everywhere. Pick high-side as the default
+    return INJ_HIGH_SIDE;
+}
+
+bool twinrx_freq_path_expert::_has_mixer_spurs(
+    double lo1_freq, double lo2_freq, double if2_freq,
+    double bandwidth, int spur_order
+) {
+    // Iterate through all N-th order harmomic combinations
+    // of LOs...
+    for (int lo1h_i = 1; lo1h_i <= spur_order; lo1h_i++) {
+        double lo1harm_freq = lo1_freq * lo1h_i;
+        for (int lo2h_i = 1; lo2h_i <= spur_order; lo2h_i++) {
+            double lo2harm_freq = lo2_freq * lo2h_i;
+            double hdelta = lo1harm_freq - lo2harm_freq;
+            // .. and check if there is a mixer spur in the IF band
+            if (std::abs(hdelta + if2_freq) < bandwidth/2 or
+                std::abs(hdelta - if2_freq) < bandwidth/2) {
+                return true;
+            }
+        }
+    }
+    // No spurs were found after NxN search
+    return false;
+}
+
+/*!---------------------------------------------------------
+ * twinrx_freq_coercion_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_freq_coercion_expert::resolve()
+{
+    const double actual_if2_freq = _if_freq_d;
+    const double actual_if1_freq = (_lo2_inj_side == INJ_LOW_SIDE) ?
+        (_lo2_freq_c + actual_if2_freq) : (_lo2_freq_c - actual_if2_freq);
+
+    _rf_freq_c = (_lo1_inj_side == INJ_LOW_SIDE) ?
+        (_lo1_freq_c + actual_if1_freq) : (_lo1_freq_c - actual_if1_freq);
+}
+
+/*!---------------------------------------------------------
+ * twinrx_nyquist_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_nyquist_expert::resolve()
+{
+    double if_freq_sign = 1.0;
+    if (_lo1_inj_side == INJ_HIGH_SIDE) if_freq_sign *= -1.0;
+    if (_lo2_inj_side == INJ_HIGH_SIDE) if_freq_sign *= -1.0;
+    _if_freq_c = _if_freq_d * if_freq_sign;
+
+    _db_iface->set_fe_connection(dboard_iface::UNIT_RX, _channel,
+        usrp::fe_connection_t(_codec_conn, _if_freq_c));
+}
+
+/*!---------------------------------------------------------
+ * twinrx_chan_gain_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_chan_gain_expert::resolve()
+{
+    if (_gain_profile != "default") {
+        //TODO: Implement me!
+        throw uhd::not_implemented_error("custom gain strategies not implemeted yet");
+    }
+
+    //Lookup table using settings
+    const twinrx_gain_table table = twinrx_gain_table::lookup_table(
+        _signal_path,
+        (_signal_path==twinrx_ctrl::PATH_HIGHBAND) ? _hb_presel : _lb_presel,
+        _gain_profile);
+
+    //Compute minimum gain. The user-specified gain value will be interpreted as
+    //the gain applied on top of the minimum gain state.
+    //If antennas are shared or swapped, the switch has 6dB of loss
+    size_t gain_index = std::min(static_cast<size_t>(boost::math::round(_gain.get())), table.get_num_entries()-1);
+
+    //Translate gain to an index in the gain table
+    const twinrx_gain_config_t& config = table.find_by_index(gain_index);
+
+    _input_atten = config.atten1;
+    if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) {
+        _hb_atten = config.atten2;
+    } else {
+        _lb_atten = config.atten2;
+    }
+
+    // Preamp 1 should use the Highband amp for frequencies above 3 GHz
+    if (_signal_path == twinrx_ctrl::PATH_HIGHBAND && _hb_presel != twinrx_ctrl::PRESEL_PATH1) {
+        _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_HIGHBAND : twinrx_ctrl::PREAMP_BYPASS;
+    } else {
+        _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_LOWBAND : twinrx_ctrl::PREAMP_BYPASS;
+    }
+
+    _preamp2 = config.amp2;
+}
+
+/*!---------------------------------------------------------
+ * twinrx_lo_config_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_lo_config_expert::resolve()
+{
+    static const uhd::dict<std::string, twinrx_ctrl::lo_source_t> src_lookup =
+        boost::assign::map_list_of
+        ("internal",  twinrx_ctrl::LO_INTERNAL)
+        ("external",  twinrx_ctrl::LO_EXTERNAL)
+        ("companion", twinrx_ctrl::LO_COMPANION)
+        ("disabled",  twinrx_ctrl::LO_DISABLED);
+
+    if (src_lookup.has_key(_lo_source_ch0)) {
+        _lo1_src_ch0 = _lo2_src_ch0 = src_lookup[_lo_source_ch0];
+    } else {
+        throw uhd::value_error("Invalid LO source for channel 0.Choose from {internal, external, companion}");
+    }
+    if (src_lookup.has_key(_lo_source_ch1)) {
+        _lo1_src_ch1 = _lo2_src_ch1 = src_lookup[_lo_source_ch1];
+    } else {
+        throw uhd::value_error("Invalid LO source for channel 1.Choose from {internal, external, companion}");
+    }
+
+    twinrx_ctrl::lo_export_source_t export_src = twinrx_ctrl::LO_EXPORT_DISABLED;
+    if (_lo_export_ch0 and (_lo_source_ch0 == "external")) {
+        throw uhd::value_error("Cannot export an external LO for channel 0");
+    }
+    if (_lo_export_ch1 and (_lo_source_ch1 == "external")) {
+        throw uhd::value_error("Cannot export an external LO for channel 1");
+    }
+    if (_lo_export_ch0 and _lo_export_ch1) {
+        throw uhd::value_error("Cannot export LOs for both channels");
+    } else if (_lo_export_ch0) {
+        export_src = (_lo1_src_ch0 == twinrx_ctrl::LO_INTERNAL) ?
+            twinrx_ctrl::LO_CH1_SYNTH : twinrx_ctrl::LO_CH2_SYNTH;
+    } else if (_lo_export_ch1) {
+        export_src = (_lo1_src_ch1 == twinrx_ctrl::LO_INTERNAL) ?
+            twinrx_ctrl::LO_CH2_SYNTH : twinrx_ctrl::LO_CH1_SYNTH;
+    }
+    _lo1_export_src = _lo2_export_src = export_src;
+}
+
+/*!---------------------------------------------------------
+ * twinrx_lo_freq_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_lo_mapping_expert::resolve()
+{
+    static const size_t CH0_MSK = 0x1;
+    static const size_t CH1_MSK = 0x2;
+
+    // Determine which channels are "driving" each synthesizer
+    // First check for explicit requests i.e. lo_source "internal" or "companion"
+    size_t synth_map[] = {0, 0};
+    if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL) {
+        synth_map[0] = synth_map[0] | CH0_MSK;
+    } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) {
+        synth_map[1] = synth_map[1] | CH0_MSK;
+    }
+    if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL) {
+        synth_map[1] = synth_map[1] | CH1_MSK;
+    } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) {
+        synth_map[0] = synth_map[0] | CH1_MSK;
+    }
+
+    // If a particular channel has its LO source disabled then the other
+    // channel is automatically put in hop mode i.e. the synthesizer that
+    // belongs to the disabled channel can be re-purposed as a redundant LO
+    // to overlap tuning with signal dwell time.
+    bool hopping_enabled = false;
+    if (_lox_src_ch0 == twinrx_ctrl::LO_DISABLED) {
+        if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL) {
+            synth_map[0] = synth_map[0] | CH0_MSK;
+            hopping_enabled = true;
+        } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) {
+            synth_map[1] = synth_map[1] | CH0_MSK;
+            hopping_enabled = true;
+        }
+    }
+    if (_lox_src_ch1 == twinrx_ctrl::LO_DISABLED) {
+        if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL) {
+            synth_map[1] = synth_map[1] | CH1_MSK;
+            hopping_enabled = true;
+        } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) {
+            synth_map[0] = synth_map[0] | CH1_MSK;
+            hopping_enabled = true;
+        }
+    }
+
+    // For each synthesizer come up with the final mapping
+    for (size_t synth = 0; synth < 2; synth++) {
+        experts::data_writer_t<lo_synth_mapping_t>& lox_mapping =
+            (synth == 0) ? _lox_mapping_synth0 : _lox_mapping_synth1;
+        if (synth_map[synth] == (CH0_MSK|CH1_MSK)) {
+            lox_mapping = MAPPING_SHARED;
+        } else if (synth_map[synth] == CH0_MSK) {
+            lox_mapping = MAPPING_CH0;
+        } else if (synth_map[synth] == CH1_MSK) {
+            lox_mapping = MAPPING_CH1;
+        } else {
+            lox_mapping = MAPPING_NONE;
+        }
+    }
+    _lox_hopping_enabled = hopping_enabled;
+}
+
+/*!---------------------------------------------------------
+ * twinrx_antenna_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_antenna_expert::resolve()
+{
+    static const std::string ANT0 = "RX1", ANT1 = "RX2";
+
+    if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT1) {
+        _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
+    } else if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT0) {
+        if (_enabled_ch0 and _enabled_ch1) {
+            _ant_mapping = twinrx_ctrl::ANT1_SHARED;
+        } else if (_enabled_ch0) {
+            _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
+        } else if (_enabled_ch1) {
+            _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
+        }
+    } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT1) {
+        if (_enabled_ch0 and _enabled_ch1) {
+            _ant_mapping = twinrx_ctrl::ANT2_SHARED;
+        } else if (_enabled_ch0) {
+            _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
+        } else if (_enabled_ch1) {
+            _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
+        }
+    } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT0) {
+        _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
+    } else if (_antenna_ch0 != ANT0 and _antenna_ch0 != ANT1) {
+        throw uhd::value_error("Invalid antenna selection " + _antenna_ch0.get() + " for channel 0. Must be " + ANT0 + " or " + ANT1);
+    } else if (_antenna_ch1 != ANT0 and _antenna_ch1 != ANT1) {
+        throw uhd::value_error("Invalid antenna selection " + _antenna_ch1.get() + " for channel 1. Must be " + ANT0 + " or " + ANT1);
+    }
+
+    //TODO: Implement hooks for the calibration switch
+    _cal_mode = twinrx_ctrl::CAL_DISABLED;
+
+    if (_cal_mode == twinrx_ctrl::CAL_CH1 and _lo_export_ch1) {
+        throw uhd::value_error("Cannot calibrate channel 0 and export the LO for channel 1.");
+    } else if (_cal_mode == twinrx_ctrl::CAL_CH2 and _lo_export_ch0) {
+        throw uhd::value_error("Cannot calibrate channel 1 and export the LO for channel 0.");
+    }
+}
+
+/*!---------------------------------------------------------
+ * twinrx_ant_gain_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_ant_gain_expert::resolve()
+{
+    switch (_ant_mapping) {
+    case twinrx_ctrl::ANTX_NATIVE:
+        _ant0_input_atten       = _ch0_input_atten;
+        _ant0_preamp1           = _ch0_preamp1;
+        _ant0_preamp2           = _ch0_preamp2;
+        _ant0_lb_preamp_presel  = _ch0_lb_preamp_presel;
+        _ant1_input_atten       = _ch1_input_atten;
+        _ant1_preamp1           = _ch1_preamp1;
+        _ant1_preamp2           = _ch1_preamp2;
+        _ant1_lb_preamp_presel  = _ch1_lb_preamp_presel;
+        break;
+    case twinrx_ctrl::ANTX_SWAPPED:
+        _ant0_input_atten       = _ch1_input_atten;
+        _ant0_preamp1           = _ch1_preamp1;
+        _ant0_preamp2           = _ch1_preamp2;
+        _ant0_lb_preamp_presel  = _ch1_lb_preamp_presel;
+        _ant1_input_atten       = _ch0_input_atten;
+        _ant1_preamp1           = _ch0_preamp1;
+        _ant1_preamp2           = _ch0_preamp2;
+        _ant1_lb_preamp_presel  = _ch0_lb_preamp_presel;
+        break;
+    case twinrx_ctrl::ANT1_SHARED:
+        if ((_ch0_input_atten != _ch1_input_atten) or
+            (_ch0_preamp1 != _ch1_preamp1) or
+            (_ch0_preamp2 != _ch1_preamp2) or
+            (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel))
+        {
+            UHD_MSG(warning) << "incompatible gain settings for antenna sharing. temporarily using Ch0 settings for Ch1.";
+        }
+        _ant0_input_atten       = _ch0_input_atten;
+        _ant0_preamp1           = _ch0_preamp1;
+        _ant0_preamp2           = _ch0_preamp2;
+        _ant0_lb_preamp_presel  = _ch0_lb_preamp_presel;
+
+        _ant1_input_atten       = 0;
+        _ant1_preamp1           = twinrx_ctrl::PREAMP_BYPASS;
+        _ant1_preamp2           = false;
+        _ant1_lb_preamp_presel  = false;
+        break;
+    case twinrx_ctrl::ANT2_SHARED:
+        if ((_ch0_input_atten != _ch1_input_atten) or
+            (_ch0_preamp1 != _ch1_preamp1) or
+            (_ch0_preamp2 != _ch1_preamp2) or
+            (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel))
+        {
+            UHD_MSG(warning) << "incompatible gain settings for antenna sharing. temporarily using Ch0 settings for Ch1.";
+        }
+        _ant1_input_atten       = _ch0_input_atten;
+        _ant1_preamp1           = _ch0_preamp1;
+        _ant1_preamp2           = _ch0_preamp2;
+        _ant1_lb_preamp_presel  = _ch0_lb_preamp_presel;
+
+        _ant0_input_atten       = 0;
+        _ant0_preamp1           = twinrx_ctrl::PREAMP_BYPASS;
+        _ant0_preamp2           = false;
+        _ant0_lb_preamp_presel  = false;
+        break;
+    default:
+        _ant0_input_atten       = 0;
+        _ant0_preamp1           = twinrx_ctrl::PREAMP_BYPASS;
+        _ant0_preamp2           = false;
+        _ant0_lb_preamp_presel  = false;
+        _ant1_input_atten       = 0;
+        _ant1_preamp1           = twinrx_ctrl::PREAMP_BYPASS;
+        _ant1_preamp2           = false;
+        _ant1_lb_preamp_presel  = false;
+        break;
+    }
+}
+
+/*!---------------------------------------------------------
+ * twinrx_settings_expert::resolve
+ * ---------------------------------------------------------
+ */
+const bool twinrx_settings_expert::FORCE_COMMIT = false;
+
+void twinrx_settings_expert::resolve()
+{
+    for (size_t i = 0; i < 2; i++) {
+        ch_settings& ch_set = (i == 1) ? _ch1 : _ch0;
+        twinrx_ctrl::channel_t ch = (i == 1) ? twinrx_ctrl::CH2 : twinrx_ctrl::CH1;
+        _ctrl->set_chan_enabled(ch, ch_set.chan_enabled, FORCE_COMMIT);
+        _ctrl->set_preamp1(ch, ch_set.preamp1, FORCE_COMMIT);
+        _ctrl->set_preamp2(ch, ch_set.preamp2, FORCE_COMMIT);
+        _ctrl->set_lb_preamp_preselector(ch, ch_set.lb_preamp_presel, FORCE_COMMIT);
+        _ctrl->set_signal_path(ch, ch_set.signal_path, FORCE_COMMIT);
+        _ctrl->set_lb_preselector(ch, ch_set.lb_presel, FORCE_COMMIT);
+        _ctrl->set_hb_preselector(ch, ch_set.hb_presel, FORCE_COMMIT);
+        _ctrl->set_input_atten(ch, ch_set.input_atten, FORCE_COMMIT);
+        _ctrl->set_lb_atten(ch, ch_set.lb_atten, FORCE_COMMIT);
+        _ctrl->set_hb_atten(ch, ch_set.hb_atten, FORCE_COMMIT);
+        _ctrl->set_lo1_source(ch, ch_set.lo1_source, FORCE_COMMIT);
+        _ctrl->set_lo2_source(ch, ch_set.lo2_source, FORCE_COMMIT);
+    }
+
+    _resolve_lox_freq(STAGE_LO1,
+        _ch0.lo1_freq_d, _ch1.lo1_freq_d, _ch0.lo1_freq_c, _ch1.lo1_freq_c,
+        _ch0.lo1_source, _ch1.lo1_source, _lo1_synth0_mapping, _lo1_synth1_mapping,
+        _lo1_hopping_enabled);
+    _resolve_lox_freq(STAGE_LO2,
+        _ch0.lo2_freq_d, _ch1.lo2_freq_d, _ch0.lo2_freq_c, _ch1.lo2_freq_c,
+        _ch0.lo2_source, _ch1.lo2_source, _lo2_synth0_mapping, _lo2_synth1_mapping,
+        _lo2_hopping_enabled);
+
+    _ctrl->set_lo1_export_source(_lo1_export_src, FORCE_COMMIT);
+    _ctrl->set_lo2_export_source(_lo2_export_src, FORCE_COMMIT);
+    _ctrl->set_antenna_mapping(_ant_mapping, FORCE_COMMIT);
+    //TODO: Re-enable this when we support this mode
+    //_ctrl->set_crossover_cal_mode(_cal_mode, FORCE_COMMIT);
+
+    _ctrl->commit();
+}
+
+void twinrx_settings_expert::_resolve_lox_freq(
+    lo_stage_t                           lo_stage,
+    uhd::experts::data_reader_t<double>& ch0_freq_d,
+    uhd::experts::data_reader_t<double>& ch1_freq_d,
+    uhd::experts::data_writer_t<double>& ch0_freq_c,
+    uhd::experts::data_writer_t<double>& ch1_freq_c,
+    twinrx_ctrl::lo_source_t             ch0_lo_source,
+    twinrx_ctrl::lo_source_t             ch1_lo_source,
+    lo_synth_mapping_t                   synth0_mapping,
+    lo_synth_mapping_t                   synth1_mapping,
+    bool                                 hopping_enabled)
+{
+    if (ch0_lo_source == twinrx_ctrl::LO_EXTERNAL) {
+        // If the LO is external then we don't need to program any synthesizers
+        ch0_freq_c = ch0_freq_d;
+    } else {
+        // When in hopping mode, only attempt to write the LO frequency if it is actually
+        // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not
+        // hopping, then always write the frequency because other inputs might require
+        // an LO re-commit
+        const bool freq_update_request = (not hopping_enabled) or ch0_freq_d.is_dirty();
+        if (synth0_mapping == MAPPING_CH0 and freq_update_request) {
+            ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch0_freq_d);
+        } else if (synth1_mapping == MAPPING_CH0 and freq_update_request) {
+            ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch0_freq_d);
+        } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) {
+            // If any synthesizer is being shared then we are not in hopping mode
+            if (rf_freq_ppm_t(ch0_freq_d) != ch1_freq_d) {
+                UHD_MSG(warning) <<
+                    "Incompatible RF/LO frequencies for LO sharing. Using Ch0 settings for both channels.";
+            }
+            twinrx_ctrl::channel_t ch = (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2;
+            ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d);
+            ch1_freq_c = ch0_freq_c;
+        }
+    }
+
+    if (ch1_lo_source == twinrx_ctrl::LO_EXTERNAL) {
+        // If the LO is external then we don't need to program any synthesizers
+        ch1_freq_c = ch1_freq_d;
+    } else {
+        // When in hopping mode, only attempt to write the LO frequency if it is actually
+        // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not
+        // hopping, then always write the frequency because other inputs might require
+        // an LO re-commit
+        const bool freq_update_request = (not hopping_enabled) or ch1_freq_d.is_dirty();
+        // As an additional layer of protection from unnecessarily commiting the LO, check
+        // if the frequency has actually changed.
+        if (synth0_mapping == MAPPING_CH1 and freq_update_request) {
+            ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch1_freq_d);
+        } else if (synth1_mapping == MAPPING_CH1 and freq_update_request) {
+            ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch1_freq_d);
+        } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) {
+            // If any synthesizer is being shared then we are not in hopping mode
+            if (rf_freq_ppm_t(ch0_freq_d) != ch1_freq_d) {
+                UHD_MSG(warning) <<
+                    "Incompatible RF/LO frequencies for LO sharing. Using Ch0 settings for both channels.";
+            }
+            twinrx_ctrl::channel_t ch = (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2;
+            ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d);
+            ch1_freq_c = ch0_freq_c;
+        }
+    }
+}
+
+double twinrx_settings_expert::_set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq)
+{
+    lo_freq_cache_t* freq_cache = NULL;
+    if (stage == STAGE_LO1) {
+        freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo1_synth0_freq : &_cached_lo1_synth1_freq;
+    } else if (stage == STAGE_LO2) {
+        freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo2_synth0_freq : &_cached_lo2_synth1_freq;
+    } else {
+        throw uhd::assertion_error("Invalid LO stage");
+    }
+
+    // Check if the frequency has actually changed before configuring synthesizers
+    double coerced_freq = 0.0;
+    if (freq_cache->desired != freq) {
+        if (stage == STAGE_LO1) {
+            coerced_freq = _ctrl->set_lo1_synth_freq(ch, freq, FORCE_COMMIT);
+        } else {
+            coerced_freq = _ctrl->set_lo2_synth_freq(ch, freq, FORCE_COMMIT);
+        }
+        freq_cache->desired = rf_freq_ppm_t(freq);
+        freq_cache->coerced = coerced_freq;
+    } else {
+        coerced_freq = freq_cache->coerced;
+    }
+    return coerced_freq;
+}
+
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp b/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp
new file mode 100644
index 000000000..f2601a09b
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp
@@ -0,0 +1,630 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_DBOARD_TWINRX_EXPERTS_HPP
+#define INCLUDED_DBOARD_TWINRX_EXPERTS_HPP
+
+#include "twinrx_ctrl.hpp"
+#include "expert_nodes.hpp"
+#include <uhd/utils/math.hpp>
+
+namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
+
+//---------------------------------------------------------
+// Misc types and definitions
+//---------------------------------------------------------
+
+struct rf_freq_abs_t : public uhd::math::fp_compare::fp_compare_delta<double> {
+    rf_freq_abs_t(double freq = 0.0, double epsilon = 1.0 /* 1Hz epsilon */) :
+        uhd::math::fp_compare::fp_compare_delta<double>(freq, epsilon) {}
+    inline double get() const { return _value; }
+};
+
+struct rf_freq_ppm_t : public rf_freq_abs_t {
+    rf_freq_ppm_t(double freq = 0.0, double epsilon_ppm = 0.1  /* 1PPM epsilon */) :
+        rf_freq_abs_t(freq, 1e-6 * freq * epsilon_ppm) {}
+};
+
+enum lo_stage_t { STAGE_LO1, STAGE_LO2 };
+enum lo_inj_side_t { INJ_LOW_SIDE, INJ_HIGH_SIDE };
+enum lo_synth_mapping_t { MAPPING_NONE, MAPPING_CH0, MAPPING_CH1, MAPPING_SHARED };
+
+static const std::string prepend_ch(std::string name, const std::string& ch) {
+    return ch + "/" + name;
+}
+
+static const std::string lo_stage_str(lo_stage_t stage, bool lower = false) {
+    std::string prefix = lower ? "lo" : "LO";
+    return prefix + ((stage == STAGE_LO1) ? "1" : "2");
+}
+
+/*!---------------------------------------------------------
+ * twinrx_freq_path_expert
+ *
+ * This expert is responsble for translating a user-specified
+ * RF and IF center frequency into TwinRX specific settings
+ * like band, preselector path, LO frequency and injection
+ * sides for both the LO stages.
+ *
+ * One instance of this expert is required for each channel
+ * ---------------------------------------------------------
+ */
+class twinrx_freq_path_expert : public experts::worker_node_t {
+public:
+    twinrx_freq_path_expert(const experts::node_retriever_t& db, std::string ch)
+    : experts::worker_node_t(prepend_ch("twinrx_freq_path_expert", ch)),
+      _rf_freq_d        (db, prepend_ch("freq/desired", ch)),
+      _if_freq_d        (db, prepend_ch("if_freq/desired", ch)),
+      _signal_path      (db, prepend_ch("ch/signal_path", ch)),
+      _lb_presel        (db, prepend_ch("ch/lb_presel", ch)),
+      _hb_presel        (db, prepend_ch("ch/hb_presel", ch)),
+      _lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", ch)),
+      _lo1_freq_d       (db, prepend_ch("los/LO1/freq/desired", ch)),
+      _lo2_freq_d       (db, prepend_ch("los/LO2/freq/desired", ch)),
+      _lo1_inj_side     (db, prepend_ch("ch/LO1/inj_side", ch)),
+      _lo2_inj_side     (db, prepend_ch("ch/LO2/inj_side", ch))
+    {
+        bind_accessor(_rf_freq_d);
+        bind_accessor(_if_freq_d);
+        bind_accessor(_signal_path);
+        bind_accessor(_lb_presel);
+        bind_accessor(_hb_presel);
+        bind_accessor(_lb_preamp_presel);
+        bind_accessor(_lo1_freq_d);
+        bind_accessor(_lo2_freq_d);
+        bind_accessor(_lo1_inj_side);
+        bind_accessor(_lo2_inj_side);
+    }
+
+private:
+    virtual void resolve();
+    static lo_inj_side_t _compute_lo2_inj_side(
+        double lo1_freq, double if1_freq,  double if2_freq, double bandwidth);
+    static bool _has_mixer_spurs(
+        double lo1_freq, double lo2_freq, double if2_freq,
+        double bandwidth, int spur_order);
+
+    //Inputs
+    experts::data_reader_t<double>                          _rf_freq_d;
+    experts::data_reader_t<double>                          _if_freq_d;
+    //Outputs
+    experts::data_writer_t<twinrx_ctrl::signal_path_t>      _signal_path;
+    experts::data_writer_t<twinrx_ctrl::preselector_path_t> _lb_presel;
+    experts::data_writer_t<twinrx_ctrl::preselector_path_t> _hb_presel;
+    experts::data_writer_t<bool>                            _lb_preamp_presel;
+    experts::data_writer_t<double>                          _lo1_freq_d;
+    experts::data_writer_t<double>                          _lo2_freq_d;
+    experts::data_writer_t<lo_inj_side_t>                   _lo1_inj_side;
+    experts::data_writer_t<lo_inj_side_t>                   _lo2_inj_side;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_lo_config_expert
+ *
+ * This expert is responsible for translating high level
+ * channel-scoped  LO source and export settings to low-level
+ * channel-scoped settings. The expert only deals with
+ * the source and export attributes, not frequency.
+ *
+ * One instance of this expert is required for all channels
+ * ---------------------------------------------------------
+ */
+class twinrx_lo_config_expert : public experts::worker_node_t {
+public:
+    twinrx_lo_config_expert(const experts::node_retriever_t& db)
+    : experts::worker_node_t("twinrx_lo_config_expert"),
+      _lo_source_ch0    (db, prepend_ch("los/all/source", "0")),
+      _lo_source_ch1    (db, prepend_ch("los/all/source", "1")),
+      _lo_export_ch0    (db, prepend_ch("los/all/export", "0")),
+      _lo_export_ch1    (db, prepend_ch("los/all/export", "1")),
+      _lo1_src_ch0      (db, prepend_ch("ch/LO1/source", "0")),
+      _lo1_src_ch1      (db, prepend_ch("ch/LO1/source", "1")),
+      _lo2_src_ch0      (db, prepend_ch("ch/LO2/source", "0")),
+      _lo2_src_ch1      (db, prepend_ch("ch/LO2/source", "1")),
+      _lo1_export_src   (db, "com/LO1/export_source"),
+      _lo2_export_src   (db, "com/LO2/export_source")
+    {
+        bind_accessor(_lo_source_ch0);
+        bind_accessor(_lo_source_ch1);
+        bind_accessor(_lo_export_ch0);
+        bind_accessor(_lo_export_ch1);
+        bind_accessor(_lo1_src_ch0);
+        bind_accessor(_lo1_src_ch1);
+        bind_accessor(_lo2_src_ch0);
+        bind_accessor(_lo2_src_ch1);
+        bind_accessor(_lo1_export_src);
+        bind_accessor(_lo2_export_src);
+    }
+
+private:
+    virtual void resolve();
+
+    //Inputs
+    experts::data_reader_t<std::string>                     _lo_source_ch0;
+    experts::data_reader_t<std::string>                     _lo_source_ch1;
+    experts::data_reader_t<bool>                            _lo_export_ch0;
+    experts::data_reader_t<bool>                            _lo_export_ch1;
+    //Outputs
+    experts::data_writer_t<twinrx_ctrl::lo_source_t>        _lo1_src_ch0;
+    experts::data_writer_t<twinrx_ctrl::lo_source_t>        _lo1_src_ch1;
+    experts::data_writer_t<twinrx_ctrl::lo_source_t>        _lo2_src_ch0;
+    experts::data_writer_t<twinrx_ctrl::lo_source_t>        _lo2_src_ch1;
+    experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src;
+    experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_lo_mapping_expert
+ *
+ * This expert is responsible for translating low-level
+ * channel-scoped  LO source and export settings to low-level
+ * synthesizer-scoped settings. The expert deals with the
+ * extremely flexible channel->synthesizer mapping and handles
+ * frequency hopping modes.
+ *
+ * One instance of this expert is required for each LO stage
+ * ---------------------------------------------------------
+ */
+class twinrx_lo_mapping_expert : public experts::worker_node_t {
+public:
+    twinrx_lo_mapping_expert(const experts::node_retriever_t& db, lo_stage_t stage)
+    : experts::worker_node_t("twinrx_" + lo_stage_str(stage, true) + "_mapping_expert"),
+      _lox_src_ch0          (db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "0")),
+      _lox_src_ch1          (db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "1")),
+      _lox_mapping_synth0   (db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "0")),
+      _lox_mapping_synth1   (db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "1")),
+      _lox_hopping_enabled  (db, "com/synth/" + lo_stage_str(stage) + "/hopping_enabled")
+    {
+        bind_accessor(_lox_src_ch0);
+        bind_accessor(_lox_src_ch1);
+        bind_accessor(_lox_mapping_synth0);
+        bind_accessor(_lox_mapping_synth1);
+        bind_accessor(_lox_hopping_enabled);
+    }
+
+private:
+    virtual void resolve();
+
+    //Inputs
+    experts::data_reader_t<twinrx_ctrl::lo_source_t>    _lox_src_ch0;
+    experts::data_reader_t<twinrx_ctrl::lo_source_t>    _lox_src_ch1;
+    //Outputs
+    experts::data_writer_t<lo_synth_mapping_t>          _lox_mapping_synth0;
+    experts::data_writer_t<lo_synth_mapping_t>          _lox_mapping_synth1;
+    experts::data_writer_t<bool>                        _lox_hopping_enabled;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_freq_coercion_expert
+ *
+ * This expert is responsible for calculating the coerced
+ * RF frequency after most settings and modes have been
+ * resolved.
+ *
+ * One instance of this expert is required for each channel
+ * ---------------------------------------------------------
+ */
+class twinrx_freq_coercion_expert : public experts::worker_node_t {
+public:
+    twinrx_freq_coercion_expert(const experts::node_retriever_t& db, std::string ch)
+    : experts::worker_node_t(prepend_ch("twinrx_freq_coercion_expert", ch)),
+      _lo1_freq_c       (db, prepend_ch("los/LO1/freq/coerced", ch)),
+      _lo2_freq_c       (db, prepend_ch("los/LO2/freq/coerced", ch)),
+      _if_freq_d        (db, prepend_ch("if_freq/desired", ch)),
+      _lo1_inj_side     (db, prepend_ch("ch/LO1/inj_side", ch)),
+      _lo2_inj_side     (db, prepend_ch("ch/LO2/inj_side", ch)),
+      _rf_freq_c        (db, prepend_ch("freq/coerced", ch))
+    {
+        bind_accessor(_lo1_freq_c);
+        bind_accessor(_lo2_freq_c);
+        bind_accessor(_if_freq_d);
+        bind_accessor(_lo1_inj_side);
+        bind_accessor(_lo2_inj_side);
+        bind_accessor(_rf_freq_c);
+    }
+
+private:
+    virtual void resolve();
+
+    //Inputs
+    experts::data_reader_t<double>          _lo1_freq_c;
+    experts::data_reader_t<double>          _lo2_freq_c;
+    experts::data_reader_t<double>          _if_freq_d;
+    experts::data_reader_t<lo_inj_side_t>   _lo1_inj_side;
+    experts::data_reader_t<lo_inj_side_t>   _lo2_inj_side;
+    //Outputs
+    experts::data_writer_t<double>          _rf_freq_c;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_nyquist_expert
+ *
+ * This expert is responsible for figuring out the DSP
+ * front-end settings required for each channel
+ *
+ * One instance of this expert is required for each channel
+ * ---------------------------------------------------------
+ */
+class twinrx_nyquist_expert : public experts::worker_node_t {
+public:
+    twinrx_nyquist_expert(const experts::node_retriever_t& db, std::string ch,
+                          dboard_iface::sptr db_iface)
+    : experts::worker_node_t(prepend_ch("twinrx_nyquist_expert", ch)),
+      _channel          (ch),
+      _codec_conn       (ch=="0"?"II":"QQ"),    //Ch->ADC Port mapping
+      _if_freq_d        (db, prepend_ch("if_freq/desired", ch)),
+      _lo1_inj_side     (db, prepend_ch("ch/LO1/inj_side", ch)),
+      _lo2_inj_side     (db, prepend_ch("ch/LO2/inj_side", ch)),
+      _if_freq_c        (db, prepend_ch("if_freq/coerced", ch)),
+      _db_iface         (db_iface)
+    {
+        bind_accessor(_if_freq_d);
+        bind_accessor(_lo1_inj_side);
+        bind_accessor(_lo2_inj_side);
+        bind_accessor(_if_freq_c);
+    }
+
+private:
+    virtual void resolve();
+
+    //Inputs
+    const std::string                                       _channel;
+    const std::string                                       _codec_conn;
+    experts::data_reader_t<double>                          _if_freq_d;
+    experts::data_reader_t<lo_inj_side_t>                   _lo1_inj_side;
+    experts::data_reader_t<lo_inj_side_t>                   _lo2_inj_side;
+    //Outputs
+    experts::data_writer_t<double>                          _if_freq_c;
+    dboard_iface::sptr                                      _db_iface;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_antenna_expert
+ *
+ * This expert is responsible for translating high-level
+ * antenna selection settings and channel enables to low-level
+ * switch configurations.
+ *
+ * One instance of this expert is required for all channels
+ * ---------------------------------------------------------
+ */
+class twinrx_antenna_expert : public experts::worker_node_t {
+public:
+    twinrx_antenna_expert(const experts::node_retriever_t& db)
+    : experts::worker_node_t("twinrx_antenna_expert"),
+      _antenna_ch0      (db, prepend_ch("antenna", "0")),
+      _antenna_ch1      (db, prepend_ch("antenna", "1")),
+      _enabled_ch0      (db, prepend_ch("enabled", "0")),
+      _enabled_ch1      (db, prepend_ch("enabled", "1")),
+      _lo_export_ch0    (db, prepend_ch("los/all/export", "0")),
+      _lo_export_ch1    (db, prepend_ch("los/all/export", "1")),
+      _ant_mapping      (db, "com/ant_mapping"),
+      _cal_mode         (db, "com/cal_mode")
+    {
+        bind_accessor(_antenna_ch0);
+        bind_accessor(_antenna_ch1);
+        bind_accessor(_enabled_ch0);
+        bind_accessor(_enabled_ch1);
+        bind_accessor(_lo_export_ch0);
+        bind_accessor(_lo_export_ch1);
+        bind_accessor(_ant_mapping);
+        bind_accessor(_cal_mode);
+    }
+
+private:
+    virtual void resolve();
+
+    //Inputs
+    experts::data_reader_t<std::string>                     _antenna_ch0;
+    experts::data_reader_t<std::string>                     _antenna_ch1;
+    experts::data_reader_t<bool>                            _enabled_ch0;
+    experts::data_reader_t<bool>                            _enabled_ch1;
+    experts::data_reader_t<bool>                            _lo_export_ch0;
+    experts::data_reader_t<bool>                            _lo_export_ch1;
+    //Outputs
+    experts::data_writer_t<twinrx_ctrl::antenna_mapping_t>  _ant_mapping;
+    experts::data_writer_t<twinrx_ctrl::cal_mode_t>         _cal_mode;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_chan_gain_expert
+ *
+ * This expert is responsible for mapping high-level channel
+ * gain settings to individual attenuator and amp configurations
+ * that are also channel-scoped. This expert will implement
+ * the gain distribution strategy.
+ *
+ * One instance of this expert is required for each channel
+ * ---------------------------------------------------------
+ */
+class twinrx_chan_gain_expert : public experts::worker_node_t {
+public:
+    twinrx_chan_gain_expert(const experts::node_retriever_t& db, std::string ch)
+    : experts::worker_node_t(prepend_ch("twinrx_chan_gain_expert", ch)),
+      _gain         (db, prepend_ch("gain", ch)),
+      _gain_profile (db, prepend_ch("gain_profile", ch)),
+      _signal_path  (db, prepend_ch("ch/signal_path", ch)),
+      _lb_presel    (db, prepend_ch("ch/lb_presel", ch)),
+      _hb_presel    (db, prepend_ch("ch/hb_presel", ch)),
+      _ant_mapping  (db, "com/ant_mapping"),
+      _input_atten  (db, prepend_ch("ch/input_atten", ch)),
+      _lb_atten     (db, prepend_ch("ch/lb_atten", ch)),
+      _hb_atten     (db, prepend_ch("ch/hb_atten", ch)),
+      _preamp1      (db, prepend_ch("ch/preamp1", ch)),
+      _preamp2      (db, prepend_ch("ch/preamp2", ch))
+    {
+        bind_accessor(_gain);
+        bind_accessor(_gain_profile);
+        bind_accessor(_signal_path);
+        bind_accessor(_lb_presel);
+        bind_accessor(_hb_presel);
+        bind_accessor(_ant_mapping);
+        bind_accessor(_input_atten);
+        bind_accessor(_lb_atten);
+        bind_accessor(_hb_atten);
+        bind_accessor(_preamp1);
+        bind_accessor(_preamp2);
+    }
+
+private:
+    virtual void resolve();
+
+    //Inputs
+    experts::data_reader_t<double>                          _gain;
+    experts::data_reader_t<std::string>                     _gain_profile;
+    experts::data_reader_t<twinrx_ctrl::signal_path_t>      _signal_path;
+    experts::data_reader_t<twinrx_ctrl::preselector_path_t> _lb_presel;
+    experts::data_reader_t<twinrx_ctrl::preselector_path_t> _hb_presel;
+    experts::data_reader_t<twinrx_ctrl::antenna_mapping_t>  _ant_mapping;
+    //Outputs
+    experts::data_writer_t<boost::uint8_t>                  _input_atten;
+    experts::data_writer_t<boost::uint8_t>                  _lb_atten;
+    experts::data_writer_t<boost::uint8_t>                  _hb_atten;
+    experts::data_writer_t<twinrx_ctrl::preamp_state_t>     _preamp1;
+    experts::data_writer_t<bool>                            _preamp2;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_ant_gain_expert
+ *
+ * This expert is responsible for translating between the
+ * channel-scoped low-level gain settings to antenna-scoped
+ * gain settings.
+ *
+ * One instance of this expert is required for all channels
+ * ---------------------------------------------------------
+ */
+class twinrx_ant_gain_expert : public experts::worker_node_t {
+public:
+    twinrx_ant_gain_expert(const experts::node_retriever_t& db)
+    : experts::worker_node_t("twinrx_ant_gain_expert"),
+      _ant_mapping          (db, "com/ant_mapping"),
+      _ch0_input_atten      (db, prepend_ch("ch/input_atten", "0")),
+      _ch0_preamp1          (db, prepend_ch("ch/preamp1", "0")),
+      _ch0_preamp2          (db, prepend_ch("ch/preamp2", "0")),
+      _ch0_lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", "0")),
+      _ch1_input_atten      (db, prepend_ch("ch/input_atten", "1")),
+      _ch1_preamp1          (db, prepend_ch("ch/preamp1", "1")),
+      _ch1_preamp2          (db, prepend_ch("ch/preamp2", "1")),
+      _ch1_lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", "1")),
+      _ant0_input_atten     (db, prepend_ch("ant/input_atten", "0")),
+      _ant0_preamp1         (db, prepend_ch("ant/preamp1", "0")),
+      _ant0_preamp2         (db, prepend_ch("ant/preamp2", "0")),
+      _ant0_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "0")),
+      _ant1_input_atten     (db, prepend_ch("ant/input_atten", "1")),
+      _ant1_preamp1         (db, prepend_ch("ant/preamp1", "1")),
+      _ant1_preamp2         (db, prepend_ch("ant/preamp2", "1")),
+      _ant1_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "1"))
+    {
+        bind_accessor(_ant_mapping);
+        bind_accessor(_ch0_input_atten);
+        bind_accessor(_ch0_preamp1);
+        bind_accessor(_ch0_preamp2);
+        bind_accessor(_ch0_lb_preamp_presel);
+        bind_accessor(_ch1_input_atten);
+        bind_accessor(_ch1_preamp1);
+        bind_accessor(_ch1_preamp2);
+        bind_accessor(_ch1_lb_preamp_presel);
+        bind_accessor(_ant0_input_atten);
+        bind_accessor(_ant0_preamp1);
+        bind_accessor(_ant0_preamp2);
+        bind_accessor(_ant0_lb_preamp_presel);
+        bind_accessor(_ant1_input_atten);
+        bind_accessor(_ant1_preamp1);
+        bind_accessor(_ant1_preamp2);
+        bind_accessor(_ant1_lb_preamp_presel);
+    }
+
+private:
+    virtual void resolve();
+
+    //Inputs
+    experts::data_reader_t<twinrx_ctrl::antenna_mapping_t>  _ant_mapping;
+    experts::data_reader_t<boost::uint8_t>                  _ch0_input_atten;
+    experts::data_reader_t<twinrx_ctrl::preamp_state_t>     _ch0_preamp1;
+    experts::data_reader_t<bool>                            _ch0_preamp2;
+    experts::data_reader_t<bool>                            _ch0_lb_preamp_presel;
+    experts::data_reader_t<boost::uint8_t>                  _ch1_input_atten;
+    experts::data_reader_t<twinrx_ctrl::preamp_state_t>     _ch1_preamp1;
+    experts::data_reader_t<bool>                            _ch1_preamp2;
+    experts::data_reader_t<bool>                            _ch1_lb_preamp_presel;
+
+    //Outputs
+    experts::data_writer_t<boost::uint8_t>                  _ant0_input_atten;
+    experts::data_writer_t<twinrx_ctrl::preamp_state_t>     _ant0_preamp1;
+    experts::data_writer_t<bool>                            _ant0_preamp2;
+    experts::data_writer_t<bool>                            _ant0_lb_preamp_presel;
+    experts::data_writer_t<boost::uint8_t>                  _ant1_input_atten;
+    experts::data_writer_t<twinrx_ctrl::preamp_state_t>     _ant1_preamp1;
+    experts::data_writer_t<bool>                            _ant1_preamp2;
+    experts::data_writer_t<bool>                            _ant1_lb_preamp_presel;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_settings_expert
+ *
+ * This expert is responsible for gathering all low-level
+ * settings and writing them to hardware. All LO frequency
+ * settings are cached with a hysteresis. All other settings
+ * are always written to twinrx_ctrl and rely on register
+ * level caching.
+ *
+ * One instance of this expert is required for all channels
+ * ---------------------------------------------------------
+ */
+class twinrx_settings_expert : public experts::worker_node_t {
+public:
+    twinrx_settings_expert(const experts::node_retriever_t& db, twinrx_ctrl::sptr ctrl)
+    : experts::worker_node_t("twinrx_settings_expert"), _ctrl(ctrl),
+      _ch0              (db, "0"),
+      _ch1              (db, "1"),
+      _lo1_synth0_mapping(db, "0/synth/LO1/mapping"),
+      _lo1_synth1_mapping(db, "1/synth/LO1/mapping"),
+      _lo2_synth0_mapping(db, "0/synth/LO2/mapping"),
+      _lo2_synth1_mapping(db, "1/synth/LO2/mapping"),
+      _lo1_hopping_enabled(db, "com/synth/LO1/hopping_enabled"),
+      _lo2_hopping_enabled(db, "com/synth/LO2/hopping_enabled"),
+      _lo1_export_src   (db, "com/LO1/export_source"),
+      _lo2_export_src   (db, "com/LO2/export_source"),
+      _ant_mapping      (db, "com/ant_mapping"),
+      _cal_mode         (db, "com/cal_mode")
+    {
+        for (size_t i = 0; i < 2; i++) {
+            ch_settings& ch = (i==1) ? _ch1 : _ch0;
+            bind_accessor(ch.chan_enabled);
+            bind_accessor(ch.preamp1);
+            bind_accessor(ch.preamp2);
+            bind_accessor(ch.lb_preamp_presel);
+            bind_accessor(ch.signal_path);
+            bind_accessor(ch.lb_presel);
+            bind_accessor(ch.hb_presel);
+            bind_accessor(ch.input_atten);
+            bind_accessor(ch.lb_atten);
+            bind_accessor(ch.hb_atten);
+            bind_accessor(ch.lo1_source);
+            bind_accessor(ch.lo2_source);
+            bind_accessor(ch.lo1_freq_d);
+            bind_accessor(ch.lo2_freq_d);
+            bind_accessor(ch.lo1_freq_c);
+            bind_accessor(ch.lo2_freq_c);
+        }
+        bind_accessor(_lo1_synth0_mapping);
+        bind_accessor(_lo1_synth1_mapping);
+        bind_accessor(_lo2_synth0_mapping);
+        bind_accessor(_lo2_synth1_mapping);
+        bind_accessor(_lo1_hopping_enabled);
+        bind_accessor(_lo2_hopping_enabled);
+        bind_accessor(_lo1_export_src);
+        bind_accessor(_lo2_export_src);
+        bind_accessor(_ant_mapping);
+        bind_accessor(_cal_mode);
+    }
+
+private:
+    virtual void resolve();
+    void _resolve_lox_freq(
+        lo_stage_t                      lo_stage,
+        experts::data_reader_t<double>& ch0_freq_d,
+        experts::data_reader_t<double>& ch1_freq_d,
+        experts::data_writer_t<double>& ch0_freq_c,
+        experts::data_writer_t<double>& ch1_freq_c,
+        twinrx_ctrl::lo_source_t        ch0_lo_source,
+        twinrx_ctrl::lo_source_t        ch1_lo_source,
+        lo_synth_mapping_t              synth0_mapping,
+        lo_synth_mapping_t              synth1_mapping,
+        bool                            hopping_enabled);
+    double _set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq);
+
+    class ch_settings {
+    public:
+        ch_settings(const experts::node_retriever_t& db, const std::string& ch) :
+            chan_enabled    (db, prepend_ch("enabled", ch)),
+            preamp1         (db, prepend_ch("ant/preamp1", ch)),
+            preamp2         (db, prepend_ch("ant/preamp2", ch)),
+            lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", ch)),
+            signal_path     (db, prepend_ch("ch/signal_path", ch)),
+            lb_presel       (db, prepend_ch("ch/lb_presel", ch)),
+            hb_presel       (db, prepend_ch("ch/hb_presel", ch)),
+            input_atten     (db, prepend_ch("ant/input_atten", ch)),
+            lb_atten        (db, prepend_ch("ch/lb_atten", ch)),
+            hb_atten        (db, prepend_ch("ch/hb_atten", ch)),
+            lo1_source      (db, prepend_ch("ch/LO1/source", ch)),
+            lo2_source      (db, prepend_ch("ch/LO2/source", ch)),
+            lo1_freq_d      (db, prepend_ch("los/LO1/freq/desired", ch)),
+            lo2_freq_d      (db, prepend_ch("los/LO2/freq/desired", ch)),
+            lo1_freq_c      (db, prepend_ch("los/LO1/freq/coerced", ch)),
+            lo2_freq_c      (db, prepend_ch("los/LO2/freq/coerced", ch))
+        {}
+
+        //Inputs (channel specific)
+        experts::data_reader_t<bool>                            chan_enabled;
+        experts::data_reader_t<twinrx_ctrl::preamp_state_t>     preamp1;
+        experts::data_reader_t<bool>                            preamp2;
+        experts::data_reader_t<bool>                            lb_preamp_presel;
+        experts::data_reader_t<twinrx_ctrl::signal_path_t>      signal_path;
+        experts::data_reader_t<twinrx_ctrl::preselector_path_t> lb_presel;
+        experts::data_reader_t<twinrx_ctrl::preselector_path_t> hb_presel;
+        experts::data_reader_t<boost::uint8_t>                  input_atten;
+        experts::data_reader_t<boost::uint8_t>                  lb_atten;
+        experts::data_reader_t<boost::uint8_t>                  hb_atten;
+        experts::data_reader_t<twinrx_ctrl::lo_source_t>        lo1_source;
+        experts::data_reader_t<twinrx_ctrl::lo_source_t>        lo2_source;
+        experts::data_reader_t<double>                          lo1_freq_d;
+        experts::data_reader_t<double>                          lo2_freq_d;
+
+        //Output (channel specific)
+        experts::data_writer_t<double>                          lo1_freq_c;
+        experts::data_writer_t<double>                          lo2_freq_c;
+    };
+
+    //External interface
+    twinrx_ctrl::sptr                                       _ctrl;
+
+    //Inputs (channel agnostic)
+    ch_settings                                             _ch0;
+    ch_settings                                             _ch1;
+    experts::data_reader_t<lo_synth_mapping_t>              _lo1_synth0_mapping;
+    experts::data_reader_t<lo_synth_mapping_t>              _lo1_synth1_mapping;
+    experts::data_reader_t<lo_synth_mapping_t>              _lo2_synth0_mapping;
+    experts::data_reader_t<lo_synth_mapping_t>              _lo2_synth1_mapping;
+    experts::data_reader_t<bool>                            _lo1_hopping_enabled;
+    experts::data_reader_t<bool>                            _lo2_hopping_enabled;
+    experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src;
+    experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src;
+    experts::data_reader_t<twinrx_ctrl::antenna_mapping_t>  _ant_mapping;
+    experts::data_reader_t<twinrx_ctrl::cal_mode_t>         _cal_mode;
+
+    //Outputs (channel agnostic)
+    //None
+
+    //Misc
+    struct lo_freq_cache_t {
+        rf_freq_ppm_t desired;
+        double        coerced;
+    };
+    lo_freq_cache_t  _cached_lo1_synth0_freq;
+    lo_freq_cache_t  _cached_lo2_synth0_freq;
+    lo_freq_cache_t  _cached_lo1_synth1_freq;
+    lo_freq_cache_t  _cached_lo2_synth1_freq;
+
+    static const bool FORCE_COMMIT;
+};
+
+
+}}}} //namespaces
+
+#endif /* INCLUDED_DBOARD_TWINRX_EXPERTS_HPP */
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp
new file mode 100644
index 000000000..5cc8b49f3
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp
@@ -0,0 +1,860 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "twinrx_gain_tables.hpp"
+#include <uhd/exception.hpp>
+#include <boost/assign/list_of.hpp>
+
+using namespace uhd::usrp::dboard::twinrx;
+
+static const std::vector<twinrx_gain_config_t> HIGHBAND1_TABLE = boost::assign::list_of
+        //                       Index,   Gain, Atten1, Atten2,  Amp1,  Amp2
+        ( twinrx_gain_config_t(      0,  -28.3,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      1,  -28.3,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      2,  -28.3,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      3,  -28.3,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      4,  -28.3,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      5,  -28.3,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      6,  -28.3,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      7,  -27.3,   31,   30, false, false ) )
+        ( twinrx_gain_config_t(      8,  -26.3,   31,   29, false, false ) )
+        ( twinrx_gain_config_t(      9,  -25.3,   31,   28, false, false ) )
+        ( twinrx_gain_config_t(     10,  -24.3,   31,   27, false, false ) )
+        ( twinrx_gain_config_t(     11,  -23.3,   31,   26, false, false ) )
+        ( twinrx_gain_config_t(     12,  -22.3,   31,   25, false, false ) )
+        ( twinrx_gain_config_t(     13,  -21.3,   31,   24, false, false ) )
+        ( twinrx_gain_config_t(     14,  -20.3,   31,   23, false, false ) )
+        ( twinrx_gain_config_t(     15,  -19.3,   31,   22, false, false ) )
+        ( twinrx_gain_config_t(     16,  -18.3,   31,   21, false, false ) )
+        ( twinrx_gain_config_t(     17,  -17.3,   31,   20, false, false ) )
+        ( twinrx_gain_config_t(     18,  -16.3,   31,   19, false, false ) )
+        ( twinrx_gain_config_t(     19,  -15.3,   31,   18, false, false ) )
+        ( twinrx_gain_config_t(     20,  -14.3,   31,   17, false, false ) )
+        ( twinrx_gain_config_t(     21,  -13.3,   31,   16, false, false ) )
+        ( twinrx_gain_config_t(     22,  -12.3,   31,   15, false, false ) )
+        ( twinrx_gain_config_t(     23,  -11.3,   31,   14, false, false ) )
+        ( twinrx_gain_config_t(     24,  -10.3,   31,   13, false, false ) )
+        ( twinrx_gain_config_t(     25,   -9.3,   31,   12, false, false ) )
+        ( twinrx_gain_config_t(     26,   -8.3,   30,   12, false, false ) )
+        ( twinrx_gain_config_t(     27,   -7.3,   30,   11, false, false ) )
+        ( twinrx_gain_config_t(     28,   -6.3,   29,   11, false, false ) )
+        ( twinrx_gain_config_t(     29,   -5.3,   28,   11, false, false ) )
+        ( twinrx_gain_config_t(     30,   -4.3,   27,   11, false, false ) )
+        ( twinrx_gain_config_t(     31,   -3.3,   27,   10, false, false ) )
+        ( twinrx_gain_config_t(     32,   -2.3,   26,   10, false, false ) )
+        ( twinrx_gain_config_t(     33,   -1.3,   25,   10, false, false ) )
+        ( twinrx_gain_config_t(     34,   -0.3,   24,   10, false, false ) )
+        ( twinrx_gain_config_t(     35,    0.7,   23,   10, false, false ) )
+        ( twinrx_gain_config_t(     36,    1.7,   22,   10, false, false ) )
+        ( twinrx_gain_config_t(     37,    2.7,   21,   10, false, false ) )
+        ( twinrx_gain_config_t(     38,    3.7,   21,    9, false, false ) )
+        ( twinrx_gain_config_t(     39,    4.7,   20,    9, false, false ) )
+        ( twinrx_gain_config_t(     40,    5.7,   19,    9, false, false ) )
+        ( twinrx_gain_config_t(     41,    6.7,   18,    9, false, false ) )
+        ( twinrx_gain_config_t(     42,    7.7,   17,    9, false, false ) )
+        ( twinrx_gain_config_t(     43,    8.7,   16,    9, false, false ) )
+        ( twinrx_gain_config_t(     44,    9.7,   15,    9, false, false ) )
+        ( twinrx_gain_config_t(     45,   10.7,   14,    9, false, false ) )
+        ( twinrx_gain_config_t(     46,   11.7,   13,    9, false, false ) )
+        ( twinrx_gain_config_t(     47,   12.7,   12,    9, false, false ) )
+        ( twinrx_gain_config_t(     48,   13.7,   11,    9, false, false ) )
+        ( twinrx_gain_config_t(     49,   14.7,   10,    9, false, false ) )
+        ( twinrx_gain_config_t(     50,   15.7,    9,    9, false, false ) )
+        ( twinrx_gain_config_t(     51,   16.7,    8,    9, false, false ) )
+        ( twinrx_gain_config_t(     52,   17.7,    7,    9, false, false ) )
+        ( twinrx_gain_config_t(     53,   18.7,    6,    9, false, false ) )
+        ( twinrx_gain_config_t(     54,   19.7,    5,    9, false, false ) )
+        ( twinrx_gain_config_t(     55,   20.7,    4,    9, false, false ) )
+        ( twinrx_gain_config_t(     56,   21.7,    3,    9, false, false ) )
+        ( twinrx_gain_config_t(     57,   22.7,    2,    9, false, false ) )
+        ( twinrx_gain_config_t(     58,   23.7,    1,    9, false, false ) )
+        ( twinrx_gain_config_t(     59,   24.7,    0,    9, false, false ) )
+        ( twinrx_gain_config_t(     60,   25.7,    0,    8, false, false ) )
+        ( twinrx_gain_config_t(     61,   26.7,    0,    7, false, false ) )
+        ( twinrx_gain_config_t(     62,   27.7,    0,    6, false, false ) )
+        ( twinrx_gain_config_t(     63,   28.7,    0,    5, false, false ) )
+        ( twinrx_gain_config_t(     64,   29.7,    0,    4, false, false ) )
+        ( twinrx_gain_config_t(     65,   30.7,    0,    3, false, false ) )
+        ( twinrx_gain_config_t(     66,   31.7,    0,    2, false, false ) )
+        ( twinrx_gain_config_t(     67,   32.7,    0,    1, false, false ) )
+        ( twinrx_gain_config_t(     68,   33.7,    0,    0, false, false ) )
+        ( twinrx_gain_config_t(     69,   33.9,    3,    9,  true, false ) )
+        ( twinrx_gain_config_t(     70,   34.9,    2,    9,  true, false ) )
+        ( twinrx_gain_config_t(     71,   35.9,    1,    9,  true, false ) )
+        ( twinrx_gain_config_t(     72,   36.9,    0,    9,  true, false ) )
+        ( twinrx_gain_config_t(     73,   37.9,    0,    8,  true, false ) )
+        ( twinrx_gain_config_t(     74,   38.9,    0,    7,  true, false ) )
+        ( twinrx_gain_config_t(     75,   39.9,    0,    6,  true, false ) )
+        ( twinrx_gain_config_t(     76,   40.9,    0,    5,  true, false ) )
+        ( twinrx_gain_config_t(     77,   41.9,    0,    4,  true, false ) )
+        ( twinrx_gain_config_t(     78,   42.9,    0,    3,  true, false ) )
+        ( twinrx_gain_config_t(     79,   43.9,    0,    2,  true, false ) )
+        ( twinrx_gain_config_t(     80,   44.9,    0,    1,  true, false ) )
+        ( twinrx_gain_config_t(     81,   45.9,    0,    0,  true, false ) )
+        ( twinrx_gain_config_t(     82,   47.3,    1,   10,  true,  true ) )
+        ( twinrx_gain_config_t(     83,   48.3,    0,   10,  true,  true ) )
+        ( twinrx_gain_config_t(     84,   49.3,    0,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     85,   50.3,    0,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     86,   51.3,    0,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     87,   52.3,    0,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     88,   53.3,    0,    5,  true,  true ) )
+        ( twinrx_gain_config_t(     89,   54.3,    0,    4,  true,  true ) )
+        ( twinrx_gain_config_t(     90,   55.3,    0,    3,  true,  true ) )
+        ( twinrx_gain_config_t(     91,   56.3,    0,    2,  true,  true ) )
+        ( twinrx_gain_config_t(     92,   57.3,    0,    1,  true,  true ) )
+        ( twinrx_gain_config_t(     93,   58.3,    0,    0,  true,  true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> HIGHBAND2_TABLE = boost::assign::list_of
+        //                       Index,   Gain, Atten1, Atten2,  Amp1,  Amp2
+        ( twinrx_gain_config_t(      0,  -30.9,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      1,  -30.9,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      2,  -30.9,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      3,  -30.9,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      4,  -30.9,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      5,  -30.9,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      6,  -30.9,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      7,  -30.9,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      8,  -29.9,   31,   30, false, false ) )
+        ( twinrx_gain_config_t(      9,  -28.9,   31,   29, false, false ) )
+        ( twinrx_gain_config_t(     10,  -27.9,   31,   28, false, false ) )
+        ( twinrx_gain_config_t(     11,  -26.9,   31,   27, false, false ) )
+        ( twinrx_gain_config_t(     12,  -25.9,   31,   26, false, false ) )
+        ( twinrx_gain_config_t(     13,  -24.9,   31,   25, false, false ) )
+        ( twinrx_gain_config_t(     14,  -23.9,   31,   24, false, false ) )
+        ( twinrx_gain_config_t(     15,  -22.9,   31,   23, false, false ) )
+        ( twinrx_gain_config_t(     16,  -21.9,   31,   22, false, false ) )
+        ( twinrx_gain_config_t(     17,  -20.9,   31,   21, false, false ) )
+        ( twinrx_gain_config_t(     18,  -19.9,   31,   20, false, false ) )
+        ( twinrx_gain_config_t(     19,  -18.9,   31,   19, false, false ) )
+        ( twinrx_gain_config_t(     20,  -17.9,   31,   18, false, false ) )
+        ( twinrx_gain_config_t(     21,  -16.9,   31,   17, false, false ) )
+        ( twinrx_gain_config_t(     22,  -15.9,   31,   16, false, false ) )
+        ( twinrx_gain_config_t(     23,  -14.9,   31,   15, false, false ) )
+        ( twinrx_gain_config_t(     24,  -13.9,   31,   14, false, false ) )
+        ( twinrx_gain_config_t(     25,  -12.9,   31,   13, false, false ) )
+        ( twinrx_gain_config_t(     26,  -11.9,   31,   12, false, false ) )
+        ( twinrx_gain_config_t(     27,  -10.9,   31,   11, false, false ) )
+        ( twinrx_gain_config_t(     28,   -9.9,   30,   11, false, false ) )
+        ( twinrx_gain_config_t(     29,   -8.9,   29,   11, false, false ) )
+        ( twinrx_gain_config_t(     30,   -7.9,   29,   10, false, false ) )
+        ( twinrx_gain_config_t(     31,   -6.9,   28,   10, false, false ) )
+        ( twinrx_gain_config_t(     32,   -5.9,   27,   10, false, false ) )
+        ( twinrx_gain_config_t(     33,   -4.9,   27,    9, false, false ) )
+        ( twinrx_gain_config_t(     34,   -3.9,   26,    9, false, false ) )
+        ( twinrx_gain_config_t(     35,   -2.9,   25,    9, false, false ) )
+        ( twinrx_gain_config_t(     36,   -1.9,   24,    9, false, false ) )
+        ( twinrx_gain_config_t(     37,   -0.9,   23,    9, false, false ) )
+        ( twinrx_gain_config_t(     38,    0.1,   23,    8, false, false ) )
+        ( twinrx_gain_config_t(     39,    1.1,   22,    8, false, false ) )
+        ( twinrx_gain_config_t(     40,    2.1,   21,    8, false, false ) )
+        ( twinrx_gain_config_t(     41,    3.1,   20,    8, false, false ) )
+        ( twinrx_gain_config_t(     42,    4.1,   19,    8, false, false ) )
+        ( twinrx_gain_config_t(     43,    5.1,   18,    8, false, false ) )
+        ( twinrx_gain_config_t(     44,    6.1,   17,    8, false, false ) )
+        ( twinrx_gain_config_t(     45,    7.1,   16,    8, false, false ) )
+        ( twinrx_gain_config_t(     46,    8.1,   15,    8, false, false ) )
+        ( twinrx_gain_config_t(     47,    9.1,   14,    8, false, false ) )
+        ( twinrx_gain_config_t(     48,   10.1,   13,    8, false, false ) )
+        ( twinrx_gain_config_t(     49,   11.1,   12,    8, false, false ) )
+        ( twinrx_gain_config_t(     50,   12.1,   11,    8, false, false ) )
+        ( twinrx_gain_config_t(     51,   13.1,   10,    8, false, false ) )
+        ( twinrx_gain_config_t(     52,   14.1,    9,    8, false, false ) )
+        ( twinrx_gain_config_t(     53,   15.1,    8,    8, false, false ) )
+        ( twinrx_gain_config_t(     54,   16.1,    7,    8, false, false ) )
+        ( twinrx_gain_config_t(     55,   17.1,    6,    8, false, false ) )
+        ( twinrx_gain_config_t(     56,   18.1,    5,    8, false, false ) )
+        ( twinrx_gain_config_t(     57,   19.1,    4,    8, false, false ) )
+        ( twinrx_gain_config_t(     58,   20.1,    3,    8, false, false ) )
+        ( twinrx_gain_config_t(     59,   21.1,    2,    8, false, false ) )
+        ( twinrx_gain_config_t(     60,   22.1,    1,    8, false, false ) )
+        ( twinrx_gain_config_t(     61,   23.1,    0,    8, false, false ) )
+        ( twinrx_gain_config_t(     62,   24.1,    0,    7, false, false ) )
+        ( twinrx_gain_config_t(     63,   25.1,    0,    6, false, false ) )
+        ( twinrx_gain_config_t(     64,   26.1,    0,    5, false, false ) )
+        ( twinrx_gain_config_t(     65,   27.1,    0,    4, false, false ) )
+        ( twinrx_gain_config_t(     66,   28.1,    0,    3, false, false ) )
+        ( twinrx_gain_config_t(     67,   29.1,    0,    2, false, false ) )
+        ( twinrx_gain_config_t(     68,   30.1,    0,    1, false, false ) )
+        ( twinrx_gain_config_t(     69,   31.9,    0,   10, false,  true ) )
+        ( twinrx_gain_config_t(     70,   31.9,    0,   10, false,  true ) )
+        ( twinrx_gain_config_t(     71,   32.9,    0,    9, false,  true ) )
+        ( twinrx_gain_config_t(     72,   33.9,    0,    8, false,  true ) )
+        ( twinrx_gain_config_t(     73,   34.9,    0,    7, false,  true ) )
+        ( twinrx_gain_config_t(     74,   35.9,    0,    6, false,  true ) )
+        ( twinrx_gain_config_t(     75,   36.9,    0,    5, false,  true ) )
+        ( twinrx_gain_config_t(     76,   38.6,    0,    6,  true, false ) )
+        ( twinrx_gain_config_t(     77,   39.6,    0,    5,  true, false ) )
+        ( twinrx_gain_config_t(     78,   40.6,    0,    4,  true, false ) )
+        ( twinrx_gain_config_t(     79,   41.6,    0,    3,  true, false ) )
+        ( twinrx_gain_config_t(     80,   42.6,    0,    2,  true, false ) )
+        ( twinrx_gain_config_t(     81,   43.6,    0,    1,  true, false ) )
+        ( twinrx_gain_config_t(     82,   44.4,    2,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     83,   45.4,    1,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     84,   46.4,    0,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     85,   47.4,    0,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     86,   48.4,    0,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     87,   49.4,    0,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     88,   50.4,    0,    5,  true,  true ) )
+        ( twinrx_gain_config_t(     89,   51.4,    0,    4,  true,  true ) )
+        ( twinrx_gain_config_t(     90,   52.4,    0,    3,  true,  true ) )
+        ( twinrx_gain_config_t(     91,   53.4,    0,    2,  true,  true ) )
+        ( twinrx_gain_config_t(     92,   54.4,    0,    1,  true,  true ) )
+        ( twinrx_gain_config_t(     93,   55.4,    0,    0,  true,  true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> HIGHBAND3_TABLE = boost::assign::list_of
+        //                       Index,   Gain, Atten1, Atten2,  Amp1,  Amp2
+        ( twinrx_gain_config_t(      0,  -31.1,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      1,  -31.1,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      2,  -31.1,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      3,  -31.1,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      4,  -31.1,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      5,  -31.1,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      6,  -31.1,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      7,  -30.1,   31,   30, false, false ) )
+        ( twinrx_gain_config_t(      8,  -29.1,   31,   29, false, false ) )
+        ( twinrx_gain_config_t(      9,  -28.1,   31,   28, false, false ) )
+        ( twinrx_gain_config_t(     10,  -27.1,   31,   27, false, false ) )
+        ( twinrx_gain_config_t(     11,  -26.1,   31,   26, false, false ) )
+        ( twinrx_gain_config_t(     12,  -25.1,   31,   25, false, false ) )
+        ( twinrx_gain_config_t(     13,  -24.1,   31,   24, false, false ) )
+        ( twinrx_gain_config_t(     14,  -23.1,   31,   23, false, false ) )
+        ( twinrx_gain_config_t(     15,  -22.1,   31,   22, false, false ) )
+        ( twinrx_gain_config_t(     16,  -21.1,   31,   21, false, false ) )
+        ( twinrx_gain_config_t(     17,  -20.1,   31,   20, false, false ) )
+        ( twinrx_gain_config_t(     18,  -19.1,   31,   19, false, false ) )
+        ( twinrx_gain_config_t(     19,  -18.1,   31,   18, false, false ) )
+        ( twinrx_gain_config_t(     20,  -17.1,   31,   17, false, false ) )
+        ( twinrx_gain_config_t(     21,  -16.1,   31,   16, false, false ) )
+        ( twinrx_gain_config_t(     22,  -15.1,   31,   15, false, false ) )
+        ( twinrx_gain_config_t(     23,  -14.1,   31,   14, false, false ) )
+        ( twinrx_gain_config_t(     24,  -13.1,   31,   13, false, false ) )
+        ( twinrx_gain_config_t(     25,  -12.1,   30,   13, false, false ) )
+        ( twinrx_gain_config_t(     26,  -11.1,   30,   12, false, false ) )
+        ( twinrx_gain_config_t(     27,  -10.1,   29,   12, false, false ) )
+        ( twinrx_gain_config_t(     28,   -9.1,   28,   12, false, false ) )
+        ( twinrx_gain_config_t(     29,   -8.1,   28,   11, false, false ) )
+        ( twinrx_gain_config_t(     30,   -7.1,   27,   11, false, false ) )
+        ( twinrx_gain_config_t(     31,   -6.1,   26,   11, false, false ) )
+        ( twinrx_gain_config_t(     32,   -5.1,   26,   10, false, false ) )
+        ( twinrx_gain_config_t(     33,   -4.1,   25,   10, false, false ) )
+        ( twinrx_gain_config_t(     34,   -3.1,   24,   10, false, false ) )
+        ( twinrx_gain_config_t(     35,   -2.1,   23,   10, false, false ) )
+        ( twinrx_gain_config_t(     36,   -1.1,   22,   10, false, false ) )
+        ( twinrx_gain_config_t(     37,   -0.1,   21,   10, false, false ) )
+        ( twinrx_gain_config_t(     38,    0.9,   21,    9, false, false ) )
+        ( twinrx_gain_config_t(     39,    1.9,   20,    9, false, false ) )
+        ( twinrx_gain_config_t(     40,    2.9,   19,    9, false, false ) )
+        ( twinrx_gain_config_t(     41,    3.9,   18,    9, false, false ) )
+        ( twinrx_gain_config_t(     42,    4.9,   17,    9, false, false ) )
+        ( twinrx_gain_config_t(     43,    5.9,   16,    9, false, false ) )
+        ( twinrx_gain_config_t(     44,    6.9,   15,    9, false, false ) )
+        ( twinrx_gain_config_t(     45,    7.9,   14,    9, false, false ) )
+        ( twinrx_gain_config_t(     46,    8.9,   13,    9, false, false ) )
+        ( twinrx_gain_config_t(     47,    9.9,   12,    9, false, false ) )
+        ( twinrx_gain_config_t(     48,   10.9,   11,    9, false, false ) )
+        ( twinrx_gain_config_t(     49,   11.9,   10,    9, false, false ) )
+        ( twinrx_gain_config_t(     50,   12.9,    9,    9, false, false ) )
+        ( twinrx_gain_config_t(     51,   13.9,    8,    9, false, false ) )
+        ( twinrx_gain_config_t(     52,   14.9,    7,    9, false, false ) )
+        ( twinrx_gain_config_t(     53,   15.9,    6,    9, false, false ) )
+        ( twinrx_gain_config_t(     54,   16.9,    5,    9, false, false ) )
+        ( twinrx_gain_config_t(     55,   17.9,    4,    9, false, false ) )
+        ( twinrx_gain_config_t(     56,   18.9,    3,    9, false, false ) )
+        ( twinrx_gain_config_t(     57,   19.9,    2,    9, false, false ) )
+        ( twinrx_gain_config_t(     58,   20.9,    1,    9, false, false ) )
+        ( twinrx_gain_config_t(     59,   21.9,    0,    9, false, false ) )
+        ( twinrx_gain_config_t(     60,   22.9,    0,    8, false, false ) )
+        ( twinrx_gain_config_t(     61,   23.9,    0,    7, false, false ) )
+        ( twinrx_gain_config_t(     62,   24.9,    0,    6, false, false ) )
+        ( twinrx_gain_config_t(     63,   25.9,    0,    5, false, false ) )
+        ( twinrx_gain_config_t(     64,   26.9,    0,    4, false, false ) )
+        ( twinrx_gain_config_t(     65,   27.9,    0,    3, false, false ) )
+        ( twinrx_gain_config_t(     66,   28.9,    0,    2, false, false ) )
+        ( twinrx_gain_config_t(     67,   29.9,    0,    1, false, false ) )
+        ( twinrx_gain_config_t(     68,   31.3,    0,    9, false,  true ) )
+        ( twinrx_gain_config_t(     69,   32.3,    0,    8, false,  true ) )
+        ( twinrx_gain_config_t(     70,   33.3,    0,    7, false,  true ) )
+        ( twinrx_gain_config_t(     71,   34.3,    0,    6, false,  true ) )
+        ( twinrx_gain_config_t(     72,   35.3,    0,    5, false,  true ) )
+        ( twinrx_gain_config_t(     73,   36.3,    0,    4, false,  true ) )
+        ( twinrx_gain_config_t(     74,   37.3,    0,    3, false,  true ) )
+        ( twinrx_gain_config_t(     75,   37.6,    0,    9,  true, false ) )
+        ( twinrx_gain_config_t(     76,   38.6,    0,    8,  true, false ) )
+        ( twinrx_gain_config_t(     77,   39.6,    0,    7,  true, false ) )
+        ( twinrx_gain_config_t(     78,   40.6,    0,    6,  true, false ) )
+        ( twinrx_gain_config_t(     79,   41.6,    0,    5,  true, false ) )
+        ( twinrx_gain_config_t(     80,   42.6,    0,    4,  true, false ) )
+        ( twinrx_gain_config_t(     81,   43.6,    0,    3,  true, false ) )
+        ( twinrx_gain_config_t(     82,   44.6,    0,    2,  true, false ) )
+        ( twinrx_gain_config_t(     83,   45.6,    0,    1,  true, false ) )
+        ( twinrx_gain_config_t(     84,   47.0,    0,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     85,   48.0,    0,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     86,   49.0,    0,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     87,   50.0,    0,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     88,   51.0,    0,    5,  true,  true ) )
+        ( twinrx_gain_config_t(     89,   52.0,    0,    4,  true,  true ) )
+        ( twinrx_gain_config_t(     90,   53.0,    0,    3,  true,  true ) )
+        ( twinrx_gain_config_t(     91,   54.0,    0,    2,  true,  true ) )
+        ( twinrx_gain_config_t(     92,   55.0,    0,    1,  true,  true ) )
+        ( twinrx_gain_config_t(     93,   56.0,    0,    0,  true,  true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> HIGHBAND4_TABLE = boost::assign::list_of
+        //                       Index,   Gain, Atten1, Atten2,  Amp1,  Amp2
+        ( twinrx_gain_config_t(      0,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      1,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      2,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      3,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      4,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      5,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      6,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      7,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      8,  -37.2,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      9,  -36.2,   31,   30, false, false ) )
+        ( twinrx_gain_config_t(     10,  -35.2,   31,   29, false, false ) )
+        ( twinrx_gain_config_t(     11,  -34.2,   31,   28, false, false ) )
+        ( twinrx_gain_config_t(     12,  -33.2,   31,   27, false, false ) )
+        ( twinrx_gain_config_t(     13,  -32.2,   31,   26, false, false ) )
+        ( twinrx_gain_config_t(     14,  -31.2,   31,   25, false, false ) )
+        ( twinrx_gain_config_t(     15,  -30.2,   31,   24, false, false ) )
+        ( twinrx_gain_config_t(     16,  -29.2,   31,   23, false, false ) )
+        ( twinrx_gain_config_t(     17,  -28.2,   31,   22, false, false ) )
+        ( twinrx_gain_config_t(     18,  -27.2,   31,   21, false, false ) )
+        ( twinrx_gain_config_t(     19,  -26.2,   31,   20, false, false ) )
+        ( twinrx_gain_config_t(     20,  -25.2,   31,   19, false, false ) )
+        ( twinrx_gain_config_t(     21,  -24.2,   31,   18, false, false ) )
+        ( twinrx_gain_config_t(     22,  -23.2,   31,   17, false, false ) )
+        ( twinrx_gain_config_t(     23,  -22.2,   31,   16, false, false ) )
+        ( twinrx_gain_config_t(     24,  -21.2,   31,   15, false, false ) )
+        ( twinrx_gain_config_t(     25,  -20.2,   31,   14, false, false ) )
+        ( twinrx_gain_config_t(     26,  -19.2,   31,   13, false, false ) )
+        ( twinrx_gain_config_t(     27,  -18.2,   31,   12, false, false ) )
+        ( twinrx_gain_config_t(     28,  -17.2,   31,   11, false, false ) )
+        ( twinrx_gain_config_t(     29,  -16.2,   31,   10, false, false ) )
+        ( twinrx_gain_config_t(     30,  -15.2,   30,   10, false, false ) )
+        ( twinrx_gain_config_t(     31,  -14.2,   30,    9, false, false ) )
+        ( twinrx_gain_config_t(     32,  -13.2,   29,    9, false, false ) )
+        ( twinrx_gain_config_t(     33,  -12.2,   28,    9, false, false ) )
+        ( twinrx_gain_config_t(     34,  -11.2,   28,    8, false, false ) )
+        ( twinrx_gain_config_t(     35,  -10.2,   27,    8, false, false ) )
+        ( twinrx_gain_config_t(     36,   -9.2,   27,    7, false, false ) )
+        ( twinrx_gain_config_t(     37,   -8.2,   26,    7, false, false ) )
+        ( twinrx_gain_config_t(     38,   -7.2,   25,    7, false, false ) )
+        ( twinrx_gain_config_t(     39,   -6.2,   24,    7, false, false ) )
+        ( twinrx_gain_config_t(     40,   -5.2,   24,    6, false, false ) )
+        ( twinrx_gain_config_t(     41,   -4.2,   23,    6, false, false ) )
+        ( twinrx_gain_config_t(     42,   -3.2,   22,    6, false, false ) )
+        ( twinrx_gain_config_t(     43,   -2.2,   21,    6, false, false ) )
+        ( twinrx_gain_config_t(     44,   -1.2,   20,    6, false, false ) )
+        ( twinrx_gain_config_t(     45,   -0.2,   19,    6, false, false ) )
+        ( twinrx_gain_config_t(     46,    0.8,   18,    6, false, false ) )
+        ( twinrx_gain_config_t(     47,    1.8,   17,    6, false, false ) )
+        ( twinrx_gain_config_t(     48,    2.8,   16,    6, false, false ) )
+        ( twinrx_gain_config_t(     49,    3.8,   16,    5, false, false ) )
+        ( twinrx_gain_config_t(     50,    4.8,   15,    5, false, false ) )
+        ( twinrx_gain_config_t(     51,    5.8,   14,    5, false, false ) )
+        ( twinrx_gain_config_t(     52,    6.8,   13,    5, false, false ) )
+        ( twinrx_gain_config_t(     53,    7.8,   12,    5, false, false ) )
+        ( twinrx_gain_config_t(     54,    8.8,   11,    5, false, false ) )
+        ( twinrx_gain_config_t(     55,    9.8,   10,    5, false, false ) )
+        ( twinrx_gain_config_t(     56,   10.8,    9,    5, false, false ) )
+        ( twinrx_gain_config_t(     57,   11.8,    8,    5, false, false ) )
+        ( twinrx_gain_config_t(     58,   12.8,    7,    5, false, false ) )
+        ( twinrx_gain_config_t(     59,   13.8,    6,    5, false, false ) )
+        ( twinrx_gain_config_t(     60,   14.8,    5,    5, false, false ) )
+        ( twinrx_gain_config_t(     61,   15.8,    4,    5, false, false ) )
+        ( twinrx_gain_config_t(     62,   16.8,    3,    5, false, false ) )
+        ( twinrx_gain_config_t(     63,   17.8,    2,    5, false, false ) )
+        ( twinrx_gain_config_t(     64,   18.8,    1,    5, false, false ) )
+        ( twinrx_gain_config_t(     65,   19.8,    0,    5, false, false ) )
+        ( twinrx_gain_config_t(     66,   20.8,    0,    4, false, false ) )
+        ( twinrx_gain_config_t(     67,   21.8,    0,    3, false, false ) )
+        ( twinrx_gain_config_t(     68,   22.8,    0,    2, false, false ) )
+        ( twinrx_gain_config_t(     69,   23.8,    0,    1, false, false ) )
+        ( twinrx_gain_config_t(     70,   24.8,    0,    0, false, false ) )
+        ( twinrx_gain_config_t(     71,   26.1,    0,    6, false,  true ) )
+        ( twinrx_gain_config_t(     72,   26.1,    0,    6, false,  true ) )
+        ( twinrx_gain_config_t(     73,   27.1,    0,    5, false,  true ) )
+        ( twinrx_gain_config_t(     74,   28.1,    0,    4, false,  true ) )
+        ( twinrx_gain_config_t(     75,   29.1,    0,    3, false,  true ) )
+        ( twinrx_gain_config_t(     76,   30.1,    0,    2, false,  true ) )
+        ( twinrx_gain_config_t(     77,   31.1,    0,    1, false,  true ) )
+        ( twinrx_gain_config_t(     78,   32.1,    0,    0, false,  true ) )
+        ( twinrx_gain_config_t(     79,   33.3,    0,    7,  true, false ) )
+        ( twinrx_gain_config_t(     80,   34.3,    0,    6,  true, false ) )
+        ( twinrx_gain_config_t(     81,   35.3,    0,    5,  true, false ) )
+        ( twinrx_gain_config_t(     82,   36.3,    0,    4,  true, false ) )
+        ( twinrx_gain_config_t(     83,   37.3,    0,    3,  true, false ) )
+        ( twinrx_gain_config_t(     84,   38.3,    0,    2,  true, false ) )
+        ( twinrx_gain_config_t(     85,   39.3,    0,    1,  true, false ) )
+        ( twinrx_gain_config_t(     86,   40.3,    0,    0,  true, false ) )
+        ( twinrx_gain_config_t(     87,   41.6,    0,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     88,   42.6,    0,    5,  true,  true ) )
+        ( twinrx_gain_config_t(     89,   43.6,    0,    4,  true,  true ) )
+        ( twinrx_gain_config_t(     90,   44.6,    0,    3,  true,  true ) )
+        ( twinrx_gain_config_t(     91,   45.6,    0,    2,  true,  true ) )
+        ( twinrx_gain_config_t(     92,   46.6,    0,    1,  true,  true ) )
+        ( twinrx_gain_config_t(     93,   47.6,    0,    0,  true,  true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> LOWBAND1_TABLE = boost::assign::list_of
+        //                       Index,   Gain, Atten1, Atten2,  Amp1,  Amp2
+        ( twinrx_gain_config_t(      0,  -31.1,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      1,  -30.1,   31,   30, false, false ) )
+        ( twinrx_gain_config_t(      2,  -29.1,   31,   29, false, false ) )
+        ( twinrx_gain_config_t(      3,  -28.1,   31,   28, false, false ) )
+        ( twinrx_gain_config_t(      4,  -27.1,   31,   27, false, false ) )
+        ( twinrx_gain_config_t(      5,  -26.1,   31,   26, false, false ) )
+        ( twinrx_gain_config_t(      6,  -25.1,   31,   25, false, false ) )
+        ( twinrx_gain_config_t(      7,  -24.1,   31,   24, false, false ) )
+        ( twinrx_gain_config_t(      8,  -23.1,   31,   23, false, false ) )
+        ( twinrx_gain_config_t(      9,  -22.1,   31,   22, false, false ) )
+        ( twinrx_gain_config_t(     10,  -21.1,   31,   21, false, false ) )
+        ( twinrx_gain_config_t(     11,  -20.1,   31,   20, false, false ) )
+        ( twinrx_gain_config_t(     12,  -19.1,   31,   19, false, false ) )
+        ( twinrx_gain_config_t(     13,  -18.1,   31,   18, false, false ) )
+        ( twinrx_gain_config_t(     14,  -17.1,   31,   17, false, false ) )
+        ( twinrx_gain_config_t(     15,  -16.1,   31,   16, false, false ) )
+        ( twinrx_gain_config_t(     16,  -15.1,   31,   15, false, false ) )
+        ( twinrx_gain_config_t(     17,  -14.1,   31,   14, false, false ) )
+        ( twinrx_gain_config_t(     18,  -13.1,   31,   13, false, false ) )
+        ( twinrx_gain_config_t(     19,  -12.1,   31,   12, false, false ) )
+        ( twinrx_gain_config_t(     20,  -11.1,   31,   11, false, false ) )
+        ( twinrx_gain_config_t(     21,  -10.1,   31,   10, false, false ) )
+        ( twinrx_gain_config_t(     22,   -9.1,   31,    9, false, false ) )
+        ( twinrx_gain_config_t(     23,   -8.1,   31,    8, false, false ) )
+        ( twinrx_gain_config_t(     24,   -7.1,   31,    7, false, false ) )
+        ( twinrx_gain_config_t(     25,   -6.1,   31,    6, false, false ) )
+        ( twinrx_gain_config_t(     26,   -5.1,   31,    5, false, false ) )
+        ( twinrx_gain_config_t(     27,   -4.1,   31,    4, false, false ) )
+        ( twinrx_gain_config_t(     28,   -3.1,   31,    3, false, false ) )
+        ( twinrx_gain_config_t(     29,   -2.1,   31,    2, false, false ) )
+        ( twinrx_gain_config_t(     30,   -1.1,   31,    1, false, false ) )
+        ( twinrx_gain_config_t(     31,   -0.1,   31,    0, false, false ) )
+        ( twinrx_gain_config_t(     32,    0.9,   30,    0, false, false ) )
+        ( twinrx_gain_config_t(     33,    1.9,   29,    0, false, false ) )
+        ( twinrx_gain_config_t(     34,    2.9,   28,    0, false, false ) )
+        ( twinrx_gain_config_t(     35,    3.9,   27,    0, false, false ) )
+        ( twinrx_gain_config_t(     36,    4.9,   26,    0, false, false ) )
+        ( twinrx_gain_config_t(     37,    5.9,   25,    0, false, false ) )
+        ( twinrx_gain_config_t(     38,    6.9,   24,    0, false, false ) )
+        ( twinrx_gain_config_t(     39,    7.9,   23,    0, false, false ) )
+        ( twinrx_gain_config_t(     40,    8.9,   22,    0, false, false ) )
+        ( twinrx_gain_config_t(     41,    9.9,   21,    0, false, false ) )
+        ( twinrx_gain_config_t(     42,   10.9,   20,    0, false, false ) )
+        ( twinrx_gain_config_t(     43,   11.9,   19,    0, false, false ) )
+        ( twinrx_gain_config_t(     44,   12.9,   18,    0, false, false ) )
+        ( twinrx_gain_config_t(     45,   13.9,   17,    0, false, false ) )
+        ( twinrx_gain_config_t(     46,   14.9,   16,    0, false, false ) )
+        ( twinrx_gain_config_t(     47,   15.9,   15,    0, false, false ) )
+        ( twinrx_gain_config_t(     48,   16.9,   14,    0, false, false ) )
+        ( twinrx_gain_config_t(     49,   17.9,   13,    0, false, false ) )
+        ( twinrx_gain_config_t(     50,   18.9,   12,    0, false, false ) )
+        ( twinrx_gain_config_t(     51,   19.9,   11,    0, false, false ) )
+        ( twinrx_gain_config_t(     52,   20.9,   10,    0, false, false ) )
+        ( twinrx_gain_config_t(     53,   21.9,    9,    0, false, false ) )
+        ( twinrx_gain_config_t(     54,   22.9,    8,    0, false, false ) )
+        ( twinrx_gain_config_t(     55,   23.9,    7,    0, false, false ) )
+        ( twinrx_gain_config_t(     56,   24.9,    6,    0, false, false ) )
+        ( twinrx_gain_config_t(     57,   25.9,    5,    0, false, false ) )
+        ( twinrx_gain_config_t(     58,   26.9,    4,    0, false, false ) )
+        ( twinrx_gain_config_t(     59,   27.9,    3,    0, false, false ) )
+        ( twinrx_gain_config_t(     60,   28.9,    2,    0, false, false ) )
+        ( twinrx_gain_config_t(     61,   29.9,    1,    0, false, false ) )
+        ( twinrx_gain_config_t(     62,   30.9,    0,    0, false, false ) )
+        ( twinrx_gain_config_t(     63,   31.2,    4,   11, false,  true ) )
+        ( twinrx_gain_config_t(     64,   32.2,    3,   11, false,  true ) )
+        ( twinrx_gain_config_t(     65,   33.2,    2,   11, false,  true ) )
+        ( twinrx_gain_config_t(     66,   34.2,    1,   11, false,  true ) )
+        ( twinrx_gain_config_t(     67,   35.2,    0,   11, false,  true ) )
+        ( twinrx_gain_config_t(     68,   36.2,   10,    0,  true, false ) )
+        ( twinrx_gain_config_t(     69,   37.2,    9,    0,  true, false ) )
+        ( twinrx_gain_config_t(     70,   38.2,    8,    0,  true, false ) )
+        ( twinrx_gain_config_t(     71,   39.2,    7,    0,  true, false ) )
+        ( twinrx_gain_config_t(     72,   40.2,    6,    0,  true, false ) )
+        ( twinrx_gain_config_t(     73,   41.2,    5,    0,  true, false ) )
+        ( twinrx_gain_config_t(     74,   42.2,    4,    0,  true, false ) )
+        ( twinrx_gain_config_t(     75,   43.2,    3,    0,  true, false ) )
+        ( twinrx_gain_config_t(     76,   44.2,    2,    0,  true, false ) )
+        ( twinrx_gain_config_t(     77,   45.2,    1,    0,  true, false ) )
+        ( twinrx_gain_config_t(     78,   46.2,    0,    0,  true, false ) )
+        ( twinrx_gain_config_t(     79,   47.5,    4,   10,  true,  true ) )
+        ( twinrx_gain_config_t(     80,   48.5,    3,   10,  true,  true ) )
+        ( twinrx_gain_config_t(     81,   49.5,    3,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     82,   50.5,    2,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     83,   51.5,    1,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     84,   52.5,    1,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     85,   53.5,    0,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     86,   54.5,    0,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     87,   55.5,    0,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     88,   56.5,    0,    5,  true,  true ) )
+        ( twinrx_gain_config_t(     89,   57.5,    0,    4,  true,  true ) )
+        ( twinrx_gain_config_t(     90,   58.5,    0,    3,  true,  true ) )
+        ( twinrx_gain_config_t(     91,   59.5,    0,    2,  true,  true ) )
+        ( twinrx_gain_config_t(     92,   60.5,    0,    1,  true,  true ) )
+        ( twinrx_gain_config_t(     93,   61.5,    0,    0,  true,  true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> LOWBAND2_TABLE = boost::assign::list_of
+        //                       Index,   Gain, Atten1, Atten2,  Amp1,  Amp2
+        ( twinrx_gain_config_t(      0,  -33.4,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      1,  -33.4,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      2,  -32.4,   31,   30, false, false ) )
+        ( twinrx_gain_config_t(      3,  -31.4,   31,   29, false, false ) )
+        ( twinrx_gain_config_t(      4,  -30.4,   31,   28, false, false ) )
+        ( twinrx_gain_config_t(      5,  -29.4,   31,   27, false, false ) )
+        ( twinrx_gain_config_t(      6,  -28.4,   31,   26, false, false ) )
+        ( twinrx_gain_config_t(      7,  -27.4,   31,   25, false, false ) )
+        ( twinrx_gain_config_t(      8,  -26.4,   31,   24, false, false ) )
+        ( twinrx_gain_config_t(      9,  -25.4,   31,   23, false, false ) )
+        ( twinrx_gain_config_t(     10,  -24.4,   31,   22, false, false ) )
+        ( twinrx_gain_config_t(     11,  -23.4,   31,   21, false, false ) )
+        ( twinrx_gain_config_t(     12,  -22.4,   31,   20, false, false ) )
+        ( twinrx_gain_config_t(     13,  -21.4,   31,   19, false, false ) )
+        ( twinrx_gain_config_t(     14,  -20.4,   31,   18, false, false ) )
+        ( twinrx_gain_config_t(     15,  -19.4,   31,   17, false, false ) )
+        ( twinrx_gain_config_t(     16,  -18.4,   31,   16, false, false ) )
+        ( twinrx_gain_config_t(     17,  -17.4,   31,   15, false, false ) )
+        ( twinrx_gain_config_t(     18,  -16.4,   31,   14, false, false ) )
+        ( twinrx_gain_config_t(     19,  -15.4,   31,   13, false, false ) )
+        ( twinrx_gain_config_t(     20,  -14.4,   31,   12, false, false ) )
+        ( twinrx_gain_config_t(     21,  -13.4,   31,   11, false, false ) )
+        ( twinrx_gain_config_t(     22,  -12.4,   31,   10, false, false ) )
+        ( twinrx_gain_config_t(     23,  -11.4,   31,    9, false, false ) )
+        ( twinrx_gain_config_t(     24,  -10.4,   31,    8, false, false ) )
+        ( twinrx_gain_config_t(     25,   -9.4,   31,    7, false, false ) )
+        ( twinrx_gain_config_t(     26,   -8.4,   31,    6, false, false ) )
+        ( twinrx_gain_config_t(     27,   -7.4,   31,    5, false, false ) )
+        ( twinrx_gain_config_t(     28,   -6.4,   31,    4, false, false ) )
+        ( twinrx_gain_config_t(     29,   -5.4,   31,    3, false, false ) )
+        ( twinrx_gain_config_t(     30,   -4.4,   31,    2, false, false ) )
+        ( twinrx_gain_config_t(     31,   -3.4,   31,    1, false, false ) )
+        ( twinrx_gain_config_t(     32,   -2.4,   31,    0, false, false ) )
+        ( twinrx_gain_config_t(     33,   -1.4,   30,    0, false, false ) )
+        ( twinrx_gain_config_t(     34,   -0.4,   29,    0, false, false ) )
+        ( twinrx_gain_config_t(     35,    0.6,   28,    0, false, false ) )
+        ( twinrx_gain_config_t(     36,    1.6,   27,    0, false, false ) )
+        ( twinrx_gain_config_t(     37,    2.6,   26,    0, false, false ) )
+        ( twinrx_gain_config_t(     38,    3.6,   25,    0, false, false ) )
+        ( twinrx_gain_config_t(     39,    4.6,   24,    0, false, false ) )
+        ( twinrx_gain_config_t(     40,    5.6,   23,    0, false, false ) )
+        ( twinrx_gain_config_t(     41,    6.6,   22,    0, false, false ) )
+        ( twinrx_gain_config_t(     42,    7.6,   21,    0, false, false ) )
+        ( twinrx_gain_config_t(     43,    8.6,   20,    0, false, false ) )
+        ( twinrx_gain_config_t(     44,    9.6,   19,    0, false, false ) )
+        ( twinrx_gain_config_t(     45,   10.6,   18,    0, false, false ) )
+        ( twinrx_gain_config_t(     46,   11.6,   17,    0, false, false ) )
+        ( twinrx_gain_config_t(     47,   12.6,   16,    0, false, false ) )
+        ( twinrx_gain_config_t(     48,   13.6,   15,    0, false, false ) )
+        ( twinrx_gain_config_t(     49,   14.6,   14,    0, false, false ) )
+        ( twinrx_gain_config_t(     50,   15.6,   13,    0, false, false ) )
+        ( twinrx_gain_config_t(     51,   16.6,   12,    0, false, false ) )
+        ( twinrx_gain_config_t(     52,   17.6,   11,    0, false, false ) )
+        ( twinrx_gain_config_t(     53,   18.6,   10,    0, false, false ) )
+        ( twinrx_gain_config_t(     54,   19.6,    9,    0, false, false ) )
+        ( twinrx_gain_config_t(     55,   20.6,    8,    0, false, false ) )
+        ( twinrx_gain_config_t(     56,   21.6,    7,    0, false, false ) )
+        ( twinrx_gain_config_t(     57,   22.6,    6,    0, false, false ) )
+        ( twinrx_gain_config_t(     58,   23.6,    5,    0, false, false ) )
+        ( twinrx_gain_config_t(     59,   24.6,    4,    0, false, false ) )
+        ( twinrx_gain_config_t(     60,   25.6,    3,    0, false, false ) )
+        ( twinrx_gain_config_t(     61,   26.6,    2,    0, false, false ) )
+        ( twinrx_gain_config_t(     62,   27.6,    1,    0, false, false ) )
+        ( twinrx_gain_config_t(     63,   28.6,    0,    0, false, false ) )
+        ( twinrx_gain_config_t(     64,   29.7,    5,    9, false,  true ) )
+        ( twinrx_gain_config_t(     65,   30.7,    4,    9, false,  true ) )
+        ( twinrx_gain_config_t(     66,   31.7,    3,    9, false,  true ) )
+        ( twinrx_gain_config_t(     67,   32.7,    2,    9, false,  true ) )
+        ( twinrx_gain_config_t(     68,   33.7,    1,    9, false,  true ) )
+        ( twinrx_gain_config_t(     69,   34.7,    0,    9, false,  true ) )
+        ( twinrx_gain_config_t(     70,   35.7,    0,    8, false,  true ) )
+        ( twinrx_gain_config_t(     71,   36.7,    7,    0,  true, false ) )
+        ( twinrx_gain_config_t(     72,   37.7,    6,    0,  true, false ) )
+        ( twinrx_gain_config_t(     73,   38.7,    5,    0,  true, false ) )
+        ( twinrx_gain_config_t(     74,   39.7,    4,    0,  true, false ) )
+        ( twinrx_gain_config_t(     75,   40.7,    3,    0,  true, false ) )
+        ( twinrx_gain_config_t(     76,   41.7,    2,    0,  true, false ) )
+        ( twinrx_gain_config_t(     77,   42.7,    1,    0,  true, false ) )
+        ( twinrx_gain_config_t(     78,   43.7,    0,    0,  true, false ) )
+        ( twinrx_gain_config_t(     79,   44.8,    6,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     80,   45.8,    5,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     81,   46.8,    4,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     82,   47.8,    4,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     83,   48.8,    3,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     84,   49.8,    2,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     85,   50.8,    1,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     86,   51.8,    1,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     87,   52.8,    0,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     88,   53.8,    0,    5,  true,  true ) )
+        ( twinrx_gain_config_t(     89,   54.8,    0,    4,  true,  true ) )
+        ( twinrx_gain_config_t(     90,   55.8,    0,    3,  true,  true ) )
+        ( twinrx_gain_config_t(     91,   56.8,    0,    2,  true,  true ) )
+        ( twinrx_gain_config_t(     92,   57.8,    0,    1,  true,  true ) )
+        ( twinrx_gain_config_t(     93,   58.8,    0,    0,  true,  true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> LOWBAND3_TABLE = boost::assign::list_of
+        //                       Index,   Gain, Atten1, Atten2,  Amp1,  Amp2
+        ( twinrx_gain_config_t(      0,  -34.0,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      1,  -34.0,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      2,  -34.0,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      3,  -33.0,   31,   30, false, false ) )
+        ( twinrx_gain_config_t(      4,  -32.0,   31,   29, false, false ) )
+        ( twinrx_gain_config_t(      5,  -31.0,   31,   28, false, false ) )
+        ( twinrx_gain_config_t(      6,  -30.0,   31,   27, false, false ) )
+        ( twinrx_gain_config_t(      7,  -29.0,   31,   26, false, false ) )
+        ( twinrx_gain_config_t(      8,  -28.0,   31,   25, false, false ) )
+        ( twinrx_gain_config_t(      9,  -27.0,   31,   24, false, false ) )
+        ( twinrx_gain_config_t(     10,  -26.0,   31,   23, false, false ) )
+        ( twinrx_gain_config_t(     11,  -25.0,   31,   22, false, false ) )
+        ( twinrx_gain_config_t(     12,  -24.0,   31,   21, false, false ) )
+        ( twinrx_gain_config_t(     13,  -23.0,   31,   20, false, false ) )
+        ( twinrx_gain_config_t(     14,  -22.0,   31,   19, false, false ) )
+        ( twinrx_gain_config_t(     15,  -21.0,   31,   18, false, false ) )
+        ( twinrx_gain_config_t(     16,  -20.0,   31,   17, false, false ) )
+        ( twinrx_gain_config_t(     17,  -19.0,   31,   16, false, false ) )
+        ( twinrx_gain_config_t(     18,  -18.0,   31,   15, false, false ) )
+        ( twinrx_gain_config_t(     19,  -17.0,   31,   14, false, false ) )
+        ( twinrx_gain_config_t(     20,  -16.0,   31,   13, false, false ) )
+        ( twinrx_gain_config_t(     21,  -15.0,   31,   12, false, false ) )
+        ( twinrx_gain_config_t(     22,  -14.0,   31,   11, false, false ) )
+        ( twinrx_gain_config_t(     23,  -13.0,   31,   10, false, false ) )
+        ( twinrx_gain_config_t(     24,  -12.0,   31,    9, false, false ) )
+        ( twinrx_gain_config_t(     25,  -11.0,   31,    8, false, false ) )
+        ( twinrx_gain_config_t(     26,  -10.0,   31,    7, false, false ) )
+        ( twinrx_gain_config_t(     27,   -9.0,   31,    6, false, false ) )
+        ( twinrx_gain_config_t(     28,   -8.0,   31,    5, false, false ) )
+        ( twinrx_gain_config_t(     29,   -7.0,   31,    4, false, false ) )
+        ( twinrx_gain_config_t(     30,   -6.0,   31,    3, false, false ) )
+        ( twinrx_gain_config_t(     31,   -5.0,   31,    2, false, false ) )
+        ( twinrx_gain_config_t(     32,   -4.0,   31,    1, false, false ) )
+        ( twinrx_gain_config_t(     33,   -3.0,   31,    0, false, false ) )
+        ( twinrx_gain_config_t(     34,   -2.0,   30,    0, false, false ) )
+        ( twinrx_gain_config_t(     35,   -1.0,   29,    0, false, false ) )
+        ( twinrx_gain_config_t(     36,   -0.0,   28,    0, false, false ) )
+        ( twinrx_gain_config_t(     37,    1.0,   27,    0, false, false ) )
+        ( twinrx_gain_config_t(     38,    2.0,   26,    0, false, false ) )
+        ( twinrx_gain_config_t(     39,    3.0,   25,    0, false, false ) )
+        ( twinrx_gain_config_t(     40,    4.0,   24,    0, false, false ) )
+        ( twinrx_gain_config_t(     41,    5.0,   23,    0, false, false ) )
+        ( twinrx_gain_config_t(     42,    6.0,   22,    0, false, false ) )
+        ( twinrx_gain_config_t(     43,    7.0,   21,    0, false, false ) )
+        ( twinrx_gain_config_t(     44,    8.0,   20,    0, false, false ) )
+        ( twinrx_gain_config_t(     45,    9.0,   19,    0, false, false ) )
+        ( twinrx_gain_config_t(     46,   10.0,   18,    0, false, false ) )
+        ( twinrx_gain_config_t(     47,   11.0,   17,    0, false, false ) )
+        ( twinrx_gain_config_t(     48,   12.0,   16,    0, false, false ) )
+        ( twinrx_gain_config_t(     49,   13.0,   15,    0, false, false ) )
+        ( twinrx_gain_config_t(     50,   14.0,   14,    0, false, false ) )
+        ( twinrx_gain_config_t(     51,   15.0,   13,    0, false, false ) )
+        ( twinrx_gain_config_t(     52,   16.0,   12,    0, false, false ) )
+        ( twinrx_gain_config_t(     53,   17.0,   11,    0, false, false ) )
+        ( twinrx_gain_config_t(     54,   18.0,   10,    0, false, false ) )
+        ( twinrx_gain_config_t(     55,   19.0,    9,    0, false, false ) )
+        ( twinrx_gain_config_t(     56,   20.0,    8,    0, false, false ) )
+        ( twinrx_gain_config_t(     57,   21.0,    7,    0, false, false ) )
+        ( twinrx_gain_config_t(     58,   22.0,    6,    0, false, false ) )
+        ( twinrx_gain_config_t(     59,   23.0,    5,    0, false, false ) )
+        ( twinrx_gain_config_t(     60,   24.0,    4,    0, false, false ) )
+        ( twinrx_gain_config_t(     61,   25.0,    3,    0, false, false ) )
+        ( twinrx_gain_config_t(     62,   26.0,    2,    0, false, false ) )
+        ( twinrx_gain_config_t(     63,   27.0,    1,    0, false, false ) )
+        ( twinrx_gain_config_t(     64,   28.0,    0,    0, false, false ) )
+        ( twinrx_gain_config_t(     65,   29.5,    5,    8, false,  true ) )
+        ( twinrx_gain_config_t(     66,   30.5,    4,    8, false,  true ) )
+        ( twinrx_gain_config_t(     67,   31.5,    3,    8, false,  true ) )
+        ( twinrx_gain_config_t(     68,   32.5,    2,    8, false,  true ) )
+        ( twinrx_gain_config_t(     69,   33.5,    1,    8, false,  true ) )
+        ( twinrx_gain_config_t(     70,   34.5,    0,    8, false,  true ) )
+        ( twinrx_gain_config_t(     71,   34.5,    0,    8, false,  true ) )
+        ( twinrx_gain_config_t(     72,   36.5,    6,    0,  true, false ) )
+        ( twinrx_gain_config_t(     73,   36.5,    6,    0,  true, false ) )
+        ( twinrx_gain_config_t(     74,   37.5,    5,    0,  true, false ) )
+        ( twinrx_gain_config_t(     75,   38.5,    4,    0,  true, false ) )
+        ( twinrx_gain_config_t(     76,   39.5,    3,    0,  true, false ) )
+        ( twinrx_gain_config_t(     77,   40.5,    2,    0,  true, false ) )
+        ( twinrx_gain_config_t(     78,   41.5,    1,    0,  true, false ) )
+        ( twinrx_gain_config_t(     79,   42.5,    0,    0,  true, false ) )
+        ( twinrx_gain_config_t(     80,   44.0,    6,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     81,   45.0,    5,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     82,   46.0,    4,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     83,   47.0,    3,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     84,   48.0,    3,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     85,   49.0,    2,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     86,   50.0,    1,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     87,   51.0,    0,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     88,   52.0,    0,    5,  true,  true ) )
+        ( twinrx_gain_config_t(     89,   53.0,    0,    4,  true,  true ) )
+        ( twinrx_gain_config_t(     90,   54.0,    0,    3,  true,  true ) )
+        ( twinrx_gain_config_t(     91,   55.0,    0,    2,  true,  true ) )
+        ( twinrx_gain_config_t(     92,   56.0,    0,    1,  true,  true ) )
+        ( twinrx_gain_config_t(     93,   57.0,    0,    0,  true,  true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> LOWBAND4_TABLE = boost::assign::list_of
+        //                       Index,   Gain, Atten1, Atten2,  Amp1,  Amp2
+        ( twinrx_gain_config_t(      0,  -32.8,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      1,  -32.8,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      2,  -32.8,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      3,  -32.8,   31,   31, false, false ) )
+        ( twinrx_gain_config_t(      4,  -31.8,   31,   30, false, false ) )
+        ( twinrx_gain_config_t(      5,  -30.8,   31,   29, false, false ) )
+        ( twinrx_gain_config_t(      6,  -29.8,   31,   28, false, false ) )
+        ( twinrx_gain_config_t(      7,  -28.8,   31,   27, false, false ) )
+        ( twinrx_gain_config_t(      8,  -27.8,   31,   26, false, false ) )
+        ( twinrx_gain_config_t(      9,  -26.8,   31,   25, false, false ) )
+        ( twinrx_gain_config_t(     10,  -25.8,   31,   24, false, false ) )
+        ( twinrx_gain_config_t(     11,  -24.8,   31,   23, false, false ) )
+        ( twinrx_gain_config_t(     12,  -23.8,   31,   22, false, false ) )
+        ( twinrx_gain_config_t(     13,  -22.8,   31,   21, false, false ) )
+        ( twinrx_gain_config_t(     14,  -21.8,   31,   20, false, false ) )
+        ( twinrx_gain_config_t(     15,  -20.8,   31,   19, false, false ) )
+        ( twinrx_gain_config_t(     16,  -19.8,   31,   18, false, false ) )
+        ( twinrx_gain_config_t(     17,  -18.8,   31,   17, false, false ) )
+        ( twinrx_gain_config_t(     18,  -17.8,   31,   16, false, false ) )
+        ( twinrx_gain_config_t(     19,  -16.8,   31,   15, false, false ) )
+        ( twinrx_gain_config_t(     20,  -15.8,   31,   14, false, false ) )
+        ( twinrx_gain_config_t(     21,  -14.8,   31,   13, false, false ) )
+        ( twinrx_gain_config_t(     22,  -13.8,   31,   12, false, false ) )
+        ( twinrx_gain_config_t(     23,  -12.8,   31,   11, false, false ) )
+        ( twinrx_gain_config_t(     24,  -11.8,   31,   10, false, false ) )
+        ( twinrx_gain_config_t(     25,  -10.8,   31,    9, false, false ) )
+        ( twinrx_gain_config_t(     26,   -9.8,   31,    8, false, false ) )
+        ( twinrx_gain_config_t(     27,   -8.8,   31,    7, false, false ) )
+        ( twinrx_gain_config_t(     28,   -7.8,   31,    6, false, false ) )
+        ( twinrx_gain_config_t(     29,   -6.8,   31,    5, false, false ) )
+        ( twinrx_gain_config_t(     30,   -5.8,   31,    4, false, false ) )
+        ( twinrx_gain_config_t(     31,   -4.8,   31,    3, false, false ) )
+        ( twinrx_gain_config_t(     32,   -3.8,   31,    2, false, false ) )
+        ( twinrx_gain_config_t(     33,   -2.8,   31,    1, false, false ) )
+        ( twinrx_gain_config_t(     34,   -1.8,   31,    0, false, false ) )
+        ( twinrx_gain_config_t(     35,   -0.8,   30,    0, false, false ) )
+        ( twinrx_gain_config_t(     36,    0.2,   29,    0, false, false ) )
+        ( twinrx_gain_config_t(     37,    1.2,   28,    0, false, false ) )
+        ( twinrx_gain_config_t(     38,    2.2,   27,    0, false, false ) )
+        ( twinrx_gain_config_t(     39,    3.2,   26,    0, false, false ) )
+        ( twinrx_gain_config_t(     40,    4.2,   25,    0, false, false ) )
+        ( twinrx_gain_config_t(     41,    5.2,   24,    0, false, false ) )
+        ( twinrx_gain_config_t(     42,    6.2,   23,    0, false, false ) )
+        ( twinrx_gain_config_t(     43,    7.2,   22,    0, false, false ) )
+        ( twinrx_gain_config_t(     44,    8.2,   21,    0, false, false ) )
+        ( twinrx_gain_config_t(     45,    9.2,   20,    0, false, false ) )
+        ( twinrx_gain_config_t(     46,   10.2,   19,    0, false, false ) )
+        ( twinrx_gain_config_t(     47,   11.2,   18,    0, false, false ) )
+        ( twinrx_gain_config_t(     48,   12.2,   17,    0, false, false ) )
+        ( twinrx_gain_config_t(     49,   13.2,   16,    0, false, false ) )
+        ( twinrx_gain_config_t(     50,   14.2,   15,    0, false, false ) )
+        ( twinrx_gain_config_t(     51,   15.2,   14,    0, false, false ) )
+        ( twinrx_gain_config_t(     52,   16.2,   13,    0, false, false ) )
+        ( twinrx_gain_config_t(     53,   17.2,   12,    0, false, false ) )
+        ( twinrx_gain_config_t(     54,   18.2,   11,    0, false, false ) )
+        ( twinrx_gain_config_t(     55,   19.2,   10,    0, false, false ) )
+        ( twinrx_gain_config_t(     56,   20.2,    9,    0, false, false ) )
+        ( twinrx_gain_config_t(     57,   21.2,    8,    0, false, false ) )
+        ( twinrx_gain_config_t(     58,   22.2,    7,    0, false, false ) )
+        ( twinrx_gain_config_t(     59,   23.2,    6,    0, false, false ) )
+        ( twinrx_gain_config_t(     60,   24.2,    5,    0, false, false ) )
+        ( twinrx_gain_config_t(     61,   25.2,    4,    0, false, false ) )
+        ( twinrx_gain_config_t(     62,   26.2,    3,    0, false, false ) )
+        ( twinrx_gain_config_t(     63,   27.2,    2,    0, false, false ) )
+        ( twinrx_gain_config_t(     64,   28.2,    1,    0, false, false ) )
+        ( twinrx_gain_config_t(     65,   29.2,    0,    0, false, false ) )
+        ( twinrx_gain_config_t(     66,   30.4,    4,    9, false,  true ) )
+        ( twinrx_gain_config_t(     67,   31.4,    3,    9, false,  true ) )
+        ( twinrx_gain_config_t(     68,   32.4,    2,    9, false,  true ) )
+        ( twinrx_gain_config_t(     69,   33.4,    1,    9, false,  true ) )
+        ( twinrx_gain_config_t(     70,   34.4,    0,    9, false,  true ) )
+        ( twinrx_gain_config_t(     71,   35.4,    8,    0,  true, false ) )
+        ( twinrx_gain_config_t(     72,   36.4,    7,    0,  true, false ) )
+        ( twinrx_gain_config_t(     73,   37.4,    6,    0,  true, false ) )
+        ( twinrx_gain_config_t(     74,   38.4,    5,    0,  true, false ) )
+        ( twinrx_gain_config_t(     75,   39.4,    4,    0,  true, false ) )
+        ( twinrx_gain_config_t(     76,   40.4,    3,    0,  true, false ) )
+        ( twinrx_gain_config_t(     77,   41.4,    2,    0,  true, false ) )
+        ( twinrx_gain_config_t(     78,   42.4,    1,    0,  true, false ) )
+        ( twinrx_gain_config_t(     79,   43.4,    0,    0,  true, false ) )
+        ( twinrx_gain_config_t(     80,   44.6,    4,    9,  true,  true ) )
+        ( twinrx_gain_config_t(     81,   45.6,    4,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     82,   46.6,    3,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     83,   47.6,    2,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     84,   48.6,    1,    8,  true,  true ) )
+        ( twinrx_gain_config_t(     85,   49.6,    1,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     86,   50.6,    0,    7,  true,  true ) )
+        ( twinrx_gain_config_t(     87,   51.6,    0,    6,  true,  true ) )
+        ( twinrx_gain_config_t(     88,   52.6,    0,    5,  true,  true ) )
+        ( twinrx_gain_config_t(     89,   53.6,    0,    4,  true,  true ) )
+        ( twinrx_gain_config_t(     90,   54.6,    0,    3,  true,  true ) )
+        ( twinrx_gain_config_t(     91,   55.6,    0,    2,  true,  true ) )
+        ( twinrx_gain_config_t(     92,   56.6,    0,    1,  true,  true ) )
+        ( twinrx_gain_config_t(     93,   57.6,    0,    0,  true,  true ) )
+;
+
+const twinrx_gain_table twinrx_gain_table::lookup_table
+(
+    twinrx_ctrl::signal_path_t signal_path,
+    twinrx_ctrl::preselector_path_t preselector_path,
+    std::string
+) {
+
+    if (signal_path == twinrx_ctrl::PATH_HIGHBAND) {
+        switch (preselector_path) {
+            case twinrx_ctrl::PRESEL_PATH1:
+                return twinrx_gain_table(HIGHBAND1_TABLE);
+            case twinrx_ctrl::PRESEL_PATH2:
+                return twinrx_gain_table(HIGHBAND2_TABLE);
+            case twinrx_ctrl::PRESEL_PATH3:
+                return twinrx_gain_table(HIGHBAND3_TABLE);
+            case twinrx_ctrl::PRESEL_PATH4:
+                return twinrx_gain_table(HIGHBAND4_TABLE);
+        }
+    } else {
+        switch (preselector_path) {
+            case twinrx_ctrl::PRESEL_PATH1:
+                return twinrx_gain_table(LOWBAND1_TABLE);
+            case twinrx_ctrl::PRESEL_PATH2:
+                return twinrx_gain_table(LOWBAND2_TABLE);
+            case twinrx_ctrl::PRESEL_PATH3:
+                return twinrx_gain_table(LOWBAND3_TABLE);
+            case twinrx_ctrl::PRESEL_PATH4:
+                return twinrx_gain_table(LOWBAND4_TABLE);
+        }
+    }
+    throw runtime_error("NO GAIN TABLE SELECTED");
+    return twinrx_gain_table(HIGHBAND1_TABLE);
+}
+
+const twinrx_gain_config_t& twinrx_gain_table::find_by_index(size_t index) const {
+    if (index >= get_num_entries()) throw uhd::value_error("invalid gain table index");
+    return _tbl.at(index);
+}
+
+uhd::gain_range_t twinrx_gain_table::get_gain_range() const {
+    double max = std::numeric_limits<double>::min();
+    double min = std::numeric_limits<double>::max();
+    for (size_t i = 0; i < get_num_entries(); i++) {
+        const twinrx_gain_config_t& config = find_by_index(i);
+        if (config.sys_gain > max) {
+            max = config.sys_gain;
+        }
+        if (config.sys_gain < min) {
+            min = config.sys_gain;
+        }
+    }
+    return uhd::gain_range_t(min, max, 1.0);
+}
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp
new file mode 100644
index 000000000..0148965da
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp
@@ -0,0 +1,83 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP
+#define INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP
+
+#include <uhd/config.hpp>
+#include <boost/cstdint.hpp>
+#include <uhd/types/ranges.hpp>
+#include "twinrx_ctrl.hpp"
+
+namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
+
+class twinrx_gain_config_t {
+public:
+    twinrx_gain_config_t(
+        size_t index_, double sys_gain_,
+        boost::uint8_t atten1_, boost::uint8_t atten2_,
+        bool amp1_, bool amp2_
+    ): index(index_), sys_gain(sys_gain_), atten1(atten1_), atten2(atten2_),
+       amp1(amp1_), amp2(amp2_)
+    {}
+
+    twinrx_gain_config_t& operator=(const twinrx_gain_config_t& src) {
+        if (this != &src) {
+            this->index = src.index;
+            this->sys_gain = src.sys_gain;
+            this->atten1 = src.atten1;
+            this->atten2 = src.atten2;
+            this->amp1 = src.amp1;
+            this->amp2 = src.amp2;
+        }
+        return *this;
+    }
+
+    size_t         index;
+    double         sys_gain;
+    boost::uint8_t atten1;
+    boost::uint8_t atten2;
+    bool           amp1;
+    bool           amp2;
+};
+
+class twinrx_gain_table {
+public:
+    static const twinrx_gain_table lookup_table(
+        twinrx_ctrl::signal_path_t signal_path,
+        twinrx_ctrl::preselector_path_t presel_path,
+        std::string profile);
+
+    twinrx_gain_table(const std::vector<twinrx_gain_config_t>& tbl)
+    : _tbl(tbl) {}
+
+    const twinrx_gain_config_t& find_by_index(size_t index) const;
+
+    inline size_t get_num_entries() const {
+        return _tbl.size();
+    }
+
+    uhd::gain_range_t get_gain_range() const;
+
+private:
+    const std::vector<twinrx_gain_config_t>& _tbl;
+};
+
+
+}}}} //namespaces
+
+#endif /* INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP */
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_io.hpp b/host/lib/usrp/dboard/twinrx/twinrx_io.hpp
new file mode 100644
index 000000000..8b504b549
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_io.hpp
@@ -0,0 +1,524 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_DBOARD_TWINRX_IO_HPP
+#define INCLUDED_DBOARD_TWINRX_IO_HPP
+
+#include <uhd/types/wb_iface.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/utils/soft_register.hpp>
+#include <boost/thread.hpp>
+#include "gpio_atr_3000.hpp"
+
+namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
+
+static const boost::uint32_t SET_ALL_BITS = 0xFFFFFFFF;
+
+namespace cpld {
+static wb_iface::wb_addr_type addr(boost::uint8_t cpld_num, boost::uint8_t cpld_addr) {
+    //Decode CPLD addressing for the following bitmap:
+    // {CPLD1_EN, CPLD2_EN, CPLD3_EN, CPLD4_EN, CPLD_ADDR[2:0]}
+    boost::uint8_t addr = 0;
+    switch (cpld_num) {
+        case 1: addr = 0x8 << 3; break;
+        case 2: addr = 0x4 << 3; break;
+        case 3: addr = 0x2 << 3; break;
+        case 4: addr = 0x1 << 3; break;
+        default: UHD_THROW_INVALID_CODE_PATH();
+    }
+    return static_cast<wb_iface::wb_addr_type>(addr | (cpld_addr & 0x7));
+}
+
+static boost::uint32_t get_reg(wb_iface::wb_addr_type addr) {
+    return static_cast<boost::uint32_t>(addr) & 0x7;
+}
+}
+
+class twinrx_gpio : public timed_wb_iface {
+public:
+    typedef boost::shared_ptr<twinrx_gpio> sptr;
+
+    //----------------------------------------------
+    //Public GPIO fields
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_CE_CH1,     /*width*/ 1, /*shift*/  0);     //GPIO[0]       OUT
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_CE_CH2,     /*width*/ 1, /*shift*/  1);     //GPIO[1]       OUT
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_MUXOUT_CH1, /*width*/ 1, /*shift*/  2);     //GPIO[2]       IN
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_MUXOUT_CH2, /*width*/ 1, /*shift*/  3);     //GPIO[3]       IN
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_LD_CH1,     /*width*/ 1, /*shift*/  4);     //GPIO[4]       IN
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_LD_CH2,     /*width*/ 1, /*shift*/  5);     //GPIO[5]       IN
+    // NO CONNECT                                                                   //GPIO[8:6]
+    // PRIVATE                                                                      //GPIO[15:9]
+    // NO CONNECT                                                                   //GPIO[16]
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_CE_CH1,     /*width*/ 1, /*shift*/ 17);     //GPIO[17]      OUT
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_CE_CH2,     /*width*/ 1, /*shift*/ 18);     //GPIO[18]      OUT
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_MUXOUT_CH1, /*width*/ 1, /*shift*/ 19);     //GPIO[19]      IN
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_MUXOUT_CH2, /*width*/ 1, /*shift*/ 20);     //GPIO[20]      IN
+    // NO CONNECT                                                                   //GPIO[21:23]
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_CLK,       /*width*/ 1, /*shift*/ 24);     //GPIO[24]      IN
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_PWR_GOOD,  /*width*/ 1, /*shift*/ 25);     //GPIO[25]      IN
+    UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_EN,        /*width*/ 1, /*shift*/ 26);     //GPIO[26]      OUT
+    // PRIVATE                                                                      //GPIO[27:31]
+    //----------------------------------------------
+
+    twinrx_gpio(dboard_iface::sptr iface) : _db_iface(iface) {
+        _db_iface->set_gpio_ddr(dboard_iface::UNIT_BOTH, GPIO_OUTPUT_MASK, SET_ALL_BITS);
+        _db_iface->set_pin_ctrl(dboard_iface::UNIT_BOTH, GPIO_PINCTRL_MASK, SET_ALL_BITS);
+        _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, 0, ~GPIO_PINCTRL_MASK);
+    }
+
+    ~twinrx_gpio() {
+        _db_iface->set_gpio_ddr(dboard_iface::UNIT_BOTH, ~GPIO_OUTPUT_MASK, SET_ALL_BITS);
+    }
+
+    void set_field(const uhd::soft_reg_field_t field, const boost::uint32_t value) {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        using namespace soft_reg_field;
+
+        _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH,
+            (value << shift(field)),
+            mask<boost::uint32_t>(field));
+    }
+
+    boost::uint32_t get_field(const uhd::soft_reg_field_t field) {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        using namespace soft_reg_field;
+        return (_db_iface->read_gpio(dboard_iface::UNIT_BOTH) & mask<boost::uint32_t>(field)) >> shift(field);
+    }
+
+    // CPLD register write-only interface
+    void poke32(const wb_addr_type addr, const boost::uint32_t data) {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        using namespace soft_reg_field;
+
+        //Step 1: Write the reg offset and data to the GPIO bus and de-assert all enables
+        _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH,
+            (cpld::get_reg(addr) << shift(CPLD_FULL_ADDR)) | (data << shift(CPLD_DATA)),
+            mask<boost::uint32_t>(CPLD_FULL_ADDR)|mask<boost::uint32_t>(CPLD_DATA));
+        //Sleep for 166ns to ensure that we don't toggle the enables to quickly
+        //nanosleep is not really accurate in userland and it is also not very
+        //cross-platform. So just sleep for the minimum amount of time in us.
+        boost::this_thread::sleep(boost::posix_time::microseconds(0));
+        //Step 2: Write the reg offset and data, and assert the necessary enable
+        _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH,
+            (static_cast<boost::uint32_t>(addr) << shift(CPLD_FULL_ADDR)) | (data << shift(CPLD_DATA)),
+            mask<boost::uint32_t>(CPLD_FULL_ADDR)|mask<boost::uint32_t>(CPLD_DATA));
+    }
+
+    // Timed command interface
+    inline time_spec_t get_time() {
+        return _db_iface->get_command_time();
+    }
+
+    void set_time(const time_spec_t& t) {
+        boost::lock_guard<boost::mutex> lock(_mutex);
+        _db_iface->set_command_time(t);
+    }
+
+private:    //Members/definitions
+    static const boost::uint32_t GPIO_OUTPUT_MASK   = 0xFC06FE03;
+    static const boost::uint32_t GPIO_PINCTRL_MASK  = 0x00000000;
+
+    //Private GPIO fields
+    UHD_DEFINE_SOFT_REG_FIELD(CPLD_FULL_ADDR, /*width*/ 7, /*shift*/  9);     //GPIO[15:9]
+    UHD_DEFINE_SOFT_REG_FIELD(CPLD_DATA,      /*width*/ 5, /*shift*/ 27);     //GPIO[31:27]
+
+    //Members
+    dboard_iface::sptr  _db_iface;
+    boost::mutex        _mutex;
+};
+
+class twinrx_cpld_regmap : public uhd::soft_regmap_t {
+public:
+    typedef boost::shared_ptr<twinrx_cpld_regmap> sptr;
+
+    //----------------------------------------------
+    // IF CCA: CPLD 1
+    //----------------------------------------------
+    class if0_reg0_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_IF1_EN_CH1,    /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_LO2_EN_CH1,       /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_LO2_EN_CH2,       /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(SW19_CTRL_CH2,        /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(SW20_CTRL_CH2,        /*width*/ 1, /*shift*/ 4);
+
+        if0_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 0), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } if0_reg0;
+
+    class if0_reg1_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW20_CTRL_CH1,        /*width*/ 1, /*shift*/ 2);
+
+        if0_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 1), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } if0_reg1;
+
+    class if0_reg2_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_IF1_EN_CH2,    /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_IF1_EN_CH1,    /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(LO2_LE_CH1,           /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(LO2_LE_CH2,           /*width*/ 1, /*shift*/ 4);
+
+        if0_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 2), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } if0_reg2;
+
+    class if0_reg3_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW24_CTRL_CH1,        /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW13_CTRL_CH1,        /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(IF1_IF2_EN_CH1,       /*width*/ 1, /*shift*/ 3);
+
+        if0_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 3), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } if0_reg3;
+
+    class if0_reg4_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW21_CTRL_CH2,        /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW25_CTRL,            /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(IF1_IF2_EN_CH2,       /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(SW19_CTRL_CH1,        /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(SW21_CTRL_CH1,        /*width*/ 1, /*shift*/ 4);
+
+        if0_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 4), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } if0_reg4;
+
+    class if0_reg6_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_IF1_EN_CH2,    /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW13_CTRL_CH2,        /*width*/ 1, /*shift*/ 2);
+
+        if0_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 6), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } if0_reg6;
+
+    class if0_reg7_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW24_CTRL_CH2,        /*width*/ 1, /*shift*/ 0);
+
+        if0_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 7), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } if0_reg7;
+
+    //----------------------------------------------
+    // RF CCA: CPLD 2
+    //----------------------------------------------
+    class rf0_reg0_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(ATTEN_IN_CH1,         /*width*/ 5, /*shift*/ 0);
+
+        rf0_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 0), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf0_reg0;
+
+    class rf0_reg1_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SWPA1_CTL_CH1,        /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(HB_PREAMP_EN_CH1,     /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(LB_PREAMP_EN_CH1,     /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(SWPA3_CTRL_CH2,       /*width*/ 1, /*shift*/ 4);
+
+        rf0_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 1), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf0_reg1;
+
+    class rf0_reg2_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW6_CTRL_CH1,         /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW5_CTRL_CH1,         /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW4_CTRL_CH1,         /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(LO1_LE_CH1,           /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(LO1_LE_CH2,           /*width*/ 1, /*shift*/ 4);
+
+        rf0_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 2), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf0_reg2;
+
+    class rf0_reg3_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW9_CTRL_CH2,         /*width*/ 2, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW7_CTRL_CH1,         /*width*/ 2, /*shift*/ 2);
+
+        rf0_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 3), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf0_reg3;
+
+    class rf0_reg4_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(ATTEN_IN_CH2,         /*width*/ 5, /*shift*/ 0);
+
+        rf0_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 4), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf0_reg4;
+
+    class rf0_reg5_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW9_CTRL_CH1,         /*width*/ 2, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(HB_PREAMP_EN_CH2,     /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(SW3_CTRL_CH1,         /*width*/ 1, /*shift*/ 4);
+
+        rf0_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 5), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf0_reg5;
+
+    class rf0_reg6_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW6_CTRL_CH2,         /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW5_CTRL_CH2,         /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW4_CTRL_CH2,         /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(SWPA4_CTRL_CH2,       /*width*/ 1, /*shift*/ 4);
+
+        rf0_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 6), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf0_reg6;
+
+    class rf0_reg7_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SWPA1_CTRL_CH2,       /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SWPA3_CTRL_CH1,       /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW3_CTRL_CH2,         /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(SW7_CTRL_CH2,         /*width*/ 2, /*shift*/ 3);
+
+        rf0_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 7), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf0_reg7;
+
+    //----------------------------------------------
+    // RF CCA: CPLD 3
+    //----------------------------------------------
+    class rf1_reg0_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(ATTEN_HB_CH1,         /*width*/ 5, /*shift*/ 0);
+
+        rf1_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 0), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf1_reg0;
+
+    class rf1_reg1_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW17_CTRL_CH1,        /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_LO1_EN_CH1,       /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW16_CTRL_CH1,        /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(SW15_CTRL_CH1,        /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(SW14_CTRL_CH1,        /*width*/ 1, /*shift*/ 4);
+
+        rf1_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 1), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf1_reg1;
+
+    class rf1_reg2_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW12_CTRL_CH1,        /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_EN_CH1,        /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(HB_PRESEL_PGA_EN_CH2, /*width*/ 1, /*shift*/ 2);
+
+        rf1_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 2), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf1_reg2;
+
+    class rf1_reg3_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW23_CTRL,            /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW22_CTRL_CH1,        /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW10_CTRL_CH1,        /*width*/ 2, /*shift*/ 2);
+
+        rf1_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 3), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf1_reg3;
+
+    class rf1_reg4_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(ATTEN_HB_CH2,         /*width*/ 5, /*shift*/ 0);
+
+        rf1_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 4), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf1_reg4;
+
+    class rf1_reg5_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_LO1_EN_CH2,       /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW15_CTRL_CH2,        /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW14_CTRL_CH2,        /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(SW18_CTRL_CH1,        /*width*/ 1, /*shift*/ 4);
+
+        rf1_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 5), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf1_reg5;
+
+    class rf1_reg6_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(HB_PRESEL_PGA_EN_CH1, /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW17_CTRL_CH2,        /*width*/ 1, /*shift*/ 2);
+        UHD_DEFINE_SOFT_REG_FIELD(SW16_CTRL_CH2,        /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(PREAMP2_EN_CH2,       /*width*/ 1, /*shift*/ 4);
+
+        rf1_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 6), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf1_reg6;
+
+    class rf1_reg7_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW22_CTRL_CH2,        /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW10_CTRL_CH2,        /*width*/ 2, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW12_CTRL_CH2,        /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_EN_CH2,        /*width*/ 1, /*shift*/ 4);
+
+        rf1_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 7), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf1_reg7;
+
+    //----------------------------------------------
+    // RF CCA: CPLD 4
+    //----------------------------------------------
+    class rf2_reg0_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(ATTEN_LB_CH1,         /*width*/ 5, /*shift*/ 0);
+
+        rf2_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 0), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf2_reg0;
+
+    class rf2_reg2_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SW11_CTRL_CH1,        /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_EN_CH1,        /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SWPA2_CTRL_CH1,       /*width*/ 1, /*shift*/ 2);
+
+        rf2_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 2), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf2_reg2;
+
+    class rf2_reg3_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(PREAMP2_EN_CH1,       /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW18_CTRL_CH2,        /*width*/ 1, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW8_CTRL_CH1,         /*width*/ 2, /*shift*/ 2);
+
+        rf2_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 3), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf2_reg3;
+
+    class rf2_reg4_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(ATTEN_LB_CH2,         /*width*/ 5, /*shift*/ 0);
+
+        rf2_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 4), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf2_reg4;
+
+    class rf2_reg5_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SWPA2_CTRL_CH2,       /*width*/ 1, /*shift*/ 0);
+
+        rf2_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 5), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf2_reg5;
+
+    class rf2_reg6_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(LB_PREAMP_EN_CH2,     /*width*/ 1, /*shift*/ 0);
+
+        rf2_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 6), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf2_reg6;
+
+    class rf2_reg7_t : public uhd::soft_reg32_wo_t {
+    public:
+        UHD_DEFINE_SOFT_REG_FIELD(SWPA4_CTRL_CH1,       /*width*/ 1, /*shift*/ 0);
+        UHD_DEFINE_SOFT_REG_FIELD(SW8_CTRL_CH2,         /*width*/ 2, /*shift*/ 1);
+        UHD_DEFINE_SOFT_REG_FIELD(SW11_CTRL_CH2,        /*width*/ 1, /*shift*/ 3);
+        UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_EN_CH2,        /*width*/ 1, /*shift*/ 4);
+
+        rf2_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 7), OPTIMIZED_FLUSH) {
+            set(REGISTER, 0);
+        }
+    } rf2_reg7;
+
+    twinrx_cpld_regmap() : soft_regmap_t("twinrx_cpld") {
+        // IF CCA: CPLD 1
+        add_to_map(if0_reg0, "if0_reg0");
+        add_to_map(if0_reg1, "if0_reg1");
+        add_to_map(if0_reg2, "if0_reg2");
+        add_to_map(if0_reg3, "if0_reg3");
+        add_to_map(if0_reg4, "if0_reg4");
+        add_to_map(if0_reg6, "if0_reg6");
+        add_to_map(if0_reg7, "if0_reg7");
+        // RF CCA: CPLD 2
+        add_to_map(rf0_reg0, "rf0_reg0");
+        add_to_map(rf0_reg1, "rf0_reg1");
+        add_to_map(rf0_reg2, "rf0_reg2");
+        add_to_map(rf0_reg3, "rf0_reg3");
+        add_to_map(rf0_reg4, "rf0_reg4");
+        add_to_map(rf0_reg5, "rf0_reg5");
+        add_to_map(rf0_reg6, "rf0_reg6");
+        add_to_map(rf0_reg7, "rf0_reg7");
+        // RF CCA: CPLD 3
+        add_to_map(rf1_reg0, "rf1_reg0");
+        add_to_map(rf1_reg1, "rf1_reg1");
+        add_to_map(rf1_reg2, "rf1_reg2");
+        add_to_map(rf1_reg3, "rf1_reg3");
+        add_to_map(rf1_reg4, "rf1_reg4");
+        add_to_map(rf1_reg5, "rf1_reg5");
+        add_to_map(rf1_reg6, "rf1_reg6");
+        add_to_map(rf1_reg7, "rf1_reg7");
+        // RF CCA: CPLD 4
+        add_to_map(rf2_reg0, "rf2_reg0");
+        add_to_map(rf2_reg2, "rf2_reg2");
+        add_to_map(rf2_reg3, "rf2_reg3");
+        add_to_map(rf2_reg4, "rf2_reg4");
+        add_to_map(rf2_reg5, "rf2_reg5");
+        add_to_map(rf2_reg6, "rf2_reg6");
+        add_to_map(rf2_reg7, "rf2_reg7");
+    }
+};
+
+}}}} //namespaces
+
+#endif /* INCLUDED_DBOARD_TWINRX_IO_HPP */
-- 
cgit v1.2.3