// // 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 . // /*********************************************************************** * Included Files and Libraries **********************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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)> 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 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 regs; std::set changed_regs; // Get only regs with changes try { changed_regs = get_changed_addrs(); } catch (uhd::runtime_error&) { // 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 = int(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 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 get_changed_addrs() { return _regs.get_changed_addrs(); }; 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 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 get_changed_addrs() { return _regs.get_changed_addrs(); }; 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 ubx_pgas = boost::assign::list_of("PGA-TX")("PGA-RX"); static const std::vector ubx_plls = boost::assign::list_of("TXLO")("RXLO"); static const std::vector ubx_tx_antennas = boost::assign::list_of("TX/RX")("CAL"); static const std::vector ubx_rx_antennas = boost::assign::list_of("TX/RX")("RX2")("CAL"); static const std::vector ubx_power_modes = boost::assign::list_of("performance")("powersave"); static const std::vector 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 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); /*********************************************************************** * 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::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(&ubx_xcvr::write_spi_regs, this, TXLO1, _1))); _txlo2.reset(new max2870(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, _1))); _rxlo1.reset(new max2870(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, _1))); _rxlo2.reset(new max2870(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, _1))); } else if (_rev == 1) { _txlo1.reset(new max2871(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, _1))); _txlo2.reset(new max2871(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, _1))); _rxlo1.reset(new max2871(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, _1))); _rxlo2.reset(new max2871(boost::bind(&ubx_xcvr::write_spi_regs, this, 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 >("power_mode/options") .set(ubx_power_modes); get_rx_subtree()->create("power_mode/value") .subscribe(boost::bind(&ubx_xcvr::set_power_mode, this, _1)) .set("performance"); get_rx_subtree()->create >("xcvr_mode/options") .set(ubx_xcvr_modes); get_rx_subtree()->create("xcvr_mode/value") .subscribe(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1)) .set("FDX"); //////////////////////////////////////////////////////////////////// // Register TX properties //////////////////////////////////////////////////////////////////// get_tx_subtree()->create("name").set("UBX TX"); get_tx_subtree()->create("tune_args") .set(device_addr_t()); get_tx_subtree()->create("sensors/lo_locked") .publish(boost::bind(&ubx_xcvr::get_locked, this, "TXLO")); get_tx_subtree()->create("gains/PGA0/value") .coerce(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0); get_tx_subtree()->create("gains/PGA0/range") .set(ubx_tx_gain_range); get_tx_subtree()->create("freq/value") .coerce(boost::bind(&ubx_xcvr::set_tx_freq, this, _1)) .set(ubx_freq_range.start()); get_tx_subtree()->create("freq/range") .set(ubx_freq_range); get_tx_subtree()->create >("antenna/options") .set(ubx_tx_antennas); get_tx_subtree()->create("antenna/value") .subscribe(boost::bind(&ubx_xcvr::set_tx_ant, this, _1)) .set(ubx_tx_antennas.at(0)); get_tx_subtree()->create("connection") .set("QI"); get_tx_subtree()->create("enabled") .set(true); //always enabled get_tx_subtree()->create("use_lo_offset") .set(false); get_tx_subtree()->create("bandwidth/value") .set(2*20.0e6); //20MHz low-pass, complex double-sided, so it should be 2x20MHz=40MHz get_tx_subtree()->create("bandwidth/range") .set(freq_range_t(2*20.0e6, 2*20.0e6)); //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// get_rx_subtree()->create("name").set("UBX RX"); get_rx_subtree()->create("tune_args") .set(device_addr_t()); get_rx_subtree()->create("sensors/lo_locked") .publish(boost::bind(&ubx_xcvr::get_locked, this, "RXLO")); get_rx_subtree()->create("gains/PGA0/value") .coerce(boost::bind(&ubx_xcvr::set_rx_gain, this, _1)) .set(0); get_rx_subtree()->create("gains/PGA0/range") .set(ubx_rx_gain_range); get_rx_subtree()->create("freq/value") .coerce(boost::bind(&ubx_xcvr::set_rx_freq, this, _1)) .set(ubx_freq_range.start()); get_rx_subtree()->create("freq/range") .set(ubx_freq_range); get_rx_subtree()->create >("antenna/options") .set(ubx_rx_antennas); get_rx_subtree()->create("antenna/value") .subscribe(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2"); get_rx_subtree()->create("connection") .set("IQ"); get_rx_subtree()->create("enabled") .set(true); //always enabled get_rx_subtree()->create("use_lo_offset") .set(false); get_rx_subtree()->create("bandwidth/value") .set(2*20.0e6); //20MHz low-pass, complex double-sided, so it should be 2x20MHz=40MHz get_rx_subtree()->create("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 write_spi_reg(spi_dest_t dest, boost::uint32_t value) { boost::mutex::scoped_lock lock(_spi_lock); ROUTE_SPI(_iface, dest); WRITE_SPI(_iface, value); } void write_spi_regs(spi_dest_t dest, std::vector values) { boost::mutex::scoped_lock lock(_spi_lock); ROUTE_SPI(_iface, dest); BOOST_FOREACH(boost::uint32_t value, values) WRITE_SPI(_iface, value); } 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(CPLD, _cpld_reg.value); } void set_gpio_field(ubx_gpio_field_id_t id, boost::uint32_t value) { // Look up field info std::map::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::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("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("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; boost::mutex _spi_lock; ubx_cpld_reg_t _cpld_reg; boost::shared_ptr _txlo1; boost::shared_ptr _txlo2; boost::shared_ptr _rxlo1; boost::shared_ptr _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 _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"); }