diff options
-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 | ||||
-rw-r--r-- | host/utils/usrp_cal_utils.hpp | 6 |
5 files changed, 1590 insertions, 2 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"); +} diff --git a/host/utils/usrp_cal_utils.hpp b/host/utils/usrp_cal_utils.hpp index 6673b6329..c027a4785 100644 --- a/host/utils/usrp_cal_utils.hpp +++ b/host/utils/usrp_cal_utils.hpp @@ -87,7 +87,8 @@ static inline void set_optimum_defaults(uhd::usrp::multi_usrp::sptr usrp) if (tx_name.find("WBX") == std::string::npos and tx_name.find("SBX") == std::string::npos and tx_name.find("CBX") == std::string::npos and - tx_name.find("RFX") == std::string::npos + tx_name.find("RFX") == std::string::npos and + tx_name.find("UBX") == std::string::npos ) { throw std::runtime_error("self-calibration is not supported for this TX dboard"); @@ -99,7 +100,8 @@ static inline void set_optimum_defaults(uhd::usrp::multi_usrp::sptr usrp) if (rx_name.find("WBX") == std::string::npos and rx_name.find("SBX") == std::string::npos and rx_name.find("CBX") == std::string::npos and - rx_name.find("RFX") == std::string::npos + rx_name.find("RFX") == std::string::npos and + rx_name.find("UBX") == std::string::npos ) { throw std::runtime_error("self-calibration is not supported for this RX dboard"); |