Files
UHD-Fairwaves/host/umtrx_impl.cpp

1073 lines
47 KiB
C++

// Copyright 2012-2014 Fairwaves LLC
// Copyright 2010-2011 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/>.
//
#include "umtrx_impl.hpp"
#include "umtrx_regs.hpp"
#include "umtrx_version.hpp"
#include "cores/apply_corrections.hpp"
#include <uhd/utils/log.hpp>
#include <uhd/utils/msg.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp> //sleep
#include <boost/assign/list_of.hpp>
#include <boost/utility.hpp>
static int verbosity = 0;
using namespace uhd;
using namespace uhd::usrp;
using namespace uhd::transport;
namespace asio = boost::asio;
const umtrx_impl::pa_type_map_pair_t umtrx_impl::_pa_type_map[] = {
{umtrx_impl::PA_NONE, "NONE"}, // Also serves as the default
{umtrx_impl::PA_EPA881F40A, "EPA881F40A"},
{umtrx_impl::PA_EPA942H40A, "EPA942H40A"},
{umtrx_impl::PA_EPA1800F37A, "EPA1800F37A"}
};
// Values recommended by Andrey Sviyazov
const int umtrx_impl::UMTRX_VGA1_DEF = -20;
const int umtrx_impl::UMTRX_VGA2_DEF = 22;
static const double _dcdc_val_to_volt_init[256] =
{
9.300000, 9.320000, 9.340000, 9.320000, 9.360000, 9.380000, 9.380000, 9.400000,
9.400000, 9.400000, 9.440000, 9.440000, 9.460000, 9.480000, 9.500000, 9.500000,
9.500000, 9.520000, 9.540000, 9.560000, 9.580000, 9.580000, 9.580000, 9.600000,
9.620000, 9.620000, 9.640000, 9.660000, 9.660000, 9.680000, 9.700000, 9.720000,
9.760000, 9.760000, 9.760000, 9.760000, 9.800000, 9.800000, 9.820000, 9.840000,
9.840000, 9.860000, 9.880000, 9.900000, 9.920000, 9.920000, 9.960000, 9.960000,
9.960000, 10.000000, 10.000000, 10.040000, 10.040000, 10.060000, 10.080000, 10.100000,
10.120000, 10.140000, 10.160000, 10.180000, 10.200000, 10.240000, 10.240000, 10.260000,
10.280000, 10.300000, 10.320000, 10.320000, 10.360000, 10.380000, 10.400000, 10.400000,
10.420000, 10.440000, 10.460000, 10.500000, 10.520000, 10.540000, 10.560000, 10.580000,
10.600000, 10.620000, 10.640000, 10.660000, 10.680000, 10.720000, 10.760000, 10.760000,
10.780000, 10.820000, 10.840000, 10.860000, 10.880000, 10.900000, 10.940000, 10.960000,
11.000000, 11.000000, 11.020000, 11.060000, 11.080000, 11.120000, 11.140000, 11.180000,
11.200000, 11.220000, 11.260000, 11.280000, 11.320000, 11.360000, 11.380000, 11.400000,
11.420000, 11.460000, 11.500000, 11.520000, 11.560000, 11.580000, 11.620000, 11.640000,
11.680000, 11.700000, 11.740000, 11.780000, 11.780000, 11.860000, 11.860000, 11.920000,
11.920000, 11.980000, 11.980000, 12.020000, 12.080000, 12.120000, 12.140000, 12.200000,
12.200000, 12.240000, 12.300000, 12.340000, 12.400000, 12.440000, 12.480000, 12.500000,
12.520000, 12.560000, 12.600000, 12.640000, 12.680000, 12.760000, 12.780000, 12.840000,
12.880000, 12.940000, 12.980000, 13.040000, 13.100000, 13.120000, 13.180000, 13.240000,
13.240000, 13.300000, 13.360000, 13.440000, 13.500000, 13.540000, 13.620000, 13.680000,
13.720000, 13.760000, 13.820000, 13.860000, 13.920000, 13.980000, 14.080000, 14.140000,
14.180000, 14.240000, 14.320000, 14.380000, 14.420000, 14.480000, 14.560000, 14.640000,
14.720000, 14.800000, 14.900000, 14.980000, 15.060000, 15.100000, 15.180000, 15.280000,
15.340000, 15.400000, 15.460000, 15.600000, 15.660000, 15.740000, 15.860000, 15.960000,
16.020000, 16.120000, 16.200000, 16.320000, 16.420000, 16.520000, 16.640000, 16.760000,
16.820000, 16.900000, 17.040000, 17.140000, 17.260000, 17.360000, 17.500000, 17.620000,
17.760000, 17.880000, 18.020000, 18.160000, 18.300000, 18.440000, 18.580000, 18.640000,
18.640000, 18.620000, 18.640000, 18.620000, 18.640000, 18.620000, 18.640000, 18.640000,
18.640000, 18.640000, 18.640000, 18.640000, 18.640000, 18.640000, 18.620000, 18.640000,
18.620000, 18.640000, 18.620000, 18.640000, 18.640000, 18.620000, 18.640000, 18.620000,
18.640000, 18.620000, 18.640000, 18.620000, 18.640000, 18.620000, 18.640000, 18.620000
};
const std::vector<double> umtrx_impl::_dcdc_val_to_volt(_dcdc_val_to_volt_init, &_dcdc_val_to_volt_init[256]);
template<typename map_t>
static map_t map_reverse(map_t curve)
{
map_t reversed;
for (typename map_t::iterator i = curve.begin(); i != curve.end(); ++i)
reversed[i->second] = i->first;
return reversed;
}
const umtrx_impl::pa_curve_t umtrx_impl::_EPA942H40A_v2w_curve = boost::assign::map_list_of
(9.5, 1.15)
(10, 1.31)
(11, 1.6)
(12, 1.9)
(12.5, 2.1)
(13, 2.25)
(13.5, 2.44)
(14, 2.6)
(14.5, 2.8)
(15, 3.0)
(15.5, 3.2)
(16, 3.45)
(16.5, 3.7)
(17, 3.9)
(17.5, 4.1)
(18, 4.35)
(18.5, 4.6)
(19, 4.8)
(19.5, 5.1)
(20, 5.4)
(20.5, 5.65)
(21.1, 6.0)
(21.6, 6.2)
(22.1, 6.5)
(22.6, 6.8)
(23.1, 7.1)
(23.4, 7.25)
(23.7, 7.4)
(24, 7.55)
(24.2, 7.7)
(24.5, 7.9)
(24.8, 8.0)
(25.2, 8.25)
(25.5, 8.45)
(25.9, 8.65)
(26.2, 8.9)
(26.6, 9.1)
(28, 10.0);
const umtrx_impl::pa_curve_t umtrx_impl::_EPA942H40A_w2v_curve = map_reverse(umtrx_impl::_EPA942H40A_v2w_curve);
/***********************************************************************
* Make
**********************************************************************/
static device::sptr umtrx_make(const device_addr_t &device_addr){
return device::sptr(new umtrx_impl(device_addr));
}
device_addrs_t umtrx_find(const device_addr_t &hint);
UHD_STATIC_BLOCK(register_umtrx_device){
#ifdef UHD_HAS_DEVICE_FILTER
device::register_device(&umtrx_find, &umtrx_make, device::USRP);
#else
device::register_device(&umtrx_find, &umtrx_make);
#endif
}
/***********************************************************************
* MTU Discovery
**********************************************************************/
struct mtu_result_t{
size_t recv_mtu, send_mtu;
};
static std::vector<std::string> power_sensors =
boost::assign::list_of("PR1")("PF1")("PR2")("PF2");
static std::vector<std::string> dc_sensors =
boost::assign::list_of("zero")("Vin")("VinPA")("DCOUT");
static mtu_result_t determine_mtu(const std::string &addr, const mtu_result_t &user_mtu){
udp_simple::sptr udp_sock = udp_simple::make_connected(
addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT)
);
//The FPGA offers 4K buffers, and the user may manually request this.
//However, multiple simultaneous receives (2DSP slave + 2DSP master),
//require that buffering to be used internally, and this is a safe setting.
std::vector<boost::uint8_t> buffer(std::max(user_mtu.recv_mtu, user_mtu.send_mtu));
usrp2_ctrl_data_t *ctrl_data = reinterpret_cast<usrp2_ctrl_data_t *>(&buffer.front());
static const double echo_timeout = 0.020; //20 ms
//test holler - check if its supported in this fw version
ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO);
ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM);
ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t));
udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t)));
udp_sock->recv(boost::asio::buffer(buffer), echo_timeout);
if (ntohl(ctrl_data->id) != USRP2_CTRL_ID_HOLLER_BACK_DUDE)
throw uhd::not_implemented_error("holler protocol not implemented");
size_t min_recv_mtu = sizeof(usrp2_ctrl_data_t), max_recv_mtu = user_mtu.recv_mtu;
size_t min_send_mtu = sizeof(usrp2_ctrl_data_t), max_send_mtu = user_mtu.send_mtu;
while (min_recv_mtu < max_recv_mtu){
size_t test_mtu = (max_recv_mtu/2 + min_recv_mtu/2 + 3) & ~3;
ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO);
ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM);
ctrl_data->data.echo_args.len = htonl(test_mtu);
udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t)));
size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout);
if (len >= test_mtu) min_recv_mtu = test_mtu;
else max_recv_mtu = test_mtu - 4;
}
while (min_send_mtu < max_send_mtu){
size_t test_mtu = (max_send_mtu/2 + min_send_mtu/2 + 3) & ~3;
ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO);
ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM);
ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t));
udp_sock->send(boost::asio::buffer(buffer, test_mtu));
size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout);
if (len >= sizeof(usrp2_ctrl_data_t)) len = ntohl(ctrl_data->data.echo_args.len);
if (len >= test_mtu) min_send_mtu = test_mtu;
else max_send_mtu = test_mtu - 4;
}
mtu_result_t mtu;
mtu.recv_mtu = min_recv_mtu;
mtu.send_mtu = min_send_mtu;
return mtu;
}
/***********************************************************************
* Structors
**********************************************************************/
umtrx_impl::umtrx_impl(const device_addr_t &device_addr)
{
_device_ip_addr = device_addr["addr"];
UHD_MSG(status) << "UmTRX driver version: " << UMTRX_VERSION << std::endl;
UHD_MSG(status) << "Opening a UmTRX device... " << _device_ip_addr << std::endl;
//mtu self check -- not really doing anything with it
mtu_result_t user_mtu;
user_mtu.recv_mtu = size_t(device_addr.cast<double>("recv_frame_size", udp_simple::mtu));
user_mtu.send_mtu = size_t(device_addr.cast<double>("send_frame_size", udp_simple::mtu));
user_mtu = determine_mtu(_device_ip_addr, user_mtu);
UHD_VAR(user_mtu.recv_mtu);
UHD_VAR(user_mtu.send_mtu);
////////////////////////////////////////////////////////////////////
// create controller objects and initialize the properties tree
////////////////////////////////////////////////////////////////////
_tree = property_tree::make();
_tree->create<std::string>("/name").set("UmTRX Device");
const fs_path mb_path = "/mboards/0";
////////////////////////////////////////////////////////////////
// create the iface that controls i2c, spi, uart, and wb
////////////////////////////////////////////////////////////////
_iface = umtrx_iface::make(udp_simple::make_connected(
_device_ip_addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT)
));
_tree->create<std::string>(mb_path / "name").set(_iface->get_cname());
_tree->create<std::string>(mb_path / "fw_version").set(_iface->get_fw_version_string());
//get access to the various interfaces
_tree->create<uhd::wb_iface::sptr>(mb_path / "wb_iface").set(_iface);
_tree->create<uhd::spi_iface::sptr>(mb_path / "spi_iface").set(_iface);
_tree->create<uhd::i2c_iface::sptr>(mb_path / "i2c_iface").set(_iface);
//check the fpga compatibility number
const boost::uint32_t fpga_compat_num = _iface->peek32(U2_REG_COMPAT_NUM_RB);
const boost::uint16_t fpga_major = fpga_compat_num >> 16, fpga_minor = fpga_compat_num & 0xffff;
if (fpga_major != USRP2_FPGA_COMPAT_NUM){
throw uhd::runtime_error(str(boost::format(
"\nPlease update the firmware and FPGA images for your device.\n"
"See the application notes for UmTRX for instructions.\n"
"Expected FPGA compatibility number %d, but got %d:\n"
"The FPGA build is not compatible with the host code build."
) % int(USRP2_FPGA_COMPAT_NUM) % fpga_major));
}
_tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % fpga_major % fpga_minor));
//lock the device/motherboard to this process
_iface->lock_device(true);
////////////////////////////////////////////////////////////////
// high performance settings control
////////////////////////////////////////////////////////////////
_iface->poke32(U2_REG_MISC_CTRL_SFC_CLEAR, 1); //clear settings fifo control state machine
const size_t fifo_ctrl_window(device_addr.cast<size_t>("fifo_ctrl_window", 1024)); //default gets clipped to hardware maximum
_ctrl = umtrx_fifo_ctrl::make(this->make_xport(UMTRX_CTRL_FRAMER, device_addr_t()), UMTRX_CTRL_SID, fifo_ctrl_window);
_ctrl->peek32(0); //test readback
_tree->create<time_spec_t>(mb_path / "time/cmd")
.subscribe(boost::bind(&umtrx_fifo_ctrl::set_time, _ctrl, _1));
_tree->create<double>(mb_path / "tick_rate")
.subscribe(boost::bind(&umtrx_fifo_ctrl::set_tick_rate, _ctrl, _1));
////////////////////////////////////////////////////////////////////
// setup the mboard eeprom
////////////////////////////////////////////////////////////////////
_tree->create<mboard_eeprom_t>(mb_path / "eeprom")
.set(_iface->mb_eeprom)
.subscribe(boost::bind(&umtrx_impl::set_mb_eeprom, this, _iface, _1));
////////////////////////////////////////////////////////////////
// create clock control objects
////////////////////////////////////////////////////////////////
_tree->access<double>(mb_path / "tick_rate")
.publish(boost::bind(&umtrx_impl::get_master_clock_rate, this))
.subscribe(boost::bind(&umtrx_impl::update_tick_rate, this, _1));
_tree->create<double>(mb_path / "dsp_rate")
.publish(boost::bind(&umtrx_impl::get_master_dsp_rate, this));
////////////////////////////////////////////////////////////////
// reset LMS chips
////////////////////////////////////////////////////////////////
_iface->poke32(U2_REG_MISC_LMS_RES, LMS1_RESET | LMS2_RESET);
_iface->poke32(U2_REG_MISC_LMS_RES, 0);
_iface->poke32(U2_REG_MISC_LMS_RES, LMS1_RESET | LMS2_RESET);
////////////////////////////////////////////////////////////////////////
// autodetect umtrx hardware rev and initialize rev. specific sensors
////////////////////////////////////////////////////////////////////////
detect_hw_rev(mb_path);
_tree->create<std::string>(mb_path / "hwrev").set(get_hw_rev());
UHD_MSG(status) << "Detected UmTRX " << get_hw_rev() << std::endl;
_tree->create<bool>(mb_path / "divsw1")
.subscribe(boost::bind(&umtrx_impl::set_divsw1, this, _1));
_tree->create<bool>(mb_path / "divsw2")
.subscribe(boost::bind(&umtrx_impl::set_divsw2, this, _1));
// TODO: Add EEPROM cell to manually override this
_pll_div = 1;
////////////////////////////////////////////////////////////////////
// get the atached PA/LNA type
////////////////////////////////////////////////////////////////////
_pa_type = pa_str_to_type(device_addr.cast<std::string>("pa", "NONE"));
if (_hw_rev < UMTRX_VER_2_3_1 and get_pa_type() != PA_NONE)
{
UHD_MSG(error) << "PA type " << get_pa_type_str() << " is not supported for UmTRX "
<< get_hw_rev() << ". Setting PA type to NONE." << std::endl;
_pa_type = PA_NONE;
}
UHD_MSG(status) << "Installed PA: " << get_pa_type_str() << std::endl;
////////////////////////////////////////////////////////////////////
// create codec control objects
////////////////////////////////////////////////////////////////////
for (char name = 'A'; name <= 'B'; name++)
{
const fs_path rx_codec_path = mb_path / ("rx_codecs") / std::string(1, name);
_tree->create<std::string>(rx_codec_path / "name").set("RX LMS ADC");
_tree->create<int>(rx_codec_path / "gains"); //empty cuz gains are in frontend
const fs_path tx_codec_path = mb_path / ("tx_codecs") / std::string(1, name);
_tree->create<std::string>(tx_codec_path / "name").set("TX LMS DAC");
_tree->create<int>(tx_codec_path / "gains"); //empty cuz gains are in frontend
}
////////////////////////////////////////////////////////////////
// create frontend control objects
////////////////////////////////////////////////////////////////
_rx_fes.resize(2);
_tx_fes.resize(2);
_rx_fes[0] = rx_frontend_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_RX_FRONT0));
_rx_fes[1] = rx_frontend_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_RX_FRONT1));
_tx_fes[0] = tx_frontend_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_TX_FRONT0));
_tx_fes[1] = tx_frontend_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_TX_FRONT1));
_tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
.subscribe(boost::bind(&umtrx_impl::update_rx_subdev_spec, this, _1));
_tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
.subscribe(boost::bind(&umtrx_impl::update_tx_subdev_spec, this, _1));
for (char name = 'A'; name <= 'B'; name++)
{
const std::string fe_name = std::string(1, name);
const fs_path rx_fe_path = mb_path / "rx_frontends" / fe_name;
const fs_path tx_fe_path = mb_path / "tx_frontends" / fe_name;
const rx_frontend_core_200::sptr rx_fe = (fe_name=="A")?_rx_fes[0]:_rx_fes[1];
const tx_frontend_core_200::sptr tx_fe = (fe_name=="A")?_tx_fes[0]:_tx_fes[1];
tx_fe->set_mux("IQ");
rx_fe->set_mux(false/*no swap*/);
_tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value")
.coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, rx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<bool>(rx_fe_path / "dc_offset" / "enable")
.subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, rx_fe, _1))
.set(true);
_tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value")
.subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, rx_fe, _1))
.set(std::polar<double>(0.0, 0.0));
/*
_tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value")
.coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, tx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
*/
_tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value")
.subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, tx_fe, _1))
.set(std::polar<double>(0.0, 0.0));
}
////////////////////////////////////////////////////////////////
// create rx dsp control objects
////////////////////////////////////////////////////////////////
_rx_dsps.resize(_iface->peek32(U2_REG_NUM_DDC));
if (_rx_dsps.size() < 2) throw uhd::runtime_error(str(boost::format("umtrx rx_dsps %u -- (unsupported FPGA image?)") % _rx_dsps.size()));
if (_rx_dsps.size() > 0) _rx_dsps[0] = rx_dsp_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_RX_DSP0), U2_REG_SR_ADDR(SR_RX_CTRL0), UMTRX_DSP_RX0_SID, true);
if (_rx_dsps.size() > 1) _rx_dsps[1] = rx_dsp_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_RX_DSP1), U2_REG_SR_ADDR(SR_RX_CTRL1), UMTRX_DSP_RX1_SID, true);
if (_rx_dsps.size() > 2) _rx_dsps[2] = rx_dsp_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_RX_DSP2), U2_REG_SR_ADDR(SR_RX_CTRL2), UMTRX_DSP_RX2_SID, true);
if (_rx_dsps.size() > 3) _rx_dsps[3] = rx_dsp_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_RX_DSP3), U2_REG_SR_ADDR(SR_RX_CTRL3), UMTRX_DSP_RX3_SID, true);
_tree->create<sensor_value_t>(mb_path / "rx_dsps"); //phony property so this dir exists
for (size_t dspno = 0; dspno < _rx_dsps.size(); dspno++){
_rx_dsps[dspno]->set_mux("IQ", false/*no swap*/);
_rx_dsps[dspno]->set_link_rate(UMTRX_LINK_RATE_BPS);
_tree->access<double>(mb_path / "dsp_rate")
.subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1));
_tree->access<double>(mb_path / "tick_rate")
.subscribe(boost::bind(&rx_dsp_core_200::set_vita_rate, _rx_dsps[dspno], _1));
fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno);
_tree->create<meta_range_t>(rx_dsp_path / "rate/range")
.publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno]));
_tree->create<double>(rx_dsp_path / "rate/value")
.set(this->get_master_clock_rate()/12) //some default
.coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1))
.subscribe(boost::bind(&umtrx_impl::update_rx_samp_rate, this, dspno, _1));
_tree->create<double>(rx_dsp_path / "freq/value")
.coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1));
_tree->create<meta_range_t>(rx_dsp_path / "freq/range")
.publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno]));
_tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
.subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1));
}
////////////////////////////////////////////////////////////////
// create tx dsp control objects
////////////////////////////////////////////////////////////////
_tx_dsps.resize(_iface->peek32(U2_REG_NUM_DUC));
if (_tx_dsps.empty()) _tx_dsps.resize(1); //uhd cant support empty sides
if (_tx_dsps.size() > 0) _tx_dsps[0] = tx_dsp_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_TX_DSP0), U2_REG_SR_ADDR(SR_TX_CTRL0), UMTRX_DSP_TX0_SID);
if (_tx_dsps.size() > 1) _tx_dsps[1] = tx_dsp_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_TX_DSP1), U2_REG_SR_ADDR(SR_TX_CTRL1), UMTRX_DSP_TX1_SID);
_tree->create<sensor_value_t>(mb_path / "tx_dsps"); //phony property so this dir exists
for (size_t dspno = 0; dspno < _tx_dsps.size(); dspno++){
_tx_dsps[dspno]->set_link_rate(UMTRX_LINK_RATE_BPS);
_tree->access<double>(mb_path / "dsp_rate")
.subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsps[dspno], _1));
fs_path tx_dsp_path = mb_path / str(boost::format("tx_dsps/%u") % dspno);
_tree->create<meta_range_t>(tx_dsp_path / "rate/range")
.publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsps[dspno]));
_tree->create<double>(tx_dsp_path / "rate/value")
.set(this->get_master_clock_rate()/12) //some default
.coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsps[dspno], _1))
.subscribe(boost::bind(&umtrx_impl::update_tx_samp_rate, this, dspno, _1));
_tree->create<double>(tx_dsp_path / "freq/value")
.coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsps[dspno], _1));
_tree->create<meta_range_t>(tx_dsp_path / "freq/range")
.publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsps[dspno]));
}
////////////////////////////////////////////////////////////////
// create time control objects
////////////////////////////////////////////////////////////////
time64_core_200::readback_bases_type time64_rb_bases;
time64_rb_bases.rb_hi_now = U2_REG_TIME64_HI_RB_IMM;
time64_rb_bases.rb_lo_now = U2_REG_TIME64_LO_RB_IMM;
time64_rb_bases.rb_hi_pps = U2_REG_TIME64_HI_RB_PPS;
time64_rb_bases.rb_lo_pps = U2_REG_TIME64_LO_RB_PPS;
_time64 = time64_core_200::make(_ctrl, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases);
_tree->access<double>(mb_path / "tick_rate")
.subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1));
_tree->create<time_spec_t>(mb_path / "time" / "now")
.publish(boost::bind(&time64_core_200::get_time_now, _time64))
.subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1));
_tree->create<time_spec_t>(mb_path / "time" / "pps")
.publish(boost::bind(&time64_core_200::get_time_last_pps, _time64))
.subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1));
//setup time source props
_tree->create<std::string>(mb_path / "time_source" / "value")
.subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1));
_tree->create<std::vector<std::string> >(mb_path / "time_source" / "options")
.publish(boost::bind(&time64_core_200::get_time_sources, _time64));
//setup reference source props
_tree->create<std::string>(mb_path / "clock_source" / "value")
.subscribe(boost::bind(&umtrx_impl::update_clock_source, this, _1));
static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external");
_tree->create<std::vector<std::string> >(mb_path / "clock_source"/ "options").set(clock_sources);
////////////////////////////////////////////////////////////////////
// create RF frontend interfacing
////////////////////////////////////////////////////////////////////
_lms_ctrl["A"] = lms6002d_ctrl::make(_ctrl/*spi*/, SPI_SS_LMS1, SPI_SS_AUX1, this->get_master_clock_rate() / _pll_div);
_lms_ctrl["B"] = lms6002d_ctrl::make(_ctrl/*spi*/, SPI_SS_LMS2, SPI_SS_AUX2, this->get_master_clock_rate() / _pll_div);
// LMS dboard do not have physical eeprom so we just hardcode values from host/lib/usrp/dboard/db_lms.cpp
dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_db_eeprom;
rx_db_eeprom.id = 0xfa07;
rx_db_eeprom.revision = _iface->mb_eeprom["revision"];
tx_db_eeprom.id = 0xfa09;
tx_db_eeprom.revision = _iface->mb_eeprom["revision"];
BOOST_FOREACH(const std::string &fe_name, _lms_ctrl.keys())
{
lms6002d_ctrl::sptr ctrl = _lms_ctrl[fe_name];
const fs_path rx_rf_fe_path = mb_path / "dboards" / fe_name / "rx_frontends" / "0";
const fs_path tx_rf_fe_path = mb_path / "dboards" / fe_name / "tx_frontends" / "0";
_tree->create<std::string>(rx_rf_fe_path / "name").set("LMS6002D");
_tree->create<std::string>(tx_rf_fe_path / "name").set("LMS6002D");
// Different serial numbers for each LMS on a UmTRX.
// This is required to properly correlate calibration files to LMS chips.
rx_db_eeprom.serial = _iface->mb_eeprom["serial"] + "." + fe_name;
tx_db_eeprom.serial = _iface->mb_eeprom["serial"] + "." + fe_name;
_tree->create<dboard_eeprom_t>(mb_path / "dboards" / fe_name / "rx_eeprom")
.set(rx_db_eeprom);
_tree->create<dboard_eeprom_t>(mb_path / "dboards" / fe_name / "tx_eeprom")
.set(tx_db_eeprom);
_tree->create<dboard_eeprom_t>(mb_path / "dboards" / fe_name / "gdb_eeprom")
.set(gdb_db_eeprom);
//sensors -- always say locked
_tree->create<sensor_value_t>(rx_rf_fe_path / "sensors" / "lo_locked")
.publish(boost::bind(&lms6002d_ctrl::get_rx_pll_locked, ctrl));
_tree->create<sensor_value_t>(tx_rf_fe_path / "sensors" / "lo_locked")
.publish(boost::bind(&lms6002d_ctrl::get_tx_pll_locked, ctrl));
//rx gains
BOOST_FOREACH(const std::string &name, ctrl->get_rx_gains())
{
_tree->create<meta_range_t>(rx_rf_fe_path / "gains" / name / "range")
.publish(boost::bind(&lms6002d_ctrl::get_rx_gain_range, ctrl, name));
_tree->create<double>(rx_rf_fe_path / "gains" / name / "value")
.coerce(boost::bind(&lms6002d_ctrl::set_rx_gain, ctrl, _1, name))
.set((ctrl->get_rx_gain_range(name).start() + ctrl->get_rx_gain_range(name).stop())/2.0);
}
//tx gains
if (get_pa_type() == PA_NONE)
{
// Use internal LMS gain control if we don't have a PA
BOOST_FOREACH(const std::string &name, ctrl->get_tx_gains())
{
_tree->create<meta_range_t>(tx_rf_fe_path / "gains" / name / "range")
.publish(boost::bind(&lms6002d_ctrl::get_tx_gain_range, ctrl, name));
_tree->create<double>(tx_rf_fe_path / "gains" / name / "value")
.coerce(boost::bind(&lms6002d_ctrl::set_tx_gain, ctrl, _1, name))
.set((ctrl->get_tx_gain_range(name).start() + ctrl->get_tx_gain_range(name).stop())/2.0);
}
} else {
// Set LMS internal VGA gains to optimal values
ctrl->set_tx_gain(UMTRX_VGA1_DEF, "VGA1");
ctrl->set_tx_gain(UMTRX_VGA2_DEF, "VGA2");
// Use PA control to control output power
_tree->create<meta_range_t>(tx_rf_fe_path / "gains" / "PA" / "range")
.publish(boost::bind(&umtrx_impl::get_pa_power_range, this));
_tree->create<double>(tx_rf_fe_path / "gains" / "PA" / "value")
.coerce(boost::bind(&umtrx_impl::set_pa_power, this, _1))
// Set default output power to maximum
.set(get_pa_power_range().stop());
}
//rx freq
_tree->create<double>(rx_rf_fe_path / "freq" / "value")
.coerce(boost::bind(&lms6002d_ctrl::set_rx_freq, ctrl, _1));
_tree->create<meta_range_t>(rx_rf_fe_path / "freq" / "range")
.publish(boost::bind(&lms6002d_ctrl::get_rx_freq_range, ctrl));
_tree->create<bool>(rx_rf_fe_path / "use_lo_offset").set(false);
//tx freq
_tree->create<double>(tx_rf_fe_path / "freq" / "value")
.coerce(boost::bind(&lms6002d_ctrl::set_tx_freq, ctrl, _1));
_tree->create<meta_range_t>(tx_rf_fe_path / "freq" / "range")
.publish(boost::bind(&lms6002d_ctrl::get_tx_freq_range, ctrl));
_tree->create<bool>(tx_rf_fe_path / "use_lo_offset").set(false);
//rx ant
_tree->create<std::vector<std::string> >(rx_rf_fe_path / "antenna" / "options")
.publish(boost::bind(&lms6002d_ctrl::get_rx_antennas, ctrl));
_tree->create<std::string>(rx_rf_fe_path / "antenna" / "value")
.subscribe(boost::bind(&lms6002d_ctrl::set_rx_ant, ctrl, _1))
.set("RX1");
//tx ant
_tree->create<std::vector<std::string> >(tx_rf_fe_path / "antenna" / "options")
.publish(boost::bind(&lms6002d_ctrl::get_tx_antennas, ctrl));
_tree->create<std::string>(tx_rf_fe_path / "antenna" / "value")
.subscribe(boost::bind(&lms6002d_ctrl::set_tx_ant, ctrl, _1))
.set("TX2");
//misc
_tree->create<std::string>(rx_rf_fe_path / "connection").set("IQ");
_tree->create<std::string>(tx_rf_fe_path / "connection").set("IQ");
_tree->create<bool>(rx_rf_fe_path / "enabled")
.coerce(boost::bind(&lms6002d_ctrl::set_rx_enabled, ctrl, _1));
_tree->create<bool>(tx_rf_fe_path / "enabled")
.coerce(boost::bind(&lms6002d_ctrl::set_tx_enabled, ctrl, _1));
//rx bw
_tree->create<double>(rx_rf_fe_path / "bandwidth" / "value")
.coerce(boost::bind(&lms6002d_ctrl::set_rx_bandwidth, ctrl, _1))
.set(2*0.75e6);
_tree->create<meta_range_t>(rx_rf_fe_path / "bandwidth" / "range")
.publish(boost::bind(&lms6002d_ctrl::get_rx_bw_range, ctrl));
//tx bw
_tree->create<double>(tx_rf_fe_path / "bandwidth" / "value")
.coerce(boost::bind(&lms6002d_ctrl::set_tx_bandwidth, ctrl, _1))
.set(2*0.75e6);
_tree->create<meta_range_t>(tx_rf_fe_path / "bandwidth" / "range")
.publish(boost::bind(&lms6002d_ctrl::get_tx_bw_range, ctrl));
//bind frontend corrections to the dboard freq props
_tree->access<double>(tx_rf_fe_path / "freq" / "value")
.set(0.0) //default value
.subscribe(boost::bind(&umtrx_impl::set_tx_fe_corrections, this, "0", fe_name, _1));
_tree->access<double>(rx_rf_fe_path / "freq" / "value")
.set(0.0) //default value
.subscribe(boost::bind(&umtrx_impl::set_rx_fe_corrections, this, "0", fe_name, _1));
//tx cal props
_tree->create<uint8_t>(tx_rf_fe_path / "lms6002d" / "tx_dc_i" / "value")
.subscribe(boost::bind(&lms6002d_ctrl::_set_tx_vga1dc_i_int, ctrl, _1))
.publish(boost::bind(&lms6002d_ctrl::get_tx_vga1dc_i_int, ctrl));
_tree->create<uint8_t>(tx_rf_fe_path / "lms6002d" / "tx_dc_q" / "value")
.subscribe(boost::bind(&lms6002d_ctrl::_set_tx_vga1dc_q_int, ctrl, _1))
.publish(boost::bind(&lms6002d_ctrl::get_tx_vga1dc_q_int, ctrl));
//set Tx DC calibration values, which are read from mboard EEPROM
std::string tx_name = (fe_name=="A")?"tx1":"tx2";
const std::string dc_i_str = _iface->mb_eeprom.get(tx_name+"-vga1-dc-i", "");
const std::string dc_q_str = _iface->mb_eeprom.get(tx_name+"-vga1-dc-q", "");
double dc_i = dc_i_str.empty() ? 0.0 : dc_offset_int2double(boost::lexical_cast<int>(dc_i_str));
double dc_q = dc_q_str.empty() ? 0.0 : dc_offset_int2double(boost::lexical_cast<int>(dc_q_str));
//plugin dc_offset from lms into the frontend corrections
_tree->create<std::complex<double> >(mb_path / "tx_frontends" / fe_name / "dc_offset" / "value")
.publish(boost::bind(&umtrx_impl::get_dc_offset_correction, this, fe_name))
.subscribe(boost::bind(&umtrx_impl::set_dc_offset_correction, this, fe_name, _1))
.set(std::complex<double>(dc_i, dc_q));
}
//set TCXO DAC calibration value, which is read from mboard EEPROM
const std::string tcxo_dac = _iface->mb_eeprom.get("tcxo-dac", "");
if (not tcxo_dac.empty()) _tree->create<uint16_t>(mb_path / "tcxo_dac" / "value")
.subscribe(boost::bind(&umtrx_impl::set_tcxo_dac, this, _iface, _1))
.set(boost::lexical_cast<uint16_t>(tcxo_dac));
////////////////////////////////////////////////////////////////////
// post config tasks
////////////////////////////////////////////////////////////////////
_tree->access<double>(mb_path / "tick_rate")
.set(this->get_master_clock_rate());
_tree->access<double>(mb_path / "dsp_rate")
.set(this->get_master_dsp_rate());
this->time64_self_test();
//reset cordic rates and their properties to zero
BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps"))
{
_tree->access<double>(mb_path / "rx_dsps" / name / "freq" / "value").set(0.0);
}
BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps"))
{
_tree->access<double>(mb_path / "tx_dsps" / name / "freq" / "value").set(0.0);
}
_rx_streamers.resize(_rx_dsps.size());
_tx_streamers.resize(_tx_dsps.size());
subdev_spec_t rx_spec("A:0 B:0 A:0 B:0");
rx_spec.resize(_rx_dsps.size());
_tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec);
subdev_spec_t tx_spec("A:0 B:0 A:0 B:0");
tx_spec.resize(_tx_dsps.size());
_tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec);
_tree->access<std::string>(mb_path / "clock_source" / "value").set("internal");
_tree->access<std::string>(mb_path / "time_source" / "value").set("none");
}
umtrx_impl::~umtrx_impl(void)
{
BOOST_FOREACH(const std::string &fe_name, _lms_ctrl.keys())
{
lms6002d_ctrl::sptr ctrl = _lms_ctrl[fe_name];
try
{
ctrl->set_rx_enabled(false);
ctrl->set_tx_enabled(false);
}
catch (...){}
}
}
int umtrx_impl::volt_to_dcdc_r(double v)
{
if (v <= _dcdc_val_to_volt[0])
return 0;
else if (v >= _dcdc_val_to_volt[255])
return 255;
else
return std::lower_bound(_dcdc_val_to_volt.begin(), _dcdc_val_to_volt.end(), v) - _dcdc_val_to_volt.begin();
}
std::string umtrx_impl::pa_type_to_str(pa_type pa)
{
BOOST_FOREACH(const umtrx_impl::pa_type_map_pair_t &pa_map_pair, _pa_type_map)
{
if (pa_map_pair.type == pa)
return pa_map_pair.name;
}
throw uhd::environment_error("Can't map PA type to a string.");
return "NONE";
}
umtrx_impl::pa_type umtrx_impl::pa_str_to_type(std::string pa_str)
{
BOOST_FOREACH(const umtrx_impl::pa_type_map_pair_t &pa_map_pair, _pa_type_map)
{
if (pa_map_pair.name == pa_str)
return pa_map_pair.type;
}
UHD_MSG(error) << "PA name " << pa_str << " is not recognized. "
<< "Setting PA type to NONE." << std::endl;
return umtrx_impl::PA_NONE;
}
void umtrx_impl::set_pa_dcdc_r(uint8_t val)
{
// AD5245 control
if (_hw_rev >= UMTRX_VER_2_3_1)
{
_pa_dcdc_r = val;
_iface->write_i2c(BOOST_BINARY(0101100), boost::assign::list_of(0)(val));
}
}
double umtrx_impl::pa_interpolate_curve(const pa_curve_t &curve, double v)
{
pa_curve_t::const_iterator i = curve.upper_bound(v);
if (i == curve.end())
{
return (--i)->second;
}
if (i == curve.begin())
{
return i->second;
}
pa_curve_t::const_iterator l=i;
--l;
const double delta=(v - l->first) / (i->first - l->first);
return delta*i->second + (1-delta)*l->second;
}
const umtrx_impl::pa_curve_t &umtrx_impl::get_pa_curve(bool w2v) const
{
switch (get_pa_type())
{
case PA_NONE:
throw uhd::key_error("umtrx_impl::get_pa_curve() must not be called with PA type NONE");
case PA_EPA881F40A:
case PA_EPA942H40A:
case PA_EPA1800F37A:
if (w2v)
return _EPA942H40A_w2v_curve;
else
return _EPA942H40A_v2w_curve;
default:
throw uhd::key_error("Unknown PA type in umtrx_impl::get_pa_curve()");
}
}
uhd::gain_range_t umtrx_impl::get_pa_power_range() const
{
const umtrx_impl::pa_curve_t &curve = get_pa_curve(false);
return uhd::gain_range_t(curve.begin()->second, curve.rbegin()->second, 0.1);
}
double umtrx_impl::set_pa_power(double power)
{
// TODO:: Use DCDC bypass for maximum output power
// TODO:: Limit output power for UmSITE-TM3
// Find voltage required for the requested output power
double v = pa_interpolate_curve(get_pa_curve(true), power);
uint8_t dcdc_val = volt_to_dcdc_r(v);
// Set the value
set_pa_dcdc_r(dcdc_val);
// Check what power do we actually have by reading the DCDC voltage
// and converting it to the PA power
double v_actual = read_dc_v("DCOUT").to_real();
double w_actual = pa_interpolate_curve(get_pa_curve(false), v_actual);
// TODO:: Check that power is actually there by reading VSWR sensor.
UHD_MSG(status) << "Setting PA power: Requested=" << power << "W "
<< "(" << v << "V dcdc_r=" << int(dcdc_val) << "). Actual=" << w_actual <<"W "
<< "(" << v_actual << "V)" << std::endl;
return w_actual;
}
void umtrx_impl::set_mb_eeprom(const uhd::i2c_iface::sptr &iface, const uhd::usrp::mboard_eeprom_t &eeprom)
{
store_umtrx_eeprom(eeprom, *iface);
}
void umtrx_impl::time64_self_test(void)
{
//check the the ticks elapsed across a sleep is within an expected range
//this proves that the clock is the correct rate and not off by a factor
UHD_MSG(status) << "Time register self-test... " << std::flush;
const time_spec_t t0 = _time64->get_time_now();
const double sleepTime = 0.50;
boost::this_thread::sleep(boost::posix_time::milliseconds(long(sleepTime*1000)));
const time_spec_t t1 = _time64->get_time_now();
const double secs_elapsed = (t1 - t0).get_real_secs();
const bool within_range = (secs_elapsed < (1.5)*sleepTime and secs_elapsed > (0.5)*sleepTime);
UHD_MSG(status) << (within_range? "pass" : "fail") << std::endl;
}
void umtrx_impl::update_clock_source(const std::string &){}
void umtrx_impl::set_rx_fe_corrections(const std::string &mb, const std::string &board, const double lo_freq){
apply_rx_fe_corrections(this->get_tree()->subtree("/mboards/" + mb), board, lo_freq);
}
void umtrx_impl::set_tx_fe_corrections(const std::string &mb, const std::string &board, const double lo_freq){
apply_tx_fe_corrections(this->get_tree()->subtree("/mboards/" + mb), board, lo_freq);
}
void umtrx_impl::set_tcxo_dac(const umtrx_iface::sptr &iface, const uint16_t val){
if (verbosity>0) printf("umtrx_impl::set_tcxo_dac(%d)\n", val);
iface->send_zpu_action(UMTRX_ZPU_REQUEST_SET_VCTCXO_DAC, val);
}
uint16_t umtrx_impl::get_tcxo_dac(const umtrx_iface::sptr &iface){
uint16_t val = iface->send_zpu_action(UMTRX_ZPU_REQUEST_GET_VCTCXO_DAC, 0);
if (verbosity>0) printf("umtrx_impl::get_tcxo_dac(): %d\n", val);
return (uint16_t)val;
}
std::complex<double> umtrx_impl::get_dc_offset_correction(const std::string &which) const
{
return std::complex<double>(
dc_offset_int2double(_lms_ctrl[which]->get_tx_vga1dc_i_int()),
dc_offset_int2double(_lms_ctrl[which]->get_tx_vga1dc_q_int()));
}
void umtrx_impl::set_dc_offset_correction(const std::string &which, const std::complex<double> &corr)
{
_lms_ctrl[which]->_set_tx_vga1dc_i_int(dc_offset_double2int(corr.real()));
_lms_ctrl[which]->_set_tx_vga1dc_q_int(dc_offset_double2int(corr.imag()));
}
double umtrx_impl::dc_offset_int2double(uint8_t corr)
{
return (corr-128)/128.0;
}
uint8_t umtrx_impl::dc_offset_double2int(double corr)
{
return (int)(corr*128 + 128.5);
}
uhd::sensor_value_t umtrx_impl::read_temp_c(const std::string &which)
{
double temp = (which == "A") ? _temp_side_a.get_temp() :
_temp_side_b.get_temp();
return uhd::sensor_value_t("Temp"+which, temp, "C");
}
uhd::sensor_value_t umtrx_impl::read_pa_v(const std::string &which)
{
unsigned i;
for (i = 0; i < 4; i++) {
if (which == power_sensors[i])
break;
}
UHD_ASSERT_THROW(i < 4);
_sense_pwr.set_input((ads1015_ctrl::ads1015_input)
(ads1015_ctrl::ADS1015_CONF_AIN0_GND + i));
double val = _sense_pwr.get_value() * 10;
return uhd::sensor_value_t("Voltage"+which, val, "V");
}
uhd::sensor_value_t umtrx_impl::read_dc_v(const std::string &which)
{
unsigned i;
for (i = 0; i < 4; i++) {
if (which == dc_sensors[i])
break;
}
UHD_ASSERT_THROW(i < 4);
_sense_dc.set_input((ads1015_ctrl::ads1015_input)
(ads1015_ctrl::ADS1015_CONF_AIN0_GND + i));
double val = _sense_dc.get_value() * 40;
return uhd::sensor_value_t("Voltage"+which, val, "V");
}
void umtrx_impl::detect_hw_rev(const fs_path& mb_path)
{
//UmTRX v2.0 doesn't have temp sensors
//UmTRX v2.1 has a temp sensor only on A side
//UmTRX v2.2 has both temp sensor
//UmTRX v2.3.0 has power sensors ADC
//UmTRX v2.3.1 has power supply ADC & programmed resistor array
if (!tmp102_ctrl::check(_iface, tmp102_ctrl::TMP102_SDA)) {
_tree->create<sensor_value_t>(mb_path / "sensors"); //phony property so this dir exists
_hw_rev = UMTRX_VER_2_0;
return;
}
// Initialize side A temp sensor
_temp_side_a.init(_iface, tmp102_ctrl::TMP102_SDA);
_temp_side_a.set_ex_mode(true);
_tree->create<sensor_value_t>(mb_path / "sensors" / "tempA")
.publish(boost::bind(&umtrx_impl::read_temp_c, this, "A"));
UHD_MSG(status) << this->read_temp_c("A").to_pp_string() << std::endl;
if (!tmp102_ctrl::check(_iface, tmp102_ctrl::TMP102_SCL)) {
_hw_rev = UMTRX_VER_2_1;
return;
}
// Initialize side B temp sensor
_temp_side_b.init(_iface, tmp102_ctrl::TMP102_SCL);
_temp_side_b.set_ex_mode(true);
_tree->create<sensor_value_t>(mb_path / "sensors" / "tempB")
.publish(boost::bind(&umtrx_impl::read_temp_c, this, "B"));
UHD_MSG(status) << this->read_temp_c("B").to_pp_string() << std::endl;
if (!ads1015_ctrl::check(_iface, ads1015_ctrl::ADS1015_ADDR_VDD)) {
_hw_rev = UMTRX_VER_2_2;
return;
}
//Initialize PA sense ADC
_sense_pwr.init(_iface, ads1015_ctrl::ADS1015_ADDR_VDD);
_sense_pwr.set_mode(true);
_sense_pwr.set_pga(ads1015_ctrl::ADS1015_PGA_2_048V);
for (unsigned i = 0; i < power_sensors.size(); i++) {
_tree->create<sensor_value_t>(mb_path / "sensors" / "voltage"+power_sensors[i])
.publish(boost::bind(&umtrx_impl::read_pa_v, this, power_sensors[i]));
UHD_MSG(status) << this->read_pa_v(power_sensors[i]).to_pp_string() << std::endl;
}
if (!ads1015_ctrl::check(_iface, ads1015_ctrl::ADS1015_ADDR_GROUND)) {
_hw_rev = UMTRX_VER_2_3_0;
return;
}
_sense_dc.init(_iface, ads1015_ctrl::ADS1015_ADDR_GROUND);
_sense_dc.set_mode(true);
_sense_dc.set_pga(ads1015_ctrl::ADS1015_PGA_1_024V);
for (unsigned i = 0; i < power_sensors.size(); i++) {
_tree->create<sensor_value_t>(mb_path / "sensors" / "voltage"+dc_sensors[i])
.publish(boost::bind(&umtrx_impl::read_dc_v, this, dc_sensors[i]));
UHD_MSG(status) << this->read_dc_v(dc_sensors[i]).to_pp_string() << std::endl;
}
_hw_rev = UMTRX_VER_2_3_1;
_tree->create<uint8_t>(mb_path / "pa_dcdc_r")
.subscribe(boost::bind(&umtrx_impl::set_pa_dcdc_r, this, _1));
std::string pa_dcdc_r = _iface->mb_eeprom.get("pa_dcdc_r", "");
char* pa_dcdc_r_env = getenv("UMTRX_PA_DCDC_R");
if (pa_dcdc_r_env) {
UHD_MSG(status) << "EEPROM value of pa_dcdc_r:" << pa_dcdc_r.c_str()
<< " is overriden with env UMTRX_PA_DCDC_R:"
<< pa_dcdc_r_env << std::endl;
pa_dcdc_r = pa_dcdc_r_env;
}
if (pa_dcdc_r.empty())
set_pa_dcdc_r(0);
else
set_pa_dcdc_r(boost::lexical_cast<unsigned>(pa_dcdc_r));
_pa_en1 = (boost::lexical_cast<int>(_iface->mb_eeprom.get("pa_en1", "1")) == 1);
_pa_en2 = (boost::lexical_cast<int>(_iface->mb_eeprom.get("pa_en2", "1")) == 1);
if (getenv("UMTRX_PA_EN1")) _pa_en1 = (boost::lexical_cast<int>(getenv("UMTRX_PA_EN1")) != 0);
if (getenv("UMTRX_PA_EN2")) _pa_en2 = (boost::lexical_cast<int>(getenv("UMTRX_PA_EN2")) != 0);
std::string pa_low = _iface->mb_eeprom.get("pa_low", "");
char* pa_low_env = getenv("UMTRX_PA_LOW");
if (pa_low_env) {
UHD_MSG(status) << "EEPROM value of pa_low:" << pa_low.c_str()
<< " is overriden with env UMTRX_PA_LOW:"
<< pa_low_env << std::endl;
pa_low = pa_low_env;
}
if (pa_low.empty())
_pa_nlow = false;
else
_pa_nlow = (boost::lexical_cast<int>(pa_low) == 0);
_tree->create<bool>(mb_path / "pa_en1")
.subscribe(boost::bind(&umtrx_impl::set_enpa1, this, _1));
_tree->create<bool>(mb_path / "pa_en2")
.subscribe(boost::bind(&umtrx_impl::set_enpa2, this, _1));
_tree->create<bool>(mb_path / "pa_nlow")
.subscribe(boost::bind(&umtrx_impl::set_nlow, this, _1));
commit_pa_state();
UHD_MSG(status) << "PA low=`" << pa_low.c_str()
<< "` PA dcdc_r=`" << pa_dcdc_r.c_str()
<< "`" << std::endl;
}
void umtrx_impl::commit_pa_state()
{
if (_hw_rev >= UMTRX_VER_2_3_1)
_iface->poke32(U2_REG_MISC_LMS_RES, LMS1_RESET | LMS2_RESET
| PAREG_ENDCSYNC
| ((_pa_nlow) ? PAREG_NLOW_PA : 0)
| ((_pa_en1) ? PAREG_ENPA1 : 0)
| ((_pa_en2) ? PAREG_ENPA2 : 0));
}
void umtrx_impl::set_enpa1(bool en)
{
_pa_en1 = en; commit_pa_state();
}
void umtrx_impl::set_enpa2(bool en)
{
_pa_en2 = en; commit_pa_state();
}
void umtrx_impl::set_nlow(bool en)
{
_pa_nlow = en; commit_pa_state();
}
void umtrx_impl::set_divsw1(bool en)
{
_iface->poke32(U2_REG_SR_ADDR(SR_DIVSW+0), (en) ? 1 : 0);
}
void umtrx_impl::set_divsw2(bool en)
{
_iface->poke32(U2_REG_SR_ADDR(SR_DIVSW+1), (en) ? 1 : 0);
}
const char* umtrx_impl::get_hw_rev() const
{
switch (_hw_rev) {
case UMTRX_VER_2_0: return "2.0";
case UMTRX_VER_2_1: return "2.1";
case UMTRX_VER_2_2: return "2.2";
case UMTRX_VER_2_3_0: return "2.3.0";
case UMTRX_VER_2_3_1: return "2.3.1";
default: return "[unknown]";
}
}