diff options
Diffstat (limited to 'host/lib')
| -rw-r--r-- | host/lib/ic_reg_maps/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | host/lib/ic_reg_maps/gen_max2871_regs.py | 146 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/db_ubx.cpp | 1434 | 
4 files changed, 1586 insertions, 0 deletions
| 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 @@ -38,6 +38,11 @@ LIBUHD_PYTHON_GEN_SOURCE(  )  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"); +} | 
