From 4dc90d5a1e03597799f41e47f030e4bcddb7d57e Mon Sep 17 00:00:00 2001
From: michael-west <michael.west@ettus.com>
Date: Wed, 21 Jan 2015 16:44:11 -0800
Subject: UBX: Add UBX Support

---
 host/lib/ic_reg_maps/CMakeLists.txt      |    5 +
 host/lib/ic_reg_maps/gen_max2871_regs.py |  146 +++
 host/lib/usrp/dboard/CMakeLists.txt      |    1 +
 host/lib/usrp/dboard/db_ubx.cpp          | 1434 ++++++++++++++++++++++++++++++
 4 files changed, 1586 insertions(+)
 create mode 100644 host/lib/ic_reg_maps/gen_max2871_regs.py
 create mode 100644 host/lib/usrp/dboard/db_ubx.cpp

(limited to 'host/lib')

diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt
index c810c04ad..1de50579f 100644
--- a/host/lib/ic_reg_maps/CMakeLists.txt
+++ b/host/lib/ic_reg_maps/CMakeLists.txt
@@ -37,6 +37,11 @@ LIBUHD_PYTHON_GEN_SOURCE(
     ${CMAKE_CURRENT_BINARY_DIR}/max2870_regs.hpp
 )
 
+LIBUHD_PYTHON_GEN_SOURCE(
+    ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2871_regs.py
+    ${CMAKE_CURRENT_BINARY_DIR}/max2871_regs.hpp
+)
+
 LIBUHD_PYTHON_GEN_SOURCE(
     ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf4360_regs.py
     ${CMAKE_CURRENT_BINARY_DIR}/adf4360_regs.hpp
diff --git a/host/lib/ic_reg_maps/gen_max2871_regs.py b/host/lib/ic_reg_maps/gen_max2871_regs.py
new file mode 100644
index 000000000..338a019d8
--- /dev/null
+++ b/host/lib/ic_reg_maps/gen_max2871_regs.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 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/>.
+#
+
+########################################################################
+# Template for raw text data describing registers
+# name addr[bit range inclusive] default optional enums
+########################################################################
+
+REGS_TMPL="""\
+########################################################################
+## Address 0x00
+## Divider control
+## Write-only, default = 0x007D0000
+########################################################################
+int_n_mode              0x00[31]        0       frac_n, int_n
+int_16_bit              0x00[15:30]     0x007D  ##Integer divider: 16-65535 in int-N mode, 19-4091 in frac-N mode.
+frac_12_bit             0x00[3:14]      0       ##Frac divider: 0-4095
+########################################################################
+## Address 0x01
+## Charge pump control
+## Write-only, default = 0x2000FFF9
+########################################################################
+res1                    0x01[31]        0
+cpl                     0x01[29:30]     1       disabled, enabled, res1, res2
+cpt                     0x01[27:28]     0       normal, reserved, force_source, force_sink
+phase_12_bit            0x01[15:26]     1       ##sets phase shift
+mod_12_bit              0x01[3:14]      0xFFF   ##VCO frac modulus
+########################################################################
+## Address 0x02
+## Misc. control
+## Write-only, default = 0x00004042
+########################################################################
+lds                     0x02[31]        0       slow, fast
+low_noise_and_spur      0x02[29:30]     3       low_noise, reserved, low_spur_1, low_spur_2
+muxout                  0x02[26:28]     0x6     tri_state, high, low, rdiv, ndiv, ald, dld, sync, res8, res9, res10, res11, spi, res13, res14, res15
+reference_doubler       0x02[25]        0       disabled, enabled
+reference_divide_by_2   0x02[24]        0       disabled, enabled
+r_counter_10_bit        0x02[14:23]     1       ##R divider value, 1-1023
+double_buffer           0x02[13]        0       disabled, enabled
+#set $current_setting_enums = ', '.join(map(lambda x: '_'.join(("%0.2fma"%(1.631/5.1 * (1.+x))).split('.')), range(0,16)))
+charge_pump_current     0x02[9:12]      7       $current_setting_enums
+ldf                     0x02[8]         0       frac_n, int_n
+ldp                     0x02[7]         0       10ns, 6ns
+pd_polarity             0x02[6]         1       negative, positive
+power_down              0x02[5]         0       normal, shutdown
+cp_three_state          0x02[4]         0       disabled, enabled
+counter_reset           0x02[3]         0       normal, reset
+########################################################################
+## Address 0x03
+## VCO control
+## Write-only, default = 0x0000000B
+########################################################################
+vco                     0x03[26:31]     0       ##VCO subband selection, used when VAS disabledd
+shutdown_vas            0x03[25]        0       enabled, disabled ##VCO autoselect
+retune                  0x03[24]        1       disabled, enabled
+res3                    0x3[19:23]      0
+csm                     0x3[18]         0       disabled, enabled
+mutedel                 0x3[17]         0       disabled, enabled
+clock_div_mode          0x03[15:16]     0       clock_divider_off, fast_lock, phase, reserved
+clock_divider_12_bit    0x03[3:14]      1       ##clock divider, 1-4095
+########################################################################
+## Address 0x04
+## RF output control
+## Write-only, default = 0x6180B23C
+########################################################################
+res4                    0x04[29:31]     0x3
+shutdown_ldo            0x04[28]        0       enabled, disabled
+shutdown_div            0x04[27]        0       enabled, disabled
+shutdown_ref            0x04[26]        0       enabled, disabled
+bs_msb                  0x04[24:25]     0       ##Band select MSBs
+feedback_select         0x04[23]        1       divided, fundamental
+rf_divider_select       0x04[20:22]     0       div1, div2, div4, div8, div16, div32, div64, div128
+band_select_clock_div   0x04[12:19]     0
+shutdown_vco            0x04[11]        0       enabled, disabled
+mute_lock_detect        0x04[10]        0       enabled, disabled
+aux_output_select       0x04[9]         1       divided, fundamental
+aux_output_enable       0x04[8]         0       disabled, enabled
+aux_output_power        0x04[6:7]       0       m4dBm, m1dBm, 2dBm, 5dBm
+rf_output_enable        0x04[5]         1       disabled, enabled
+output_power            0x04[3:4]       3       m4dBm, m1dBm, 2dBm, 5dBm
+########################################################################
+## Address 0x05
+## Misc
+## Write only, default = 0x18400005
+########################################################################
+res5_26_31              0x05[26:31]     0x18
+shutdown_pll            0x05[25]        0       enabled, disabled
+f01                     0x05[24]        1       frac_n, auto
+ld_pin_mode             0x05[22:23]     1       low, dld, ald, high
+mux_sdo                 0x05[18]        0       normal, sdo
+res5_7_17               0x05[7:17]      0
+adc_start               0x05[6]         0       normal, start_conversion
+adc_mode                0x05[2:0]       0       disabled, temp_sensor, res2, res3, tune_pin, res5, res6, res7
+"""
+
+########################################################################
+# Template for methods in the body of the struct
+########################################################################
+BODY_TMPL="""\
+enum addr_t{
+    ADDR_R0 = 0,
+    ADDR_R1 = 1,
+    ADDR_R2 = 2,
+    ADDR_R3 = 3,
+    ADDR_R4 = 4,
+    ADDR_R5 = 5
+};
+
+boost::uint32_t get_reg(boost::uint8_t addr){
+    boost::uint32_t reg = addr & 0x7;
+    switch(addr){
+    #for $addr in range(5+1)
+    case $addr:
+        #for $reg in filter(lambda r: r.get_addr() == addr, $regs)
+        reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift();
+        #end for
+        break;
+    #end for
+    }
+    return reg;
+}
+"""
+
+if __name__ == '__main__':
+    import common; common.generate(
+        name='max2871_regs',
+        regs_tmpl=REGS_TMPL,
+        body_tmpl=BODY_TMPL,
+        file=__file__,
+    )
+
diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt
index 3c5bb4fa8..6cebecdbf 100644
--- a/host/lib/usrp/dboard/CMakeLists.txt
+++ b/host/lib/usrp/dboard/CMakeLists.txt
@@ -27,6 +27,7 @@ LIBUHD_APPEND_SOURCES(
     ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_version3.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_version4.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/db_cbx.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/db_ubx.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_common.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version2.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version3.cpp
diff --git a/host/lib/usrp/dboard/db_ubx.cpp b/host/lib/usrp/dboard/db_ubx.cpp
new file mode 100644
index 000000000..dd30cf534
--- /dev/null
+++ b/host/lib/usrp/dboard/db_ubx.cpp
@@ -0,0 +1,1434 @@
+//
+// 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/>.
+//
+
+/***********************************************************************
+ * Included Files and Libraries
+ **********************************************************************/
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/dict.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/assert_has.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/static.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <boost/thread.hpp>
+#include <boost/algorithm/string.hpp>
+#include <map>
+
+using namespace uhd;
+using namespace uhd::usrp;
+
+#define fMHz (1000000.0)
+#define UBX_PROTO_V3_TX_ID   0x73
+#define UBX_PROTO_V3_RX_ID   0x74
+#define UBX_PROTO_V4_TX_ID   0x75
+#define UBX_PROTO_V4_RX_ID   0x76
+#define UBX_V1_40MHZ_TX_ID   0x77
+#define UBX_V1_40MHZ_RX_ID   0x78
+#define UBX_V1_160MHZ_TX_ID  0x79
+#define UBX_V1_160MHZ_RX_ID  0x7a
+
+/***********************************************************************
+ * UBX Synthesizers
+ **********************************************************************/
+#include "max2870_regs.hpp"
+#include "max2871_regs.hpp"
+
+typedef boost::function<void(std::vector<boost::uint32_t>)> max287x_write_fn;
+
+class max287x_synthesizer_iface
+{
+public:
+    virtual bool is_shutdown(void) = 0;
+    virtual void shutdown(void) = 0;
+    virtual void power_up(void) = 0;
+    virtual double set_freq_and_power(double target_freq, double ref_freq, bool is_int_n, int output_power) = 0;
+};
+
+class max287x : public max287x_synthesizer_iface
+{
+public:
+    max287x(max287x_write_fn write_fn) : _write_fn(write_fn) {};
+    virtual ~max287x() {};
+
+protected:
+    virtual std::set<boost::uint32_t> get_changed_addrs(void) = 0;
+    virtual boost::uint32_t get_reg(boost::uint32_t addr) = 0;
+    virtual void save_state(void) = 0;
+
+    void write_regs(void)
+    {
+        std::vector<boost::uint32_t> regs;
+        std::set<boost::uint32_t> changed_regs;
+
+        // Get only regs with changes
+        try {
+            changed_regs = get_changed_addrs();
+        } catch (uhd::runtime_error& e) {
+            // No saved state - write all regs
+            for (int addr = 5; addr >= 0; addr--)
+                changed_regs.insert(boost::uint32_t(addr));
+        }
+
+        for (int addr = 5; addr >= 0; addr--)
+        {
+            if (changed_regs.find(boost::uint32_t(addr)) != changed_regs.end())
+                regs.push_back(get_reg(boost::uint32_t(addr)));
+        }
+
+        // writing reg 0 initiates VCO auto select, so this makes sure it is written
+        if (changed_regs.size() and changed_regs.find(0) == changed_regs.end())
+            regs.push_back(get_reg(0));
+
+        _write_fn(regs);
+        save_state();
+    }
+
+    double calculate_freq_settings(
+        double target_freq,
+        double ref_freq,
+        double target_pfd_freq,
+        bool is_int_n,
+        double &pfd_freq,
+        int& T,
+        int& D,
+        int& R,
+        int& BS,
+        int& N,
+        int& FRAC,
+        int& MOD,
+        int& RFdiv)
+    {
+        //map mode setting to valid integer divider (N) values
+        static const uhd::range_t int_n_mode_div_range(16,4095,1);
+        static const uhd::range_t frac_n_mode_div_range(19,4091,1);
+
+        double actual_freq = 0.0;
+
+        T = 0;
+        D = ref_freq <= 10.0e6 ? 1 : 0;
+        R = 0;
+        BS = 0;
+        N = 0;
+        FRAC = 0;
+        MOD = 4095;
+        RFdiv = 1;
+
+        //increase RF divider until acceptable VCO frequency (MIN freq for MAX287x VCO is 3GHz)
+        double vco_freq = target_freq;
+        while (vco_freq < 3e9)
+        {
+            vco_freq *= 2;
+            RFdiv *= 2;
+        }
+
+        /*
+         * The goal here is to loop though possible R dividers,
+         * band select clock dividers, N (int) dividers, and FRAC
+         * (frac) dividers.
+         *
+         * Calculate the N and F dividers for each set of values.
+         * The loop exits when it meets all of the constraints.
+         * The resulting loop values are loaded into the registers.
+         *
+         * f_pfd = f_ref*(1+D)/(R*(1+T))
+         * f_vco = (N + (FRAC/MOD))*f_pfd
+         *     N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD
+         * f_rf  = f_vco/RFdiv
+         */
+        for(R = int(ref_freq*(1+D)/(target_pfd_freq*(1+T))); R <= 1023; R++)
+        {
+            //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T)
+            pfd_freq = ref_freq*(1+D)/(R*(1+T));
+
+            //keep the PFD frequency at or below target
+            if (pfd_freq > target_pfd_freq)
+                continue;
+
+            //ignore fractional part of tuning
+            N = int(vco_freq/pfd_freq);
+
+            //Fractional-N calculation
+            FRAC = int(boost::math::round((vco_freq/pfd_freq - N)*MOD));
+
+            if(is_int_n)
+            {
+                if (FRAC > (MOD / 2)) //Round integer such that actual freq is closest to target
+                    N++;
+                FRAC = 0;
+            }
+
+            //keep N within int divider requirements
+            if(is_int_n)
+            {
+                if(N < int_n_mode_div_range.start()) continue;
+                if(N > int_n_mode_div_range.stop()) continue;
+            }
+            else
+            {
+                if(N < frac_n_mode_div_range.start()) continue;
+                if(N > frac_n_mode_div_range.stop()) continue;
+            }
+
+            //keep pfd freq low enough to achieve 50kHz BS clock
+            BS = std::ceil(pfd_freq / 50e3);
+            if(BS <= 1023) break;
+        }
+        UHD_ASSERT_THROW(R <= 1023);
+
+        //Reference divide-by-2 for 50% duty cycle
+        // if R even, move one divide by 2 to to regs.reference_divide_by_2
+        if(R % 2 == 0)
+        {
+            T = 1;
+            R /= 2;
+        }
+
+        //actual frequency calculation
+        actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv);
+
+        UHD_LOGV(rarely)
+            << boost::format("MAX287x: Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f"
+                ) % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl
+            << boost::format("MAX287x: tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, type=%s"
+                ) % R % BS % N % FRAC % MOD % T % D % RFdiv % ((is_int_n) ? "Integer-N" : "Fractional") << std::endl
+            << boost::format("MAX287x: Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f"
+                ) % (pfd_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl;
+
+        return actual_freq;
+    }
+
+    max287x_write_fn _write_fn;
+};
+
+class max2870 : public max287x
+{
+public:
+    max2870(max287x_write_fn write_fn) : max287x(write_fn), _first_tune(true)
+    {
+        // initialize register values (override defaults)
+        _regs.retune = max2870_regs_t::RETUNE_DISABLED;
+        _regs.clock_div_mode = max2870_regs_t::CLOCK_DIV_MODE_FAST_LOCK;
+
+        // MAX2870 data sheet says that all registers must be written twice
+        // with at least a 20ms delay between writes upon power up.  One
+        // write and a 20ms wait are done in power_up().  The second write
+        // is done when any other function that does a write to the registers
+        // is called.  To ensure all registers are written the second time, the
+        // state of the registers is not saved during the first write.
+        _save_state = false;
+        power_up();
+        _save_state = true;
+    };
+
+    ~max2870()
+    {
+        shutdown();
+    };
+
+    bool is_shutdown(void)
+    {
+        return (_regs.power_down == max2870_regs_t::POWER_DOWN_SHUTDOWN);
+    };
+
+    void shutdown(void)
+    {
+        _regs.rf_output_enable = max2870_regs_t::RF_OUTPUT_ENABLE_DISABLED;
+        _regs.aux_output_enable = max2870_regs_t::AUX_OUTPUT_ENABLE_DISABLED;
+        _regs.power_down = max2870_regs_t::POWER_DOWN_SHUTDOWN;
+        _regs.muxout = max2870_regs_t::MUXOUT_LOW;
+        _regs.ld_pin_mode = max2870_regs_t::LD_PIN_MODE_LOW;
+        write_regs();
+    };
+
+    void power_up(void)
+    {
+        _regs.muxout = max2870_regs_t::MUXOUT_DLD;
+        _regs.ld_pin_mode = max2870_regs_t::LD_PIN_MODE_DLD;
+        _regs.power_down = max2870_regs_t::POWER_DOWN_NORMAL;
+        write_regs();
+
+        // MAX270 data sheet says to wait at least 20 ms after exiting low power mode
+        // before programming final VCO frequency
+        boost::this_thread::sleep(boost::posix_time::milliseconds(20));
+
+        _first_tune = true;
+    };
+
+    double set_freq_and_power(double target_freq, double ref_freq, bool is_int_n, int output_power)
+    {
+        //map rf divider select output dividers to enums
+        static const uhd::dict<int, max2870_regs_t::rf_divider_select_t> rfdivsel_to_enum =
+            boost::assign::map_list_of
+            (1,   max2870_regs_t::RF_DIVIDER_SELECT_DIV1)
+            (2,   max2870_regs_t::RF_DIVIDER_SELECT_DIV2)
+            (4,   max2870_regs_t::RF_DIVIDER_SELECT_DIV4)
+            (8,   max2870_regs_t::RF_DIVIDER_SELECT_DIV8)
+            (16,  max2870_regs_t::RF_DIVIDER_SELECT_DIV16)
+            (32,  max2870_regs_t::RF_DIVIDER_SELECT_DIV32)
+            (64,  max2870_regs_t::RF_DIVIDER_SELECT_DIV64)
+            (128, max2870_regs_t::RF_DIVIDER_SELECT_DIV128);
+
+        int T = 0;
+        int D = ref_freq <= 10.0e6 ? 1 : 0;
+        int R, BS, N, FRAC, MOD, RFdiv;
+        double pfd_freq = 25e6;
+
+        double actual_freq = calculate_freq_settings(
+            target_freq, ref_freq, 25e6, is_int_n, pfd_freq, T, D, R, BS, N, FRAC, MOD, RFdiv);
+
+        //load the register values
+        _regs.rf_output_enable = max2870_regs_t::RF_OUTPUT_ENABLE_ENABLED;
+
+        if(is_int_n) {
+            _regs.cpl = max2870_regs_t::CPL_DISABLED;
+            _regs.ldf = max2870_regs_t::LDF_INT_N;
+            _regs.cpoc = max2870_regs_t::CPOC_ENABLED;
+            _regs.int_n_mode = max2870_regs_t::INT_N_MODE_INT_N;
+        } else {
+            _regs.cpl = max2870_regs_t::CPL_ENABLED;
+            _regs.ldf = max2870_regs_t::LDF_FRAC_N;
+            _regs.cpoc = max2870_regs_t::CPOC_DISABLED;
+            _regs.int_n_mode = max2870_regs_t::INT_N_MODE_FRAC_N;
+        }
+
+        _regs.lds = pfd_freq <= 32e6 ? max2870_regs_t::LDS_SLOW : max2870_regs_t::LDS_FAST;
+
+        _regs.frac_12_bit = FRAC;
+        _regs.int_16_bit = N;
+        _regs.mod_12_bit = MOD;
+        _regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD)));
+        _regs.feedback_select = (target_freq >= 3.0e9) ?
+            max2870_regs_t::FEEDBACK_SELECT_DIVIDED :
+            max2870_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
+        _regs.r_counter_10_bit = R;
+        _regs.reference_divide_by_2 = T ?
+            max2870_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
+            max2870_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
+        _regs.reference_doubler = D ?
+            max2870_regs_t::REFERENCE_DOUBLER_ENABLED :
+            max2870_regs_t::REFERENCE_DOUBLER_DISABLED;
+        _regs.band_select_clock_div = BS;
+        _regs.bs_msb = (BS & 0x300) >> 8;
+        UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv));
+        _regs.rf_divider_select = rfdivsel_to_enum[RFdiv];
+
+        switch (output_power)
+        {
+        case -4:
+            _regs.output_power = max2870_regs_t::OUTPUT_POWER_M4DBM;
+            break;
+        case -1:
+            _regs.output_power = max2870_regs_t::OUTPUT_POWER_M1DBM;
+            break;
+        case 2:
+            _regs.output_power = max2870_regs_t::OUTPUT_POWER_2DBM;
+            break;
+        case 5:
+            _regs.output_power = max2870_regs_t::OUTPUT_POWER_5DBM;
+            break;
+        }
+
+        // Write the register values
+        write_regs();
+
+        // MAX2870 needs a 20ms delay after tuning for the first time
+        // for the lock detect to be reliable.
+        if (_first_tune)
+        {
+            boost::this_thread::sleep(boost::posix_time::milliseconds(20));
+            _first_tune = false;
+        }
+
+        return actual_freq;
+    };
+
+private:
+    std::set<boost::uint32_t> get_changed_addrs()
+    {
+        return _regs.get_changed_addrs<boost::uint32_t>();
+    };
+
+    boost::uint32_t get_reg(boost::uint32_t addr)
+    {
+        return _regs.get_reg(addr);
+    };
+
+    void save_state()
+    {
+        if (_save_state)
+            _regs.save_state();
+    }
+
+    max2870_regs_t _regs;
+    bool _save_state;
+    bool _first_tune;
+};
+
+class max2871 : public max287x
+{
+public:
+    max2871(max287x_write_fn write_fn) : max287x(write_fn), _first_tune(true)
+    {
+        // initialize register values (override defaults)
+        _regs.retune = max2871_regs_t::RETUNE_DISABLED;
+        //_regs.csm = max2871_regs_t::CSM_ENABLED;  // tried it - caused long lock times
+        _regs.charge_pump_current = max2871_regs_t::CHARGE_PUMP_CURRENT_5_12MA;
+
+        // MAX2871 data sheet says that all registers must be written twice
+        // with at least a 20ms delay between writes upon power up.  One
+        // write and a 20ms wait are done in power_up().  The second write
+        // is done when any other function that does a write to the registers
+        // is called.  To ensure all registers are written the second time, the
+        // state of the registers is not saved during the first write.
+        _save_state = false;
+        power_up();
+        _save_state = true;
+    };
+
+    ~max2871()
+    {
+        shutdown();
+    };
+
+    bool is_shutdown(void)
+    {
+        return (_regs.power_down == max2871_regs_t::POWER_DOWN_SHUTDOWN);
+    };
+
+    void shutdown(void)
+    {
+        _regs.rf_output_enable = max2871_regs_t::RF_OUTPUT_ENABLE_DISABLED;
+        _regs.aux_output_enable = max2871_regs_t::AUX_OUTPUT_ENABLE_DISABLED;
+        _regs.power_down = max2871_regs_t::POWER_DOWN_SHUTDOWN;
+        _regs.ld_pin_mode = max2871_regs_t::LD_PIN_MODE_LOW;
+        _regs.muxout = max2871_regs_t::MUXOUT_TRI_STATE;
+        write_regs();
+    };
+
+    void power_up(void)
+    {
+        _regs.ld_pin_mode = max2871_regs_t::LD_PIN_MODE_DLD;
+        _regs.power_down = max2871_regs_t::POWER_DOWN_NORMAL;
+        _regs.muxout = max2871_regs_t::MUXOUT_TRI_STATE;
+        write_regs();
+
+        // MAX271 data sheet says to wait at least 20 ms after exiting low power mode
+        // before programming final VCO frequency
+        boost::this_thread::sleep(boost::posix_time::milliseconds(20));
+
+        _first_tune = true;
+    };
+
+    double set_freq_and_power(double target_freq, double ref_freq, bool is_int_n, int output_power)
+    {
+        //map rf divider select output dividers to enums
+        static const uhd::dict<int, max2871_regs_t::rf_divider_select_t> rfdivsel_to_enum =
+            boost::assign::map_list_of
+            (1,   max2871_regs_t::RF_DIVIDER_SELECT_DIV1)
+            (2,   max2871_regs_t::RF_DIVIDER_SELECT_DIV2)
+            (4,   max2871_regs_t::RF_DIVIDER_SELECT_DIV4)
+            (8,   max2871_regs_t::RF_DIVIDER_SELECT_DIV8)
+            (16,  max2871_regs_t::RF_DIVIDER_SELECT_DIV16)
+            (32,  max2871_regs_t::RF_DIVIDER_SELECT_DIV32)
+            (64,  max2871_regs_t::RF_DIVIDER_SELECT_DIV64)
+            (128, max2871_regs_t::RF_DIVIDER_SELECT_DIV128);
+
+        int T = 0;
+        int D = ref_freq <= 10.0e6 ? 1 : 0;
+        int R, BS, N, FRAC, MOD, RFdiv;
+        double pfd_freq = 50e6;
+
+        double actual_freq = calculate_freq_settings(
+            target_freq, ref_freq, 50e6, is_int_n, pfd_freq, T, D, R, BS, N, FRAC, MOD, RFdiv);
+
+        //load the register values
+        _regs.rf_output_enable = max2871_regs_t::RF_OUTPUT_ENABLE_ENABLED;
+
+        if(is_int_n) {
+            _regs.cpl = max2871_regs_t::CPL_DISABLED;
+            _regs.ldf = max2871_regs_t::LDF_INT_N;
+            _regs.int_n_mode = max2871_regs_t::INT_N_MODE_INT_N;
+        } else {
+            _regs.cpl = max2871_regs_t::CPL_ENABLED;
+            _regs.ldf = max2871_regs_t::LDF_FRAC_N;
+            _regs.int_n_mode = max2871_regs_t::INT_N_MODE_FRAC_N;
+        }
+
+        _regs.lds = pfd_freq <= 32e6 ? max2871_regs_t::LDS_SLOW : max2871_regs_t::LDS_FAST;
+
+        _regs.frac_12_bit = FRAC;
+        _regs.int_16_bit = N;
+        _regs.mod_12_bit = MOD;
+        _regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD)));
+        _regs.feedback_select = (target_freq >= 3.0e9) ?
+            max2871_regs_t::FEEDBACK_SELECT_DIVIDED :
+            max2871_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
+        _regs.r_counter_10_bit = R;
+        _regs.reference_divide_by_2 = T ?
+            max2871_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
+            max2871_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
+        _regs.reference_doubler = D ?
+            max2871_regs_t::REFERENCE_DOUBLER_ENABLED :
+            max2871_regs_t::REFERENCE_DOUBLER_DISABLED;
+        _regs.band_select_clock_div = BS;
+        _regs.bs_msb = (BS & 0x300) >> 8;
+        UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv));
+        _regs.rf_divider_select = rfdivsel_to_enum[RFdiv];
+
+        switch (output_power)
+        {
+        case -4:
+            _regs.output_power = max2871_regs_t::OUTPUT_POWER_M4DBM;
+            break;
+        case -1:
+            _regs.output_power = max2871_regs_t::OUTPUT_POWER_M1DBM;
+            break;
+        case 2:
+            _regs.output_power = max2871_regs_t::OUTPUT_POWER_2DBM;
+            break;
+        case 5:
+            _regs.output_power = max2871_regs_t::OUTPUT_POWER_5DBM;
+            break;
+        default:
+            UHD_THROW_INVALID_CODE_PATH();
+            break;
+        }
+
+        write_regs();
+
+        // MAX2871 needs a 20ms delay after tuning for the first time
+        // for the lock detect to be reliable.
+        if (_first_tune)
+        {
+            boost::this_thread::sleep(boost::posix_time::milliseconds(20));
+            _first_tune = false;
+        }
+
+        return actual_freq;
+    };
+
+private:
+    std::set<boost::uint32_t> get_changed_addrs()
+    {
+        return _regs.get_changed_addrs<boost::uint32_t>();
+    };
+
+    boost::uint32_t get_reg(boost::uint32_t addr)
+    {
+        return _regs.get_reg(addr);
+    };
+
+    void save_state()
+    {
+        if (_save_state)
+            _regs.save_state();
+    }
+
+    max2871_regs_t _regs;
+    bool _save_state;
+    bool _first_tune;
+};
+
+/***********************************************************************
+ * UBX Data Structures
+ **********************************************************************/
+enum ubx_gpio_field_id_t
+{
+    SPI_ADDR,
+    TX_EN_N,
+    RX_EN_N,
+    RX_ANT,
+    TX_LO_LOCKED,
+    RX_LO_LOCKED,
+    CPLD_RST_N,
+    TX_GAIN,
+    RX_GAIN,
+    RXLO1_SYNC,
+    RXLO2_SYNC,
+    TXLO1_SYNC,
+    TXLO2_SYNC
+};
+
+enum ubx_cpld_field_id_t
+{
+    TXHB_SEL = 0,
+    TXLB_SEL = 1,
+    TXLO1_FSEL1 = 2,
+    TXLO1_FSEL2 = 3,
+    TXLO1_FSEL3 = 4,
+    RXHB_SEL = 5,
+    RXLB_SEL = 6,
+    RXLO1_FSEL1 = 7,
+    RXLO1_FSEL2 = 8,
+    RXLO1_FSEL3 = 9,
+    SEL_LNA1 = 10,
+    SEL_LNA2 = 11,
+    TXLO1_FORCEON = 12,
+    TXLO2_FORCEON = 13,
+    TXMOD_FORCEON = 14,
+    TXMIXER_FORCEON = 15,
+    TXDRV_FORCEON = 16,
+    RXLO1_FORCEON = 17,
+    RXLO2_FORCEON = 18,
+    RXDEMOD_FORCEON = 19,
+    RXMIXER_FORCEON = 20,
+    RXDRV_FORCEON = 21,
+    RXAMP_FORCEON = 22,
+    RXLNA1_FORCEON = 23,
+    RXLNA2_FORCEON = 24
+};
+
+struct ubx_gpio_field_info_t
+{
+    ubx_gpio_field_id_t id;
+    dboard_iface::unit_t unit;
+    boost::uint32_t offset;
+    boost::uint32_t mask;
+    boost::uint32_t width;
+    enum direction_t {OUTPUT,INPUT} direction;
+    bool is_atr_controlled;
+    boost::uint32_t atr_idle;
+    boost::uint32_t atr_tx;
+    boost::uint32_t atr_rx;
+    boost::uint32_t atr_full_duplex;
+};
+
+struct ubx_gpio_reg_t
+{
+    bool dirty;
+    boost::uint32_t value;
+    boost::uint32_t mask;
+    boost::uint32_t ddr;
+    boost::uint32_t atr_mask;
+    boost::uint32_t atr_idle;
+    boost::uint32_t atr_tx;
+    boost::uint32_t atr_rx;
+    boost::uint32_t atr_full_duplex;
+};
+
+struct ubx_cpld_reg_t
+{
+    void set_field(ubx_cpld_field_id_t field, boost::uint32_t val)
+    {
+        UHD_ASSERT_THROW(val == (val & 0x1));
+
+        if (val)
+            value |= boost::uint32_t(1) << field;
+        else
+            value &= ~(boost::uint32_t(1) << field);
+    }
+
+    boost::uint32_t value;
+};
+
+enum spi_dest_t {
+    TXLO1 = 0x0,    // 0x00: TXLO1, the main TXLO from 400MHz to 6000MHz
+    TXLO2 = 0x1,    // 0x01: TXLO2, the low band mixer TXLO 10MHz to 400MHz
+    RXLO1 = 0x2,    // 0x02: RXLO1, the main RXLO from 400MHz to 6000MHz
+    RXLO2 = 0x3,    // 0x03: RXLO2, the low band mixer RXLO 10MHz to 400MHz
+    CPLD = 0x4      // 0x04: CPLD SPI Register
+    };
+
+/***********************************************************************
+ * UBX Constants
+ **********************************************************************/
+static const freq_range_t ubx_freq_range(1.0e7, 6.0e9);
+static const gain_range_t ubx_tx_gain_range(0, 31.5, double(0.5));
+static const gain_range_t ubx_rx_gain_range(0, 31.5, double(0.5));
+static const std::vector<std::string> ubx_pgas = boost::assign::list_of("PGA-TX")("PGA-RX");
+static const std::vector<std::string> ubx_plls = boost::assign::list_of("TXLO")("RXLO");
+static const std::vector<std::string> ubx_tx_antennas = boost::assign::list_of("TX/RX")("CAL");
+static const std::vector<std::string> ubx_rx_antennas = boost::assign::list_of("TX/RX")("RX2")("CAL");
+static const std::vector<std::string> ubx_power_modes = boost::assign::list_of("performance")("powersave");
+static const std::vector<std::string> ubx_xcvr_modes = boost::assign::list_of("FDX")("TX")("TX/RX")("RX");
+
+static const ubx_gpio_field_info_t ubx_proto_gpio_info[] = {
+    {SPI_ADDR,      dboard_iface::UNIT_TX,  0,  0x7,        3,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {TX_EN_N,       dboard_iface::UNIT_TX,  3,  0x1<<3,     1,  ubx_gpio_field_info_t::INPUT,  true,   1,  0,  1,  0},
+    {RX_EN_N,       dboard_iface::UNIT_TX,  4,  0x1<<4,     1,  ubx_gpio_field_info_t::INPUT,  true,   1,  1,  0,  0},
+    {RX_ANT,        dboard_iface::UNIT_TX,  5,  0x1<<5,     1,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {TX_LO_LOCKED,  dboard_iface::UNIT_TX,  6,  0x1<<6,     1,  ubx_gpio_field_info_t::OUTPUT, false,  0,  0,  0,  0},
+    {RX_LO_LOCKED,  dboard_iface::UNIT_TX,  7,  0x1<<7,     1,  ubx_gpio_field_info_t::OUTPUT, false,  0,  0,  0,  0},
+    {CPLD_RST_N,    dboard_iface::UNIT_TX,  9,  0x1<<9,     1,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {TX_GAIN,       dboard_iface::UNIT_TX,  10, 0x3F<<10,   10, ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {RX_GAIN,       dboard_iface::UNIT_RX,  10, 0x3F<<10,   10, ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0}
+};
+
+static const ubx_gpio_field_info_t ubx_v1_gpio_info[] = {
+    {SPI_ADDR,      dboard_iface::UNIT_TX,   0,  0x7,        3,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {CPLD_RST_N,    dboard_iface::UNIT_TX,   3,  0x1<<3,     1,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {RX_ANT,        dboard_iface::UNIT_TX,   4,  0x1<<4,     1,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {TX_EN_N,       dboard_iface::UNIT_TX,   5,  0x1<<5,     1,  ubx_gpio_field_info_t::INPUT,  true,   1,  0,  1,  0},
+    {RX_EN_N,       dboard_iface::UNIT_TX,   6,  0x1<<6,     1,  ubx_gpio_field_info_t::INPUT,  true,   1,  1,  0,  0},
+    {TXLO1_SYNC,    dboard_iface::UNIT_TX,   7,  0x1<<7,     1,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {TXLO2_SYNC,    dboard_iface::UNIT_TX,   9,  0x1<<9,     1,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {TX_GAIN,       dboard_iface::UNIT_TX,   10, 0x3F<<10,   10, ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {RX_LO_LOCKED,  dboard_iface::UNIT_RX,   0,  0x1,        1,  ubx_gpio_field_info_t::OUTPUT, false,  0,  0,  0,  0},
+    {TX_LO_LOCKED,  dboard_iface::UNIT_RX,   1,  0x1<<1,     1,  ubx_gpio_field_info_t::OUTPUT, false,  0,  0,  0,  0},
+    {RXLO1_SYNC,    dboard_iface::UNIT_RX,   5,  0x1<<5,     1,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {RXLO2_SYNC,    dboard_iface::UNIT_RX,   7,  0x1<<7,     1,  ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0},
+    {RX_GAIN,       dboard_iface::UNIT_RX,   10, 0x3F<<10,   10, ubx_gpio_field_info_t::INPUT,  false,  0,  0,  0,  0}
+};
+
+/***********************************************************************
+ * Macros and helper functions for routing and writing SPI registers
+ **********************************************************************/
+#define ROUTE_SPI(iface, dest)  \
+    iface->set_gpio_out(dboard_iface::UNIT_TX, dest, 0x7);
+
+#define WRITE_SPI(iface, val)   \
+    iface->write_spi(dboard_iface::UNIT_TX, spi_config_t::EDGE_RISE, val, 32);
+
+UHD_INLINE void write_spi_reg(dboard_iface::sptr iface, spi_dest_t dest, boost::uint32_t value)
+{
+    ROUTE_SPI(iface, dest);
+    WRITE_SPI(iface, value);
+}
+
+UHD_INLINE void write_spi_regs(dboard_iface::sptr iface, spi_dest_t dest, std::vector<boost::uint32_t> values)
+{
+    ROUTE_SPI(iface, dest);
+    for (size_t i = 0; i < values.size(); i++)
+        WRITE_SPI(iface, values[i]);
+}
+
+/***********************************************************************
+ * UBX Class Definition
+ **********************************************************************/
+class ubx_xcvr : public xcvr_dboard_base
+{
+public:
+    ubx_xcvr(ctor_args_t args) : xcvr_dboard_base(args)
+    {
+        ////////////////////////////////////////////////////////////////////
+        // Setup GPIO hardware
+        ////////////////////////////////////////////////////////////////////
+        _iface = get_iface();
+        dboard_id_t rx_id = get_rx_id();
+        dboard_id_t tx_id = get_tx_id();
+        if (rx_id == UBX_PROTO_V3_RX_ID and tx_id == UBX_PROTO_V3_TX_ID)
+            _rev = 0;
+        if (rx_id == UBX_PROTO_V4_RX_ID and tx_id == UBX_PROTO_V4_TX_ID)
+            _rev = 1;
+        else if (rx_id == UBX_V1_40MHZ_RX_ID and tx_id == UBX_V1_40MHZ_TX_ID)
+            _rev = 1;
+        else if (rx_id == UBX_V1_160MHZ_RX_ID and tx_id == UBX_V1_160MHZ_TX_ID)
+            _rev = 1;
+        else
+            UHD_THROW_INVALID_CODE_PATH();
+
+        switch(_rev)
+        {
+        case 0:
+            for (size_t i = 0; i < sizeof(ubx_proto_gpio_info) / sizeof(ubx_gpio_field_info_t); i++)
+                _gpio_map[ubx_proto_gpio_info[i].id] = ubx_proto_gpio_info[i];
+            break;
+        case 1:
+            for (size_t i = 0; i < sizeof(ubx_v1_gpio_info) / sizeof(ubx_gpio_field_info_t); i++)
+                _gpio_map[ubx_v1_gpio_info[i].id] = ubx_v1_gpio_info[i];
+            break;
+        }
+
+        // Initialize GPIO registers
+        memset(&_tx_gpio_reg,0,sizeof(ubx_gpio_reg_t));
+        memset(&_rx_gpio_reg,0,sizeof(ubx_gpio_reg_t));
+        for (std::map<ubx_gpio_field_id_t,ubx_gpio_field_info_t>::iterator entry = _gpio_map.begin(); entry != _gpio_map.end(); entry++)
+        {
+            ubx_gpio_field_info_t info = entry->second;
+            ubx_gpio_reg_t *reg = (info.unit == dboard_iface::UNIT_TX ? &_tx_gpio_reg : &_rx_gpio_reg);
+            if (info.direction == ubx_gpio_field_info_t::INPUT)
+                reg->ddr |= info.mask;
+            if (info.is_atr_controlled)
+            {
+                reg->atr_mask |= info.mask;
+                reg->atr_idle |= (info.atr_idle << info.offset) & info.mask;
+                reg->atr_tx |= (info.atr_tx << info.offset) & info.mask;
+                reg->atr_rx |= (info.atr_rx << info.offset) & info.mask;
+                reg->atr_full_duplex |= (info.atr_full_duplex << info.offset) & info.mask;
+            }
+        }
+
+        // Enable the reference clocks that we need
+        _iface->set_clock_enabled(dboard_iface::UNIT_TX, true);
+        _iface->set_clock_enabled(dboard_iface::UNIT_RX, true);
+
+        // Set direction of GPIO pins (1 is input to UBX, 0 is output)
+        _iface->set_gpio_ddr(dboard_iface::UNIT_TX, _tx_gpio_reg.ddr);
+        _iface->set_gpio_ddr(dboard_iface::UNIT_RX, _rx_gpio_reg.ddr);
+
+        // Set default GPIO values
+        set_gpio_field(TX_GAIN, 0);
+        set_gpio_field(CPLD_RST_N, 0);
+        set_gpio_field(RX_ANT, 1);
+        set_gpio_field(TX_EN_N, 1);
+        set_gpio_field(RX_EN_N, 1);
+        set_gpio_field(SPI_ADDR, 0x7);
+        set_gpio_field(RX_GAIN, 0);
+        set_gpio_field(TXLO1_SYNC, 0);
+        set_gpio_field(TXLO2_SYNC, 0);
+        set_gpio_field(RXLO1_SYNC, 0);
+        set_gpio_field(RXLO1_SYNC, 0);
+        write_gpio();
+
+        // Configure ATR
+        _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _tx_gpio_reg.atr_idle);
+        _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx);
+        _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx);
+        _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex);
+        _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _rx_gpio_reg.atr_idle);
+        _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx);
+        _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx);
+        _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex);
+
+        // Engage ATR control (1 is ATR control, 0 is manual control)
+        _iface->set_pin_ctrl(dboard_iface::UNIT_TX, _tx_gpio_reg.atr_mask);
+        _iface->set_pin_ctrl(dboard_iface::UNIT_RX, _rx_gpio_reg.atr_mask);
+
+        // bring CPLD out of reset
+        boost::this_thread::sleep(boost::posix_time::milliseconds(20)); // hold CPLD reset for minimum of 20 ms
+
+        set_gpio_field(CPLD_RST_N, 1);
+        write_gpio();
+
+        // Initialize LOs
+        if (_rev == 0)
+        {
+            _txlo1.reset(new max2870(boost::bind(&write_spi_regs, _iface, TXLO1, _1)));
+            _txlo2.reset(new max2870(boost::bind(&write_spi_regs, _iface, TXLO2, _1)));
+            _rxlo1.reset(new max2870(boost::bind(&write_spi_regs, _iface, RXLO1, _1)));
+            _rxlo2.reset(new max2870(boost::bind(&write_spi_regs, _iface, RXLO2, _1)));
+        }
+        else if (_rev == 1)
+        {
+            _txlo1.reset(new max2871(boost::bind(&write_spi_regs, _iface, TXLO1, _1)));
+            _txlo2.reset(new max2871(boost::bind(&write_spi_regs, _iface, TXLO2, _1)));
+            _rxlo1.reset(new max2871(boost::bind(&write_spi_regs, _iface, RXLO1, _1)));
+            _rxlo2.reset(new max2871(boost::bind(&write_spi_regs, _iface, RXLO2, _1)));
+        }
+        else
+        {
+            UHD_THROW_INVALID_CODE_PATH();
+        }
+
+        // Initialize CPLD register
+        _cpld_reg.value = 0;
+        write_cpld_reg();
+
+        ////////////////////////////////////////////////////////////////////
+        // Register power save properties
+        ////////////////////////////////////////////////////////////////////
+        get_rx_subtree()->create<std::vector<std::string> >("power_mode/options")
+            .set(ubx_power_modes);
+        get_rx_subtree()->create<std::string>("power_mode/value")
+            .subscribe(boost::bind(&ubx_xcvr::set_power_mode, this, _1))
+            .set("performance");
+        get_rx_subtree()->create<std::vector<std::string> >("xcvr_mode/options")
+            .set(ubx_xcvr_modes);
+        get_rx_subtree()->create<std::string>("xcvr_mode/value")
+            .subscribe(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1))
+            .set("FDX");
+
+        ////////////////////////////////////////////////////////////////////
+        // Register TX properties
+        ////////////////////////////////////////////////////////////////////
+        get_tx_subtree()->create<std::string>("name").set("UBX TX");
+        get_tx_subtree()->create<device_addr_t>("tune_args")
+            .set(device_addr_t());
+        get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+            .publish(boost::bind(&ubx_xcvr::get_locked, this, "TXLO"));
+        get_tx_subtree()->create<double>("gains/PGA0/value")
+            .coerce(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0);
+        get_tx_subtree()->create<meta_range_t>("gains/PGA0/range")
+            .set(ubx_tx_gain_range);
+        get_tx_subtree()->create<double>("freq/value")
+            .coerce(boost::bind(&ubx_xcvr::set_tx_freq, this, _1))
+            .set(ubx_freq_range.start());
+        get_tx_subtree()->create<meta_range_t>("freq/range")
+            .set(ubx_freq_range);
+        get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
+            .set(ubx_tx_antennas);
+        get_tx_subtree()->create<std::string>("antenna/value")
+            .subscribe(boost::bind(&ubx_xcvr::set_tx_ant, this, _1))
+            .set(ubx_tx_antennas.at(0));
+        get_tx_subtree()->create<std::string>("connection")
+            .set("QI");
+        get_tx_subtree()->create<bool>("enabled")
+            .set(true); //always enabled
+        get_tx_subtree()->create<bool>("use_lo_offset")
+            .set(false);
+        get_tx_subtree()->create<double>("bandwidth/value")
+            .set(2*20.0e6); //20MHz low-pass, complex double-sided, so it should be 2x20MHz=40MHz
+        get_tx_subtree()->create<meta_range_t>("bandwidth/range")
+            .set(freq_range_t(2*20.0e6, 2*20.0e6));
+
+        ////////////////////////////////////////////////////////////////////
+        // Register RX properties
+        ////////////////////////////////////////////////////////////////////
+        get_rx_subtree()->create<std::string>("name").set("UBX RX");
+        get_rx_subtree()->create<device_addr_t>("tune_args")
+            .set(device_addr_t());
+        get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+            .publish(boost::bind(&ubx_xcvr::get_locked, this, "RXLO"));
+        get_rx_subtree()->create<double>("gains/PGA0/value")
+            .coerce(boost::bind(&ubx_xcvr::set_rx_gain, this, _1))
+            .set(0);
+        get_rx_subtree()->create<meta_range_t>("gains/PGA0/range")
+            .set(ubx_rx_gain_range);
+        get_rx_subtree()->create<double>("freq/value")
+            .coerce(boost::bind(&ubx_xcvr::set_rx_freq, this, _1))
+            .set(ubx_freq_range.start());
+        get_rx_subtree()->create<meta_range_t>("freq/range")
+            .set(ubx_freq_range);
+        get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+            .set(ubx_rx_antennas);
+        get_rx_subtree()->create<std::string>("antenna/value")
+            .subscribe(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2");
+        get_rx_subtree()->create<std::string>("connection")
+            .set("IQ");
+        get_rx_subtree()->create<bool>("enabled")
+            .set(true); //always enabled
+        get_rx_subtree()->create<bool>("use_lo_offset")
+            .set(false);
+        get_rx_subtree()->create<double>("bandwidth/value")
+            .set(2*20.0e6); //20MHz low-pass, complex double-sided, so it should be 2x20MHz=40MHz
+        get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+            .set(freq_range_t(2*20.0e6, 2*20.0e6));
+    }
+
+    ~ubx_xcvr(void)
+    {
+        // Shutdown synthesizers
+        _txlo1->shutdown();
+        _txlo2->shutdown();
+        _rxlo1->shutdown();
+        _rxlo2->shutdown();
+
+        // Reset CPLD values
+        _cpld_reg.value = 0;
+        write_cpld_reg();
+
+        // Reset GPIO values
+        set_gpio_field(TX_GAIN, 0);
+        set_gpio_field(CPLD_RST_N, 0);
+        set_gpio_field(RX_ANT, 1);
+        set_gpio_field(TX_EN_N, 1);
+        set_gpio_field(RX_EN_N, 1);
+        set_gpio_field(SPI_ADDR, 0x7);
+        set_gpio_field(RX_GAIN, 0);
+        set_gpio_field(TXLO1_SYNC, 0);
+        set_gpio_field(TXLO2_SYNC, 0);
+        set_gpio_field(RXLO1_SYNC, 0);
+        set_gpio_field(RXLO1_SYNC, 0);
+        write_gpio();
+    }
+
+private:
+    enum power_mode_t {PERFORMANCE,POWERSAVE};
+
+    /***********************************************************************
+    * Helper Functions
+    **********************************************************************/
+    void set_cpld_field(ubx_cpld_field_id_t id, boost::uint32_t value)
+    {
+        _cpld_reg.set_field(id, value);
+    }
+
+    void write_cpld_reg()
+    {
+        write_spi_reg(_iface, CPLD, _cpld_reg.value);
+    }
+
+    void set_gpio_field(ubx_gpio_field_id_t id, boost::uint32_t value)
+    {
+        // Look up field info
+        std::map<ubx_gpio_field_id_t,ubx_gpio_field_info_t>::iterator entry = _gpio_map.find(id);
+        if (entry == _gpio_map.end())
+            return;
+        ubx_gpio_field_info_t field_info = entry->second;
+        if (field_info.direction == ubx_gpio_field_info_t::OUTPUT)
+            return;
+        ubx_gpio_reg_t *reg = (field_info.unit == dboard_iface::UNIT_TX ? &_tx_gpio_reg : &_rx_gpio_reg);
+        boost::uint32_t _value = reg->value;
+        boost::uint32_t _mask = reg->mask;
+
+        // Set field and mask
+        _value &= ~field_info.mask;
+        _value |= (value << field_info.offset) & field_info.mask;
+        _mask |= field_info.mask;
+
+        // Mark whether register is dirty or not
+        if (_value != reg->value)
+        {
+            reg->value = _value;
+            reg->mask = _mask;
+            reg->dirty = true;
+        }
+    }
+
+    boost::uint32_t get_gpio_field(ubx_gpio_field_id_t id)
+    {
+        // Look up field info
+        std::map<ubx_gpio_field_id_t,ubx_gpio_field_info_t>::iterator entry = _gpio_map.find(id);
+        if (entry == _gpio_map.end())
+            return 0;
+        ubx_gpio_field_info_t field_info = entry->second;
+        if (field_info.direction == ubx_gpio_field_info_t::INPUT)
+            return 0;
+
+        // Read register
+        boost::uint32_t value = _iface->read_gpio(field_info.unit);
+        value &= field_info.mask;
+        value >>= field_info.offset;
+
+        // Return field value
+        return value;
+    }
+
+    void write_gpio()
+    {
+        if (_tx_gpio_reg.dirty)
+        {
+            _iface->set_gpio_out(dboard_iface::UNIT_TX, _tx_gpio_reg.value, _tx_gpio_reg.mask);
+            _tx_gpio_reg.dirty = false;
+            _tx_gpio_reg.mask = 0;
+        }
+        if (_rx_gpio_reg.dirty)
+        {
+            _iface->set_gpio_out(dboard_iface::UNIT_RX, _rx_gpio_reg.value, _rx_gpio_reg.mask);
+            _rx_gpio_reg.dirty = false;
+            _rx_gpio_reg.mask = 0;
+        }
+    }
+
+    /***********************************************************************
+     * Board Control Handling
+     **********************************************************************/
+    sensor_value_t get_locked(const std::string &pll_name)
+    {
+        assert_has(ubx_plls, pll_name, "ubx pll name");
+
+        if(pll_name == "TXLO")
+        {
+            _txlo_locked = (get_gpio_field(TX_LO_LOCKED) != 0);
+            return sensor_value_t("TXLO", _txlo_locked, "locked", "unlocked");
+        }
+        else if(pll_name == "RXLO")
+        {
+            _rxlo_locked = (get_gpio_field(RX_LO_LOCKED) != 0);
+            return sensor_value_t("RXLO", _rxlo_locked, "locked", "unlocked");
+        }
+
+        return sensor_value_t("Unknown", false, "locked", "unlocked");
+    }
+
+    void set_tx_ant(const std::string &ant)
+    {
+        //validate input
+        assert_has(ubx_tx_antennas, ant, "ubx tx antenna name");
+    }
+
+    // Set RX antennas
+    void set_rx_ant(const std::string &ant)
+    {
+        //validate input
+        assert_has(ubx_rx_antennas, ant, "ubx rx antenna name");
+
+        if(ant == "RX2")
+            set_gpio_field(RX_ANT, 1);
+        else if(ant == "TX/RX")
+            set_gpio_field(RX_ANT, 0);
+        else if (ant == "CAL")
+            set_gpio_field(RX_ANT, 1);
+        write_gpio();
+    }
+
+    /***********************************************************************
+     * Gain Handling
+     **********************************************************************/
+    double set_tx_gain(double gain)
+    {
+        gain = ubx_tx_gain_range.clip(gain);
+        int attn_code = int(std::floor(gain * 2));
+        _ubx_tx_atten_val = ((attn_code & 0x3F) << 10);
+        set_gpio_field(TX_GAIN, attn_code);
+        write_gpio();
+        UHD_LOGV(rarely) << boost::format("UBX TX Gain: %f dB, Code: %d, IO Bits 0x%04x") % gain % attn_code % _ubx_tx_atten_val << std::endl;
+        _tx_gain = gain;
+        return gain;
+    }
+
+    double set_rx_gain(double gain)
+    {
+        gain = ubx_rx_gain_range.clip(gain);
+        int attn_code = int(std::floor(gain * 2));
+        _ubx_rx_atten_val = ((attn_code & 0x3F) << 10);
+        set_gpio_field(RX_GAIN, attn_code);
+        write_gpio();
+        UHD_LOGV(rarely) << boost::format("UBX RX Gain: %f dB, Code: %d, IO Bits 0x%04x") % gain % attn_code % _ubx_rx_atten_val << std::endl;
+        _rx_gain = gain;
+        return gain;
+    }
+
+    /***********************************************************************
+    * Frequency Handling
+    **********************************************************************/
+    double set_tx_freq(double freq)
+    {
+        double freq_lo1 = 0.0;
+        double freq_lo2 = 0.0;
+        double ref_freq = _iface->get_clock_rate(dboard_iface::UNIT_TX);
+        bool is_int_n = false;
+
+        /*
+         * If the user sets 'mode_n=integer' in the tuning args, the user wishes to
+         * tune in Integer-N mode, which can result in better spur
+         * performance on some mixers. The default is fractional tuning.
+         */
+        property_tree::sptr subtree = this->get_tx_subtree();
+        device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
+        is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
+        UHD_LOGV(rarely) << boost::format("UBX TX: the requested frequency is %f MHz") % (freq/1e6) << std::endl;
+
+        // Clip the frequency to the valid range
+        freq = ubx_freq_range.clip(freq);
+
+        // Power up/down LOs
+        if (_txlo1->is_shutdown())
+            _txlo1->power_up();
+        if (_txlo2->is_shutdown() and (_power_mode == PERFORMANCE or freq < (500*fMHz)))
+            _txlo2->power_up();
+        else if (freq >= 500*fMHz and _power_mode == POWERSAVE)
+            _txlo2->shutdown();
+
+        // Set up registers for the requested frequency
+        if (freq < (500*fMHz))
+        {
+            set_cpld_field(TXLO1_FSEL3, 0);
+            set_cpld_field(TXLO1_FSEL2, 1);
+            set_cpld_field(TXLO1_FSEL1, 0);
+            set_cpld_field(TXLB_SEL, 1);
+            set_cpld_field(TXHB_SEL, 0);
+            write_cpld_reg();
+            // Set LO1 to IF of 2100 MHz (offset from RX IF to reduce leakage)
+            freq_lo1 = _txlo1->set_freq_and_power(2100*fMHz, ref_freq, is_int_n, 5);
+            // Set LO2 to IF minus desired frequency
+            freq_lo2 = _txlo2->set_freq_and_power(freq_lo1 - freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq >= (500*fMHz)) && (freq <= (800*fMHz)))
+        {
+            set_cpld_field(TXLO1_FSEL3, 0);
+            set_cpld_field(TXLO1_FSEL2, 0);
+            set_cpld_field(TXLO1_FSEL1, 1);
+            set_cpld_field(TXLB_SEL, 0);
+            set_cpld_field(TXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq > (800*fMHz)) && (freq <= (1000*fMHz)))
+        {
+            set_cpld_field(TXLO1_FSEL3, 0);
+            set_cpld_field(TXLO1_FSEL2, 0);
+            set_cpld_field(TXLO1_FSEL1, 1);
+            set_cpld_field(TXLB_SEL, 0);
+            set_cpld_field(TXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 5);
+        }
+        else if ((freq > (1000*fMHz)) && (freq <= (2200*fMHz)))
+        {
+            set_cpld_field(TXLO1_FSEL3, 0);
+            set_cpld_field(TXLO1_FSEL2, 1);
+            set_cpld_field(TXLO1_FSEL1, 0);
+            set_cpld_field(TXLB_SEL, 0);
+            set_cpld_field(TXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq > (2200*fMHz)) && (freq <= (2500*fMHz)))
+        {
+            set_cpld_field(TXLO1_FSEL3, 0);
+            set_cpld_field(TXLO1_FSEL2, 1);
+            set_cpld_field(TXLO1_FSEL1, 0);
+            set_cpld_field(TXLB_SEL, 0);
+            set_cpld_field(TXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq > (2500*fMHz)) && (freq <= (6000*fMHz)))
+        {
+            set_cpld_field(TXLO1_FSEL3, 1);
+            set_cpld_field(TXLO1_FSEL2, 0);
+            set_cpld_field(TXLO1_FSEL1, 0);
+            set_cpld_field(TXLB_SEL, 0);
+            set_cpld_field(TXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 5);
+        }
+
+        _tx_freq = freq_lo1 - freq_lo2;
+        _txlo1_freq = freq_lo1;
+        _txlo2_freq = freq_lo2;
+
+        UHD_LOGV(rarely) << boost::format("UBX TX: the actual frequency is %f MHz") % (_tx_freq/1e6) << std::endl;
+
+        return _tx_freq;
+    }
+
+    double set_rx_freq(double freq)
+    {
+        double freq_lo1 = 0.0;
+        double freq_lo2 = 0.0;
+        double ref_freq = _iface->get_clock_rate(dboard_iface::UNIT_RX);
+        bool is_int_n = false;
+
+        UHD_LOGV(rarely) << boost::format("UBX RX: the requested frequency is %f MHz") % (freq/1e6) << std::endl;
+
+        property_tree::sptr subtree = this->get_rx_subtree();
+        device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
+        is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
+
+        // Clip the frequency to the valid range
+        freq = ubx_freq_range.clip(freq);
+
+        // Power up/down LOs
+        if (_rxlo1->is_shutdown())
+            _rxlo1->power_up();
+        if (_rxlo2->is_shutdown() and (_power_mode == PERFORMANCE or freq < 500*fMHz))
+            _rxlo2->power_up();
+        else if (freq >= 500*fMHz and _power_mode == POWERSAVE)
+            _rxlo2->shutdown();
+
+        // Work with frequencies
+        if (freq < 100*fMHz)
+        {
+            set_cpld_field(SEL_LNA1, 0);
+            set_cpld_field(SEL_LNA2, 1);
+            set_cpld_field(RXLO1_FSEL3, 1);
+            set_cpld_field(RXLO1_FSEL2, 0);
+            set_cpld_field(RXLO1_FSEL1, 0);
+            set_cpld_field(RXLB_SEL, 1);
+            set_cpld_field(RXHB_SEL, 0);
+            write_cpld_reg();
+            // Set LO1 to IF of 2380 MHz (2440 MHz filter center minus 60 MHz offset to minimize LO leakage)
+            freq_lo1 = _rxlo1->set_freq_and_power(2380*fMHz, ref_freq, is_int_n, 5);
+            // Set LO2 to IF minus desired frequency
+            freq_lo2 = _rxlo2->set_freq_and_power(freq_lo1 - freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq >= 100*fMHz) && (freq < 500*fMHz))
+        {
+            set_cpld_field(SEL_LNA1, 0);
+            set_cpld_field(SEL_LNA2, 1);
+            set_cpld_field(RXLO1_FSEL3, 1);
+            set_cpld_field(RXLO1_FSEL2, 0);
+            set_cpld_field(RXLO1_FSEL1, 0);
+            set_cpld_field(RXLB_SEL, 1);
+            set_cpld_field(RXHB_SEL, 0);
+            write_cpld_reg();
+            // Set LO1 to IF of 2440 (center of filter)
+            freq_lo1 = _rxlo1->set_freq_and_power(2440*fMHz, ref_freq, is_int_n, 5);
+            // Set LO2 to IF minus desired frequency
+            freq_lo2 = _rxlo2->set_freq_and_power(freq_lo1 - freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq >= 500*fMHz) && (freq < 800*fMHz))
+        {
+            set_cpld_field(SEL_LNA1, 0);
+            set_cpld_field(SEL_LNA2, 1);
+            set_cpld_field(RXLO1_FSEL3, 0);
+            set_cpld_field(RXLO1_FSEL2, 0);
+            set_cpld_field(RXLO1_FSEL1, 1);
+            set_cpld_field(RXLB_SEL, 0);
+            set_cpld_field(RXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq >= 800*fMHz) && (freq < 1000*fMHz))
+        {
+            set_cpld_field(SEL_LNA1, 0);
+            set_cpld_field(SEL_LNA2, 1);
+            set_cpld_field(RXLO1_FSEL3, 0);
+            set_cpld_field(RXLO1_FSEL2, 0);
+            set_cpld_field(RXLO1_FSEL1, 1);
+            set_cpld_field(RXLB_SEL, 0);
+            set_cpld_field(RXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 5);
+        }
+        else if ((freq >= 1000*fMHz) && (freq < 1500*fMHz))
+        {
+            set_cpld_field(SEL_LNA1, 0);
+            set_cpld_field(SEL_LNA2, 1);
+            set_cpld_field(RXLO1_FSEL3, 0);
+            set_cpld_field(RXLO1_FSEL2, 1);
+            set_cpld_field(RXLO1_FSEL1, 0);
+            set_cpld_field(RXLB_SEL, 0);
+            set_cpld_field(RXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq >= 1500*fMHz) && (freq < 2200*fMHz))
+        {
+            set_cpld_field(SEL_LNA1, 1);
+            set_cpld_field(SEL_LNA2, 0);
+            set_cpld_field(RXLO1_FSEL3, 0);
+            set_cpld_field(RXLO1_FSEL2, 1);
+            set_cpld_field(RXLO1_FSEL1, 0);
+            set_cpld_field(RXLB_SEL, 0);
+            set_cpld_field(RXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq >= 2200*fMHz) && (freq < 2500*fMHz))
+        {
+            set_cpld_field(SEL_LNA1, 1);
+            set_cpld_field(SEL_LNA2, 0);
+            set_cpld_field(RXLO1_FSEL3, 0);
+            set_cpld_field(RXLO1_FSEL2, 1);
+            set_cpld_field(RXLO1_FSEL1, 0);
+            set_cpld_field(RXLB_SEL, 0);
+            set_cpld_field(RXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2);
+        }
+        else if ((freq >= 2500*fMHz) && (freq <= 6000*fMHz))
+        {
+            set_cpld_field(SEL_LNA1, 1);
+            set_cpld_field(SEL_LNA2, 0);
+            set_cpld_field(RXLO1_FSEL3, 1);
+            set_cpld_field(RXLO1_FSEL2, 0);
+            set_cpld_field(RXLO1_FSEL1, 0);
+            set_cpld_field(RXLB_SEL, 0);
+            set_cpld_field(RXHB_SEL, 1);
+            write_cpld_reg();
+            freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 5);
+        }
+
+        freq = freq_lo1 - freq_lo2;
+
+        UHD_LOGV(rarely) << boost::format("UBX RX: the actual frequency is %f MHz") % (freq/1e6) << std::endl;
+
+        return freq;
+    }
+
+    /***********************************************************************
+    * Setting Modes
+    **********************************************************************/
+    void set_power_mode(std::string mode)
+    {
+        if (mode == "performance")
+        {
+            // FIXME:  Response to ATR change is too slow for some components,
+            // so certain components are forced on here.  Force on does not
+            // necessarily mean immediately.  Some FORCEON lines are still gated
+            // by other bits in the CPLD register that are asserted during
+            // frequency tuning.
+            set_cpld_field(RXAMP_FORCEON, 1);
+            set_cpld_field(RXDEMOD_FORCEON, 1);
+            set_cpld_field(RXDRV_FORCEON, 1);
+            set_cpld_field(RXMIXER_FORCEON, 1);
+            set_cpld_field(RXLO1_FORCEON, 1);
+            set_cpld_field(RXLO2_FORCEON, 1);
+            set_cpld_field(RXLNA1_FORCEON, 1);
+            set_cpld_field(RXLNA2_FORCEON, 1);
+            set_cpld_field(TXDRV_FORCEON, 1);
+            set_cpld_field(TXMOD_FORCEON, 1);
+            set_cpld_field(TXMIXER_FORCEON, 1);
+            set_cpld_field(TXLO1_FORCEON, 1);
+            set_cpld_field(TXLO2_FORCEON, 1);
+            _power_mode = PERFORMANCE;
+        }
+        else if (mode == "powersave")
+        {
+            set_cpld_field(RXAMP_FORCEON, 0);
+            set_cpld_field(RXDEMOD_FORCEON, 0);
+            set_cpld_field(RXDRV_FORCEON, 0);
+            set_cpld_field(RXMIXER_FORCEON, 0);
+            set_cpld_field(RXLO1_FORCEON, 0);
+            set_cpld_field(RXLO2_FORCEON, 0);
+            set_cpld_field(RXLNA1_FORCEON, 0);
+            set_cpld_field(RXLNA2_FORCEON, 0);
+            set_cpld_field(TXDRV_FORCEON, 0);
+            set_cpld_field(TXMOD_FORCEON, 0);
+            set_cpld_field(TXMIXER_FORCEON, 0);
+            set_cpld_field(TXLO1_FORCEON, 0);
+            set_cpld_field(TXLO2_FORCEON, 0);
+            _power_mode = POWERSAVE;
+        }
+        write_cpld_reg();
+    }
+
+    void set_xcvr_mode(std::string mode)
+    {
+        // TO DO:  Add implementation
+        // The intent is to add behavior based on whether
+        // the board is in TX, RX, or full duplex mode
+        // to reduce power consumption and RF noise.
+        _xcvr_mode = mode;
+    }
+
+    /***********************************************************************
+    * Variables
+    **********************************************************************/
+    dboard_iface::sptr _iface;
+    ubx_cpld_reg_t _cpld_reg;
+    boost::shared_ptr<max287x_synthesizer_iface> _txlo1;
+    boost::shared_ptr<max287x_synthesizer_iface> _txlo2;
+    boost::shared_ptr<max287x_synthesizer_iface> _rxlo1;
+    boost::shared_ptr<max287x_synthesizer_iface> _rxlo2;
+    double _tx_gain;
+    double _rx_gain;
+    double _tx_freq;
+    double _txlo1_freq;
+    double _txlo2_freq;
+    double _rx_freq;
+    double _rxlo1_freq;
+    double _rxlo2_freq;
+    bool _rxlo_locked;
+    bool _txlo_locked;
+    std::string _rx_ant;
+    int _ubx_tx_atten_val;
+    int _ubx_rx_atten_val;
+    power_mode_t _power_mode;
+    std::string _xcvr_mode;
+    size_t _rev;
+    double _prev_tx_freq;
+    double _prev_rx_freq;
+    std::map<ubx_gpio_field_id_t,ubx_gpio_field_info_t> _gpio_map;
+    ubx_gpio_reg_t _tx_gpio_reg;
+    ubx_gpio_reg_t _rx_gpio_reg;
+};
+
+/***********************************************************************
+ * Register the UBX dboard (min freq, max freq, rx div2, tx div2)
+ **********************************************************************/
+static dboard_base::sptr make_ubx(dboard_base::ctor_args_t args)
+{
+    return dboard_base::sptr(new ubx_xcvr(args));
+}
+
+UHD_STATIC_BLOCK(reg_ubx_dboards)
+{
+    dboard_manager::register_dboard(0x0074, 0x0073, &make_ubx, "UBX v0.3");
+    dboard_manager::register_dboard(0x0076, 0x0075, &make_ubx, "UBX v0.4");
+    dboard_manager::register_dboard(0x0078, 0x0077, &make_ubx, "UBX-40 v1");
+    dboard_manager::register_dboard(0x007a, 0x0079, &make_ubx, "UBX-160 v1");
+}
-- 
cgit v1.2.3


From 38164fb54d0116abf8df4eb2e2df9c1a892c64af Mon Sep 17 00:00:00 2001
From: Martin Braun <martin.braun@ettus.com>
Date: Mon, 19 Jan 2015 09:50:08 +0100
Subject: ad9361: More check for interpolation/decim ratios

---
 .../lib/usrp/common/ad9361_driver/ad9361_device.cpp | 21 +++++++++++++++------
 host/lib/usrp/common/ad9361_driver/ad9361_device.h  |  2 +-
 2 files changed, 16 insertions(+), 7 deletions(-)

(limited to 'host/lib')

diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp
index 7e574920a..afaa4a1fb 100644
--- a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp
+++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp
@@ -143,22 +143,25 @@ void ad9361_device_t::_program_fir_filter(direction_t direction, int num_taps, b
 
 
 /* Program the RX FIR Filter. */
-void ad9361_device_t::_setup_rx_fir(size_t num_taps, boost::int32_t interpolation)
+void ad9361_device_t::_setup_rx_fir(size_t num_taps, boost::int32_t decimation)
 {
+    if (not (decimation == 1 or decimation == 2 or decimation == 4)) {
+        throw uhd::runtime_error("[ad9361_device_t] Invalid Rx FIR decimation.");
+    }
     boost::scoped_array<boost::uint16_t> coeffs(new boost::uint16_t[num_taps]);
     for (size_t i = 0; i < num_taps; i++) {
         switch (num_taps) {
         case 128:
-            coeffs[i] = boost::uint16_t((interpolation==4) ? fir_128_x4_coeffs[i] : hb127_coeffs[i]);
+            coeffs[i] = boost::uint16_t((decimation==4) ? fir_128_x4_coeffs[i] : hb127_coeffs[i]);
             break;
         case 96:
-            coeffs[i] = boost::uint16_t((interpolation==4) ? fir_96_x4_coeffs[i] : hb95_coeffs[i]);
+            coeffs[i] = boost::uint16_t((decimation==4) ? fir_96_x4_coeffs[i] : hb95_coeffs[i]);
             break;
         case 64:
-            coeffs[i] = boost::uint16_t((interpolation==4) ? fir_64_x4_coeffs[i] : hb63_coeffs[i]);
+            coeffs[i] = boost::uint16_t((decimation==4) ? fir_64_x4_coeffs[i] : hb63_coeffs[i]);
             break;
         case 48:
-            coeffs[i] = boost::uint16_t((interpolation==4) ? fir_48_x4_coeffs[i] : hb47_coeffs[i]);
+            coeffs[i] = boost::uint16_t((decimation==4) ? fir_48_x4_coeffs[i] : hb47_coeffs[i]);
             break;
         default:
             throw uhd::runtime_error("[ad9361_device_t] Unsupported number of Rx FIR taps.");
@@ -171,11 +174,17 @@ void ad9361_device_t::_setup_rx_fir(size_t num_taps, boost::int32_t interpolatio
 /* Program the TX FIR Filter. */
 void ad9361_device_t::_setup_tx_fir(size_t num_taps, boost::int32_t interpolation)
 {
+    if (not (interpolation == 1 or interpolation == 2 or interpolation == 4)) {
+        throw uhd::runtime_error("[ad9361_device_t] Invalid Tx FIR interpolation.");
+    }
+    if (interpolation == 1 and num_t > 64) {
+        throw uhd::runtime_error("[ad9361_device_t] Too many Tx FIR taps for interpolation value.");
+    }
     boost::scoped_array<boost::uint16_t> coeffs(new boost::uint16_t[num_taps]);
     for (size_t i = 0; i < num_taps; i++) {
         switch (num_taps) {
         case 128:
-	    coeffs[i] = boost::uint16_t((interpolation==4) ? fir_128_x4_coeffs[i] : hb127_coeffs[i]);
+            coeffs[i] = boost::uint16_t((interpolation==4) ? fir_128_x4_coeffs[i] : hb127_coeffs[i]);
             break;
         case 96:
             coeffs[i] = boost::uint16_t((interpolation==4) ? fir_96_x4_coeffs[i] : hb95_coeffs[i]);
diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.h b/host/lib/usrp/common/ad9361_driver/ad9361_device.h
index fead90424..ca83f3037 100644
--- a/host/lib/usrp/common/ad9361_driver/ad9361_device.h
+++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.h
@@ -74,7 +74,7 @@ public:
 private:    //Methods
     void _program_fir_filter(direction_t direction, int num_taps, boost::uint16_t *coeffs);
     void _setup_tx_fir(size_t num_taps, boost::int32_t interpolation);
-    void _setup_rx_fir(size_t num_taps, boost::int32_t interpolation);
+    void _setup_rx_fir(size_t num_taps, boost::int32_t decimation);
     void _calibrate_lock_bbpll();
     void _calibrate_synth_charge_pumps();
     double _calibrate_baseband_rx_analog_filter();
-- 
cgit v1.2.3