uhd/host/lib/usrp/dboard/db_ubx.cpp
2016-12-01 12:45:33 -08:00

1272 lines
53 KiB
C++

//
// 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/types/direction.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/msg.hpp>
#include <uhd/utils/static.hpp>
#include <uhd/utils/safe_call.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 <boost/thread/mutex.hpp>
#include <map>
#include "max287x.hpp"
using namespace uhd;
using namespace uhd::usrp;
/***********************************************************************
* 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;
uint32_t offset;
uint32_t mask;
uint32_t width;
enum {OUTPUT,INPUT} direction;
bool is_atr_controlled;
uint32_t atr_idle;
uint32_t atr_tx;
uint32_t atr_rx;
uint32_t atr_full_duplex;
};
struct ubx_gpio_reg_t
{
bool dirty;
uint32_t value;
uint32_t mask;
uint32_t ddr;
uint32_t atr_mask;
uint32_t atr_idle;
uint32_t atr_tx;
uint32_t atr_rx;
uint32_t atr_full_duplex;
};
struct ubx_cpld_reg_t
{
void set_field(ubx_cpld_field_id_t field, uint32_t val)
{
UHD_ASSERT_THROW(val == (val & 0x1));
if (val)
value |= uint32_t(1) << field;
else
value &= ~(uint32_t(1) << field);
}
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
**********************************************************************/
#define fMHz (1000000.0)
static const dboard_id_t UBX_PROTO_V3_TX_ID(0x73);
static const dboard_id_t UBX_PROTO_V3_RX_ID(0x74);
static const dboard_id_t UBX_PROTO_V4_TX_ID(0x75);
static const dboard_id_t UBX_PROTO_V4_RX_ID(0x76);
static const dboard_id_t UBX_V1_40MHZ_TX_ID(0x77);
static const dboard_id_t UBX_V1_40MHZ_RX_ID(0x78);
static const dboard_id_t UBX_V1_160MHZ_TX_ID(0x79);
static const dboard_id_t UBX_V1_160MHZ_RX_ID(0x7A);
static const dboard_id_t UBX_V2_40MHZ_TX_ID(0x7B);
static const dboard_id_t UBX_V2_40MHZ_RX_ID(0x7C);
static const dboard_id_t UBX_V2_160MHZ_TX_ID(0x7D);
static const dboard_id_t UBX_V2_160MHZ_RX_ID(0x7E);
static const dboard_id_t UBX_LP_160MHZ_TX_ID(0x0200);
static const dboard_id_t UBX_LP_160MHZ_RX_ID(0x0201);
static const freq_range_t ubx_freq_range(10e6, 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[] = {
//Field Unit Offset Mask Width Direction ATR IDLE,TX,RX,FDX
{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[] = {
//Field Unit Offset Mask Width Direction ATR IDLE,TX,RX,FDX
{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, true, 0, 0, 0, 0},
{TXLO2_SYNC, dboard_iface::UNIT_TX, 9, 0x1<<9, 1, ubx_gpio_field_info_t::INPUT, true, 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, true, 0, 0, 0, 0},
{RXLO2_SYNC, dboard_iface::UNIT_RX, 7, 0x1<<7, 1, ubx_gpio_field_info_t::INPUT, true, 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) \
set_gpio_field(SPI_ADDR, dest); \
write_gpio();
#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)
{
double bw = 40e6;
double pfd_freq_max = 25e6;
////////////////////////////////////////////////////////////////////
// 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;
}
else 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_V2_40MHZ_RX_ID and tx_id == UBX_V2_40MHZ_TX_ID) {
_rev = 2;
}
else if (rx_id == UBX_V1_160MHZ_RX_ID and tx_id == UBX_V1_160MHZ_TX_ID) {
bw = 160e6;
_rev = 1;
}
else if (rx_id == UBX_V2_160MHZ_RX_ID and tx_id == UBX_V2_160MHZ_TX_ID) {
bw = 160e6;
_rev = 2;
}
else if (rx_id == UBX_LP_160MHZ_RX_ID and tx_id == UBX_LP_160MHZ_TX_ID) {
// The LP version behaves and looks like a regular UBX-160 v2
bw = 160e6;
_rev = 2;
}
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];
pfd_freq_max = 25e6;
break;
case 1:
case 2:
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];
pfd_freq_max = 50e6;
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
_rx_target_pfd_freq = pfd_freq_max;
_tx_target_pfd_freq = pfd_freq_max;
if (_rev >= 1)
{
// set dboard clock rates to as close to the max PFD freq as possible
if (_iface->get_clock_rate(dboard_iface::UNIT_RX) > pfd_freq_max)
{
std::vector<double> rates = _iface->get_clock_rates(dboard_iface::UNIT_RX);
double highest_rate = 0.0;
BOOST_FOREACH(double rate, rates)
{
if (rate <= pfd_freq_max and rate > highest_rate)
highest_rate = rate;
}
_iface->set_clock_rate(dboard_iface::UNIT_RX, highest_rate);
_rx_target_pfd_freq = highest_rate;
}
if (_iface->get_clock_rate(dboard_iface::UNIT_TX) > pfd_freq_max)
{
std::vector<double> rates = _iface->get_clock_rates(dboard_iface::UNIT_TX);
double highest_rate = 0.0;
BOOST_FOREACH(double rate, rates)
{
if (rate <= pfd_freq_max and rate > highest_rate)
highest_rate = rate;
}
_iface->set_clock_rate(dboard_iface::UNIT_TX, highest_rate);
_tx_target_pfd_freq = highest_rate;
}
}
_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, gpio_atr::ATR_REG_IDLE, _tx_gpio_reg.atr_idle);
_iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx);
_iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx);
_iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex);
_iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _rx_gpio_reg.atr_idle);
_iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx);
_iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx);
_iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::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 = max287x_iface::make<max2870>(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, _1));
_txlo2 = max287x_iface::make<max2870>(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, _1));
_rxlo1 = max287x_iface::make<max2870>(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, _1));
_rxlo2 = max287x_iface::make<max2870>(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, _1));
std::vector<max287x_iface::sptr> los = boost::assign::list_of(_txlo1)(_txlo2)(_rxlo1)(_rxlo2);
BOOST_FOREACH(max287x_iface::sptr lo, los)
{
lo->set_auto_retune(false);
lo->set_muxout_mode(max287x_iface::MUXOUT_DLD);
lo->set_ld_pin_mode(max287x_iface::LD_PIN_MODE_DLD);
}
}
else if (_rev == 1 or _rev == 2)
{
_txlo1 = max287x_iface::make<max2871>(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, _1));
_txlo2 = max287x_iface::make<max2871>(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, _1));
_rxlo1 = max287x_iface::make<max2871>(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, _1));
_rxlo2 = max287x_iface::make<max2871>(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, _1));
std::vector<max287x_iface::sptr> los = boost::assign::list_of(_txlo1)(_txlo2)(_rxlo1)(_rxlo2);
BOOST_FOREACH(max287x_iface::sptr lo, los)
{
lo->set_auto_retune(false);
//lo->set_cycle_slip_mode(true); // tried it - caused longer lock times
lo->set_charge_pump_current(max287x_iface::CHARGE_PUMP_CURRENT_5_12MA);
lo->set_muxout_mode(max287x_iface::MUXOUT_SYNC);
lo->set_ld_pin_mode(max287x_iface::LD_PIN_MODE_DLD);
}
}
else
{
UHD_THROW_INVALID_CODE_PATH();
}
// Initialize CPLD register
_prev_cpld_value = 0xFFFF;
_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")
.add_coerced_subscriber(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")
.add_coerced_subscriber(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1))
.set("FDX");
get_tx_subtree()->create<std::vector<std::string> >("power_mode/options")
.set(ubx_power_modes);
get_tx_subtree()->create<std::string>("power_mode/value")
.add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1))
.set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value")));
get_tx_subtree()->create<std::vector<std::string> >("xcvr_mode/options")
.set(ubx_xcvr_modes);
get_tx_subtree()->create<std::string>("xcvr_mode/value")
.add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1))
.set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value")));
////////////////////////////////////////////////////////////////////
// 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")
.set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "TXLO"));
get_tx_subtree()->create<double>("gains/PGA0/value")
.set_coercer(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")
.set_coercer(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")
.add_coerced_subscriber(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(bw);
get_tx_subtree()->create<meta_range_t>("bandwidth/range")
.set(freq_range_t(bw, bw));
get_tx_subtree()->create<int64_t>("sync_delay")
.add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1))
.set(-8);
////////////////////////////////////////////////////////////////////
// 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")
.set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "RXLO"));
get_rx_subtree()->create<double>("gains/PGA0/value")
.set_coercer(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")
.set_coercer(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")
.add_coerced_subscriber(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(bw);
get_rx_subtree()->create<meta_range_t>("bandwidth/range")
.set(freq_range_t(bw, bw));
get_rx_subtree()->create<int64_t>("sync_delay")
.add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1))
.set(-8);
}
virtual ~ubx_xcvr(void)
{
UHD_SAFE_CALL
(
// 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, uint32_t value)
{
boost::mutex::scoped_lock lock(_spi_mutex);
ROUTE_SPI(_iface, dest);
WRITE_SPI(_iface, value);
}
void write_spi_regs(spi_dest_t dest, std::vector<uint32_t> values)
{
boost::mutex::scoped_lock lock(_spi_mutex);
ROUTE_SPI(_iface, dest);
BOOST_FOREACH(uint32_t value, values)
WRITE_SPI(_iface, value);
}
void set_cpld_field(ubx_cpld_field_id_t id, uint32_t value)
{
_cpld_reg.set_field(id, value);
}
void write_cpld_reg()
{
if (_cpld_reg.value != _prev_cpld_value)
{
write_spi_reg(CPLD, _cpld_reg.value);
_prev_cpld_value = _cpld_reg.value;
}
}
void set_gpio_field(ubx_gpio_field_id_t id, 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);
uint32_t _value = reg->value;
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;
}
}
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)
{
ubx_gpio_reg_t *reg = (field_info.unit == dboard_iface::UNIT_TX ? &_tx_gpio_reg : &_rx_gpio_reg);
return (reg->value >> field_info.offset) & field_info.mask;
}
// Read register
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;
}
}
void sync_phase(uhd::time_spec_t cmd_time, uhd::direction_t dir)
{
// Send phase sync signal only if the command time is set
if (cmd_time != uhd::time_spec_t(0.0))
{
// Delay 400 microseconds to allow LOs to lock
cmd_time += uhd::time_spec_t(0.0004);
// Phase synchronization for MAX2871 requires that the sync signal
// is at least 4/(N*PFD_freq) + 2.6ns before the rising edge of the
// ref clock and 4/(N*PFD_freq) after the rising edge of the ref clock.
// Since the ref clock, the radio clock, and the VITA time are all
// synchronized to the 10 MHz clock, use the time spec to move
// the rising edge of the sync signal away from the 10 MHz edge,
// which will move it away from the ref clock edge by the same amount.
// Since the MAX2871 requires the ref freq and PFD freq be the same
// for phase synchronization, the dboard clock rate is used as the PFD
// freq and the worst case value of 20 is used for the N value to
// calculate the offset.
double pfd_freq = _iface->get_clock_rate(dir == TX_DIRECTION ? dboard_iface::UNIT_TX : dboard_iface::UNIT_RX);
double tick_rate = _iface->get_codec_rate(dir == TX_DIRECTION ? dboard_iface::UNIT_TX : dboard_iface::UNIT_RX);
int64_t ticks = cmd_time.to_ticks(tick_rate);
ticks -= ticks % (int64_t)(tick_rate / 10e6); // align to 10 MHz clock
ticks += dir == TX_DIRECTION ? _tx_sync_delay : _rx_sync_delay;
ticks += std::ceil(tick_rate*4/(20*pfd_freq)); // add required offset (using worst case N value of 20)
cmd_time = uhd::time_spec_t::from_ticks(ticks, tick_rate);
_iface->set_command_time(cmd_time);
// Assert SYNC
ubx_gpio_field_info_t lo1_field_info = _gpio_map.find(dir == TX_DIRECTION ? TXLO1_SYNC : RXLO1_SYNC)->second;
ubx_gpio_field_info_t lo2_field_info = _gpio_map.find(dir == TX_DIRECTION ? TXLO2_SYNC : RXLO2_SYNC)->second;
uint16_t value = (1 << lo1_field_info.offset) | (1 << lo2_field_info.offset);
uint16_t mask = lo1_field_info.mask | lo2_field_info.mask;
dboard_iface::unit_t unit = lo1_field_info.unit;
UHD_ASSERT_THROW(lo1_field_info.unit == lo2_field_info.unit);
_iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, value, mask);
cmd_time += uhd::time_spec_t(1/pfd_freq);
_iface->set_command_time(cmd_time);
_iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, value, mask);
cmd_time += uhd::time_spec_t(1/pfd_freq);
_iface->set_command_time(cmd_time);
_iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, value, mask);
cmd_time += uhd::time_spec_t(1/pfd_freq);
_iface->set_command_time(cmd_time);
_iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, value, mask);
// De-assert SYNC
// Head of line blocking means the command time does not need to be set.
_iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, 0, mask);
_iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, 0, mask);
_iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, 0, mask);
_iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, 0, mask);
}
}
/***********************************************************************
* Board Control Handling
**********************************************************************/
sensor_value_t get_locked(const std::string &pll_name)
{
boost::mutex::scoped_lock lock(_mutex);
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)
{
boost::mutex::scoped_lock lock(_mutex);
//validate input
assert_has(ubx_rx_antennas, ant, "ubx rx antenna name");
// Due to an issue with TX path into to the RF switch (U32), there
// is a long transient at the beginning of transmission when the RX
// antenna is set to RX2. Forcing on the TX PA removes the transient,
// so it is forced on only when the RX2 antenna is selected. It is
// cleared when the TX/RX antenna is selected to avoid a higher noise
// floor on RX.
if (ant == "TX/RX")
{
set_gpio_field(RX_ANT, 0);
set_cpld_field(TXDRV_FORCEON, 0); // Turn off PA in TDD mode
} else {
set_gpio_field(RX_ANT, 1);
set_cpld_field(TXDRV_FORCEON, 1); // Keep PA on
}
write_gpio();
write_cpld_reg();
}
/***********************************************************************
* Gain Handling
**********************************************************************/
double set_tx_gain(double gain)
{
boost::mutex::scoped_lock lock(_mutex);
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)
{
boost::mutex::scoped_lock lock(_mutex);
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)
{
boost::mutex::scoped_lock lock(_mutex);
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;
double target_pfd_freq = _tx_target_pfd_freq;
if (is_int_n and tune_args.has_key("int_n_step"))
{
target_pfd_freq = tune_args.cast<double>("int_n_step", _tx_target_pfd_freq);
if (target_pfd_freq > _tx_target_pfd_freq)
{
UHD_MSG(warning)
<< boost::format("Requested int_n_step of %f MHz too large, clipping to %f MHz")
% (target_pfd_freq/1e6)
% (_tx_target_pfd_freq/1e6)
<< std::endl;
target_pfd_freq = _tx_target_pfd_freq;
}
}
// 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 LOs for phase sync if command time is set
uhd::time_spec_t cmd_time = _iface->get_command_time();
if (cmd_time != uhd::time_spec_t(0.0))
{
_txlo1->config_for_sync(true);
if (not _txlo2->is_shutdown())
_txlo2->config_for_sync(true);
}
else
{
_txlo1->config_for_sync(false);
if (not _txlo2->is_shutdown())
_txlo2->config_for_sync(false);
}
// 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);
// Set LO1 to IF of 2100 MHz (offset from RX IF to reduce leakage)
freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
// Set LO2 to IF minus desired frequency
freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
_txlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
}
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);
freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
}
// To reduce the number of commands issued to the device, write to the
// SPI destination already addressed first. This avoids the writes to
// the GPIO registers to route the SPI to the same destination.
switch (get_gpio_field(SPI_ADDR))
{
case TXLO1:
_txlo1->commit();
if (freq < (500*fMHz)) _txlo2->commit();
write_cpld_reg();
break;
case TXLO2:
if (freq < (500*fMHz)) _txlo2->commit();
_txlo1->commit();
write_cpld_reg();
break;
default:
write_cpld_reg();
_txlo1->commit();
if (freq < (500*fMHz)) _txlo2->commit();
break;
}
if (cmd_time != uhd::time_spec_t(0.0) and _txlo1->can_sync())
{
sync_phase(cmd_time, TX_DIRECTION);
}
_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)
{
boost::mutex::scoped_lock lock(_mutex);
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");
double target_pfd_freq = _rx_target_pfd_freq;
if (is_int_n and tune_args.has_key("int_n_step"))
{
target_pfd_freq = tune_args.cast<double>("int_n_step", _rx_target_pfd_freq);
if (target_pfd_freq > _rx_target_pfd_freq)
{
UHD_MSG(warning)
<< boost::format("Requested int_n_step of %f Mhz too large, clipping to %f MHz")
% (target_pfd_freq/1e6)
% (_rx_target_pfd_freq/1e6)
<< std::endl;
target_pfd_freq = _rx_target_pfd_freq;
}
}
// 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();
// Set up LOs for phase sync if command time is set
uhd::time_spec_t cmd_time = _iface->get_command_time();
if (cmd_time != uhd::time_spec_t(0.0))
{
_rxlo1->config_for_sync(true);
if (not _rxlo2->is_shutdown())
_rxlo2->config_for_sync(true);
}
else
{
_rxlo1->config_for_sync(false);
if (not _rxlo2->is_shutdown())
_rxlo2->config_for_sync(false);
}
// 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);
// Set LO1 to IF of 2380 MHz (2440 MHz filter center minus 60 MHz offset to minimize LO leakage)
freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
// Set LO2 to IF minus desired frequency
freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
// Set LO1 to IF of 2440 (center of filter)
freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
// Set LO2 to IF minus desired frequency
freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
}
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);
freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
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);
freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
}
// To reduce the number of commands issued to the device, write to the
// SPI destination already addressed first. This avoids the writes to
// the GPIO registers to route the SPI to the same destination.
switch (get_gpio_field(SPI_ADDR))
{
case RXLO1:
_rxlo1->commit();
if (freq < (500*fMHz)) _rxlo2->commit();
write_cpld_reg();
break;
case RXLO2:
if (freq < (500*fMHz)) _rxlo2->commit();
_rxlo1->commit();
write_cpld_reg();
break;
default:
write_cpld_reg();
_rxlo1->commit();
if (freq < (500*fMHz)) _rxlo2->commit();
break;
}
if (cmd_time != uhd::time_spec_t(0.0) and _rxlo1->can_sync())
{
sync_phase(cmd_time, RX_DIRECTION);
}
_rx_freq = freq_lo1 - freq_lo2;
_rxlo1_freq = freq_lo1;
_rxlo2_freq = freq_lo2;
UHD_LOGV(rarely) << boost::format("UBX RX: the actual frequency is %f MHz") % (_rx_freq/1e6) << std::endl;
return _rx_freq;
}
/***********************************************************************
* Setting Modes
**********************************************************************/
void set_power_mode(std::string mode)
{
boost::mutex::scoped_lock lock(_mutex);
if (mode == "performance")
{
// performance mode attempts to reduce tuning and settling time
// as much as possible without adding noise.
// RXLNA2 has a ~100ms warm up time, so the LNAs are forced on
// here to reduce the settling time as much as possible. The
// force on signals are gated by the LNA selection so the LNAs
// are turned on/off during tuning. Unfortunately, that means
// there is still a long settling time when tuning from the high
// band (>1.5 GHz) to the low band (<1.5 GHz).
set_cpld_field(RXLNA1_FORCEON, 1);
set_cpld_field(RXLNA2_FORCEON, 1);
// Placeholders in case some components need to be forced on to
// reduce settling time. Note that some FORCEON lines are still gated
// by other bits in the CPLD register are 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, 0);
set_cpld_field(RXLO1_FORCEON, 1);
set_cpld_field(RXLO2_FORCEON, 1);
/*
//set_cpld_field(TXDRV_FORCEON, 1); // controlled by RX antenna selection
set_cpld_field(TXMOD_FORCEON, 0);
set_cpld_field(TXMIXER_FORCEON, 0);
set_cpld_field(TXLO1_FORCEON, 0);
set_cpld_field(TXLO2_FORCEON, 0);
*/
write_cpld_reg();
_power_mode = PERFORMANCE;
}
else if (mode == "powersave")
{
// powersave mode attempts to use the least amount of power possible
// by powering on components only when needed. Longer tuning and
// settling times are expected.
// Clear the LNA force on bits.
set_cpld_field(RXLNA1_FORCEON, 0);
set_cpld_field(RXLNA2_FORCEON, 0);
/*
// Placeholders in case other force on bits need to be set or cleared.
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(TXDRV_FORCEON, 1); // controlled by RX antenna selection
set_cpld_field(TXMOD_FORCEON, 0);
set_cpld_field(TXMIXER_FORCEON, 0);
set_cpld_field(TXLO1_FORCEON, 0);
set_cpld_field(TXLO2_FORCEON, 0);
*/
write_cpld_reg();
_power_mode = POWERSAVE;
}
}
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;
}
void set_sync_delay(bool is_tx, int64_t value)
{
if (is_tx)
_tx_sync_delay = value;
else
_rx_sync_delay = value;
}
/***********************************************************************
* Variables
**********************************************************************/
dboard_iface::sptr _iface;
boost::mutex _spi_mutex;
boost::mutex _mutex;
ubx_cpld_reg_t _cpld_reg;
uint32_t _prev_cpld_value;
std::map<ubx_gpio_field_id_t,ubx_gpio_field_info_t> _gpio_map;
boost::shared_ptr<max287x_iface> _txlo1;
boost::shared_ptr<max287x_iface> _txlo2;
boost::shared_ptr<max287x_iface> _rxlo1;
boost::shared_ptr<max287x_iface> _rxlo2;
double _tx_target_pfd_freq;
double _rx_target_pfd_freq;
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;
ubx_gpio_reg_t _tx_gpio_reg;
ubx_gpio_reg_t _rx_gpio_reg;
int64_t _tx_sync_delay;
int64_t _rx_sync_delay;
};
/***********************************************************************
* 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(UBX_PROTO_V3_RX_ID, UBX_PROTO_V3_TX_ID, &make_ubx, "UBX v0.3");
dboard_manager::register_dboard(UBX_PROTO_V4_RX_ID, UBX_PROTO_V4_TX_ID, &make_ubx, "UBX v0.4");
dboard_manager::register_dboard(UBX_V1_40MHZ_RX_ID, UBX_V1_40MHZ_TX_ID, &make_ubx, "UBX-40 v1");
dboard_manager::register_dboard(UBX_V1_160MHZ_RX_ID, UBX_V1_160MHZ_TX_ID, &make_ubx, "UBX-160 v1");
dboard_manager::register_dboard(UBX_V2_40MHZ_RX_ID, UBX_V2_40MHZ_TX_ID, &make_ubx, "UBX-40 v2");
dboard_manager::register_dboard(UBX_V2_160MHZ_RX_ID, UBX_V2_160MHZ_TX_ID, &make_ubx, "UBX-160 v2");
dboard_manager::register_dboard(UBX_LP_160MHZ_RX_ID, UBX_LP_160MHZ_TX_ID, &make_ubx, "UBX-160-LP");
}