aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp
diff options
context:
space:
mode:
authormichael-west <michael.west@ettus.com>2015-01-21 16:44:11 -0800
committerMartin Braun <martin.braun@ettus.com>2015-01-22 20:10:02 +0100
commit4dc90d5a1e03597799f41e47f030e4bcddb7d57e (patch)
tree54cc22ba0961c609ab3bd5b68d55bd9f3221bf39 /host/lib/usrp
parentcc5dd14e8b2176b55a40f6a7ac210f72d9f14995 (diff)
downloaduhd-4dc90d5a1e03597799f41e47f030e4bcddb7d57e.tar.gz
uhd-4dc90d5a1e03597799f41e47f030e4bcddb7d57e.tar.bz2
uhd-4dc90d5a1e03597799f41e47f030e4bcddb7d57e.zip
UBX: Add UBX Support
Diffstat (limited to 'host/lib/usrp')
-rw-r--r--host/lib/usrp/dboard/CMakeLists.txt1
-rw-r--r--host/lib/usrp/dboard/db_ubx.cpp1434
2 files changed, 1435 insertions, 0 deletions
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");
+}