aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/dboard
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib/usrp/dboard')
-rw-r--r--host/lib/usrp/dboard/CMakeLists.txt28
-rw-r--r--host/lib/usrp/dboard/db_basic_and_lf.cpp274
-rw-r--r--host/lib/usrp/dboard/db_dbsrx.cpp610
-rw-r--r--host/lib/usrp/dboard/db_rfx.cpp563
-rw-r--r--host/lib/usrp/dboard/db_unknown.cpp249
-rw-r--r--host/lib/usrp/dboard/db_wbx.cpp636
-rw-r--r--host/lib/usrp/dboard/db_xcvr2450.cpp611
7 files changed, 2971 insertions, 0 deletions
diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt
new file mode 100644
index 000000000..3e995009e
--- /dev/null
+++ b/host/lib/usrp/dboard/CMakeLists.txt
@@ -0,0 +1,28 @@
+#
+# Copyright 2010 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/>.
+#
+
+#This file will be included by cmake, use absolute paths!
+
+LIBUHD_APPEND_SOURCES(
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_basic_and_lf.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_rfx.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_xcvr2450.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_wbx.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_dbsrx.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_unknown.cpp
+)
+
diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp
new file mode 100644
index 000000000..9180828d8
--- /dev/null
+++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp
@@ -0,0 +1,274 @@
+//
+// Copyright 2010 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 <uhd/usrp/subdev_props.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/utils/assert.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/format.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * The basic and lf boards:
+ * They share a common class because only the frequency bounds differ.
+ **********************************************************************/
+class basic_rx : public rx_dboard_base{
+public:
+ basic_rx(ctor_args_t args, double max_freq);
+ ~basic_rx(void);
+
+ void rx_get(const wax::obj &key, wax::obj &val);
+ void rx_set(const wax::obj &key, const wax::obj &val);
+
+private:
+ double _max_freq;
+};
+
+class basic_tx : public tx_dboard_base{
+public:
+ basic_tx(ctor_args_t args, double max_freq);
+ ~basic_tx(void);
+
+ void tx_get(const wax::obj &key, wax::obj &val);
+ void tx_set(const wax::obj &key, const wax::obj &val);
+
+private:
+ double _max_freq;
+};
+
+/***********************************************************************
+ * Register the basic and LF dboards
+ **********************************************************************/
+static dboard_base::sptr make_basic_rx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new basic_rx(args, 90e9));
+}
+
+static dboard_base::sptr make_basic_tx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new basic_tx(args, 90e9));
+}
+
+static dboard_base::sptr make_lf_rx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new basic_rx(args, 32e6));
+}
+
+static dboard_base::sptr make_lf_tx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new basic_tx(args, 32e6));
+}
+
+UHD_STATIC_BLOCK(reg_basic_and_lf_dboards){
+ dboard_manager::register_dboard(0x0000, &make_basic_tx, "Basic TX");
+ dboard_manager::register_dboard(0x0001, &make_basic_rx, "Basic RX", list_of("AB")("A")("B"));
+ dboard_manager::register_dboard(0x000e, &make_lf_tx, "LF TX");
+ dboard_manager::register_dboard(0x000f, &make_lf_rx, "LF RX", list_of("AB")("A")("B"));
+}
+
+/***********************************************************************
+ * Basic and LF RX dboard
+ **********************************************************************/
+basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){
+ _max_freq = max_freq;
+}
+
+basic_rx::~basic_rx(void){
+ /* NOP */
+}
+
+void basic_rx::rx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = std::string(str(boost::format("%s - %s")
+ % get_rx_id().to_pp_string()
+ % get_subdev_name()
+ ));
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ val = float(0);
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ val = gain_range_t(0, 0, 0);
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = double(0);
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = freq_range_t(-_max_freq, +_max_freq);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = std::string("");
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = prop_names_t(1, ""); //vector of 1 empty string
+ return;
+
+ case SUBDEV_PROP_CONNECTION:{
+ static const uhd::dict<std::string, subdev_conn_t> name_to_conn = map_list_of
+ ("A", SUBDEV_CONN_REAL_I)
+ ("B", SUBDEV_CONN_REAL_Q)
+ ("AB", SUBDEV_CONN_COMPLEX_IQ)
+ ;
+ val = name_to_conn[get_subdev_name()];
+ } return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = true; //there is no LO, so it must be true!
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void basic_rx::rx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_GAIN:
+ UHD_ASSERT_THROW(val.as<float>() == float(0));
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ UHD_ASSERT_THROW(val.as<std::string>() == std::string(""));
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ return; // it wont do you much good, but you can set it
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
+
+/***********************************************************************
+ * Basic and LF TX dboard
+ **********************************************************************/
+basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){
+ _max_freq = max_freq;
+}
+
+basic_tx::~basic_tx(void){
+ /* NOP */
+}
+
+void basic_tx::tx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = get_tx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ val = float(0);
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ val = gain_range_t(0, 0, 0);
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = double(0);
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = freq_range_t(-_max_freq, +_max_freq);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = std::string("");
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = prop_names_t(1, ""); //vector of 1 empty string
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_IQ;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = true; //there is no LO, so it must be true!
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void basic_tx::tx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_GAIN:
+ UHD_ASSERT_THROW(val.as<float>() == float(0));
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ UHD_ASSERT_THROW(val.as<std::string>() == std::string(""));
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ return; // it wont do you much good, but you can set it
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp
new file mode 100644
index 000000000..03e6b6255
--- /dev/null
+++ b/host/lib/usrp/dboard/db_dbsrx.cpp
@@ -0,0 +1,610 @@
+//
+// Copyright 2010 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/>.
+//
+
+// No RX IO Pins Used
+
+// RX IO Functions
+
+#include "max2118_regs.hpp"
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/assert.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/warning.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/usrp/subdev_props.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/format.hpp>
+#include <boost/thread.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <utility>
+#include <cmath>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * The DBSRX constants
+ **********************************************************************/
+static const bool dbsrx_debug = false;
+
+static const freq_range_t dbsrx_freq_range(0.8e9, 2.4e9);
+
+static const freq_range_t dbsrx_pfd_freq_range(0.15e6, 2.01e6);
+
+static const prop_names_t dbsrx_antennas = list_of("J3");
+
+static const uhd::dict<std::string, gain_range_t> dbsrx_gain_ranges = map_list_of
+ ("GC1", gain_range_t(0, 56, 0.5))
+ ("GC2", gain_range_t(0, 24, 1))
+;
+
+/***********************************************************************
+ * The DBSRX dboard class
+ **********************************************************************/
+class dbsrx : public rx_dboard_base{
+public:
+ dbsrx(ctor_args_t args, boost::uint8_t max2118_addr);
+ ~dbsrx(void);
+
+ void rx_get(const wax::obj &key, wax::obj &val);
+ void rx_set(const wax::obj &key, const wax::obj &val);
+
+private:
+ double _lo_freq;
+ float _bandwidth;
+ uhd::dict<std::string, float> _gains;
+ max2118_write_regs_t _max2118_write_regs;
+ max2118_read_regs_t _max2118_read_regs;
+ boost::uint8_t _max2118_addr; //0x67 or 0x65 depending on which side
+
+ void set_lo_freq(double target_freq);
+ void set_gain(float gain, const std::string &name);
+ void set_bandwidth(float bandwidth);
+
+ void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){
+ start_reg = boost::uint8_t(std::clip(int(start_reg), 0x0, 0x5));
+ stop_reg = boost::uint8_t(std::clip(int(stop_reg), 0x0, 0x5));
+
+ for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t) - 1){
+ int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) - 1 ? sizeof(boost::uint32_t) - 1 : stop_reg - start_addr + 1;
+
+ //create buffer for register data (+1 for start address)
+ byte_vector_t regs_vector(num_bytes + 1);
+
+ //first byte is the address of first register
+ regs_vector[0] = start_addr;
+
+ //get the register data
+ for(int i=0; i<num_bytes; i++){
+ regs_vector[1+i] = _max2118_write_regs.get_reg(start_addr+i);
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d"
+ ) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl;
+ }
+
+ //send the data
+ this->get_iface()->write_i2c(
+ _max2118_addr, regs_vector
+ );
+ }
+ }
+
+ void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){
+ static const boost::uint8_t status_addr = 0x0;
+ start_reg = boost::uint8_t(std::clip(int(start_reg), 0x0, 0x1));
+ stop_reg = boost::uint8_t(std::clip(int(stop_reg), 0x0, 0x1));
+
+ for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t)){
+ int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) ? sizeof(boost::uint32_t) : stop_reg - start_addr + 1;
+
+ //create buffer for register data
+ byte_vector_t regs_vector(num_bytes);
+
+ //read from i2c
+ regs_vector = this->get_iface()->read_i2c(
+ _max2118_addr, num_bytes
+ );
+
+ for(boost::uint8_t i=0; i < num_bytes; i++){
+ if (i + start_addr >= status_addr){
+ _max2118_read_regs.set_reg(i + start_addr, regs_vector[i]);
+ }
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d"
+ ) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl;
+ }
+ }
+ }
+
+ /*!
+ * Is the LO locked?
+ * \return true for locked
+ */
+ bool get_locked(void){
+ read_reg(0x0, 0x0);
+
+ //mask and return lock detect
+ bool locked = 5 >= _max2118_read_regs.adc and _max2118_read_regs.adc >= 2;
+
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: locked %d"
+ ) % locked << std::endl;
+
+ return locked;
+ }
+
+};
+
+/***********************************************************************
+ * Register the DBSRX dboard
+ **********************************************************************/
+// FIXME 0x67 is the default i2c address on USRP2
+// need to handle which side for USRP1 with different address
+static dboard_base::sptr make_dbsrx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new dbsrx(args, 0x67));
+}
+
+//dbid for USRP2 version
+UHD_STATIC_BLOCK(reg_dbsrx_dboard){
+ //register the factory function for the rx dbid
+ dboard_manager::register_dboard(0x000D, &make_dbsrx, "DBSRX");
+}
+
+//dbid for USRP1 version
+UHD_STATIC_BLOCK(reg_dbsrx_on_usrp1_dboard){
+ //register the factory function for the rx dbid
+ dboard_manager::register_dboard(0x0002, &make_dbsrx, "DBSRX");
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+dbsrx::dbsrx(ctor_args_t args, boost::uint8_t max2118_addr) : rx_dboard_base(args){
+ //warn user about incorrect DBID on USRP1, requires R193 populated
+ if (this->get_iface()->get_mboard_name() == "usrp1" and this->get_rx_id() == 0x000D)
+ uhd::print_warning(
+ str(boost::format(
+ "DBSRX: incorrect dbid\n"
+ "%s expects dbid 0x0002 and R193\n"
+ "found dbid == %d\n"
+ "Please see the daughterboard app notes"
+ ) % (this->get_iface()->get_mboard_name()) % (this->get_rx_id().to_pp_string()))
+ );
+
+ //warn user about incorrect DBID on non-USRP1, requires R194 populated
+ if (this->get_iface()->get_mboard_name() != "usrp1" and this->get_rx_id() == 0x0002)
+ uhd::print_warning(
+ str(boost::format(
+ "DBSRX: incorrect dbid\n"
+ "%s expects dbid 0x000D and R194\n"
+ "found dbid == %d\n"
+ "Please see the daughterboard app notes"
+ ) % (this->get_iface()->get_mboard_name()) % (this->get_rx_id().to_pp_string()))
+ );
+
+ //enable only the clocks we need
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true);
+
+ //set the gpio directions and atr controls (identically)
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs
+
+ //set the i2c address for the max2118
+ _max2118_addr = max2118_addr;
+
+ //send initial register settings
+ this->send_reg(0x0, 0x5);
+
+ //set defaults for LO, gains, and filter bandwidth
+ _bandwidth = 33e6;
+ set_lo_freq(dbsrx_freq_range.min);
+
+ BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){
+ set_gain(dbsrx_gain_ranges[name].min, name);
+ }
+
+ set_bandwidth(33e6); // default bandwidth from datasheet
+}
+
+dbsrx::~dbsrx(void){
+}
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+void dbsrx::set_lo_freq(double target_freq){
+ target_freq = std::clip(target_freq, dbsrx_freq_range.min, dbsrx_freq_range.max);
+
+ double actual_freq=0.0, pfd_freq=0.0, ref_clock=0.0;
+ int R=0, N=0, r=0, m=0;
+ bool update_filter_settings = false;
+
+ //choose refclock
+ std::vector<double> clock_rates = this->get_iface()->get_clock_rates(dboard_iface::UNIT_RX);
+ BOOST_FOREACH(ref_clock, std::reversed(std::sorted(clock_rates))){
+ if (ref_clock > 27.0e6) continue;
+
+ //choose m_divider such that filter tuning constraint is met
+ m = 31;
+ while ((ref_clock/m < 1e6 or ref_clock/m > 2.5e6) and m > 0){ m--; }
+
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: trying ref_clock %f and m_divider %d"
+ ) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % m << std::endl;
+
+ if (m >= 32) continue;
+
+ //choose R
+ for(r = 0; r <= 6; r += 1) {
+ //compute divider from setting
+ R = 1 << (r+1);
+ if (dbsrx_debug) std::cerr << boost::format("DBSRX R:%d\n") % R << std::endl;
+
+ //compute PFD compare frequency = ref_clock/R
+ pfd_freq = ref_clock / R;
+
+ //constrain the PFD frequency to specified range
+ if ((pfd_freq < dbsrx_pfd_freq_range.min) or (pfd_freq > dbsrx_pfd_freq_range.max)) continue;
+
+ //compute N
+ N = int(std::floor(target_freq/pfd_freq));
+
+ //constrain N to specified range
+ if ((N < 256) or (N > 32768)) continue;
+
+ goto done_loop;
+ }
+ }
+
+ //Assert because we failed to find a suitable combination of ref_clock, R and N
+ UHD_ASSERT_THROW(ref_clock/(1 << m) < 1e6 or ref_clock/(1 << m) > 2.5e6);
+ UHD_ASSERT_THROW((pfd_freq < dbsrx_pfd_freq_range.min) or (pfd_freq > dbsrx_pfd_freq_range.max));
+ UHD_ASSERT_THROW((N < 256) or (N > 32768));
+ done_loop:
+
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: choose ref_clock %f and m_divider %d"
+ ) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % m << std::endl;
+
+ //if ref_clock or m divider changed, we need to update the filter settings
+ if (ref_clock != this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) or m != _max2118_write_regs.m_divider) update_filter_settings = true;
+
+ //compute resulting output frequency
+ actual_freq = pfd_freq * N;
+
+ //apply ref_clock, R, and N settings
+ this->get_iface()->set_clock_rate(dboard_iface::UNIT_RX, ref_clock);
+ ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX);
+ _max2118_write_regs.m_divider = m;
+ _max2118_write_regs.r_divider = (max2118_write_regs_t::r_divider_t) r;
+ _max2118_write_regs.set_n_divider(N);
+ _max2118_write_regs.ade_vco_ade_read = max2118_write_regs_t::ADE_VCO_ADE_READ_ENABLED;
+
+ //compute prescaler variables
+ int scaler = actual_freq > 1125e6 ? 2 : 4;
+ _max2118_write_regs.div2 = scaler == 4 ? max2118_write_regs_t::DIV2_DIV4 : max2118_write_regs_t::DIV2_DIV2;
+
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: scaler %d, actual_freq %f MHz, register bit: %d"
+ ) % scaler % (actual_freq/1e6) % int(_max2118_write_regs.div2) << std::endl;
+
+ //compute vco frequency and select vco
+ double vco_freq = actual_freq * scaler;
+ if (vco_freq < 2433e6)
+ _max2118_write_regs.osc_band = 0;
+ else if (vco_freq < 2711e6)
+ _max2118_write_regs.osc_band = 1;
+ else if (vco_freq < 3025e6)
+ _max2118_write_regs.osc_band = 2;
+ else if (vco_freq < 3341e6)
+ _max2118_write_regs.osc_band = 3;
+ else if (vco_freq < 3727e6)
+ _max2118_write_regs.osc_band = 4;
+ else if (vco_freq < 4143e6)
+ _max2118_write_regs.osc_band = 5;
+ else if (vco_freq < 4493e6)
+ _max2118_write_regs.osc_band = 6;
+ else
+ _max2118_write_regs.osc_band = 7;
+
+ //send settings over i2c
+ send_reg(0x0, 0x4);
+
+ //check vtune for lock condition
+ read_reg(0x0, 0x0);
+
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: initial guess for vco %d, vtune adc %d"
+ ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl;
+
+ //if we are out of lock for chosen vco, change vco
+ while ((_max2118_read_regs.adc == 0) or (_max2118_read_regs.adc == 7)){
+
+ //vtune is too low, try lower frequency vco
+ if (_max2118_read_regs.adc == 0){
+ if (_max2118_write_regs.osc_band == 0){
+ uhd::print_warning(
+ str(boost::format(
+ "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n"
+ ) % int(_max2118_write_regs.osc_band))
+ );
+ UHD_ASSERT_THROW(_max2118_read_regs.adc == 0);
+ }
+ if (_max2118_write_regs.osc_band <= 0) break;
+ _max2118_write_regs.osc_band -= 1;
+ }
+
+ //vtune is too high, try higher frequency vco
+ if (_max2118_read_regs.adc == 7){
+ if (_max2118_write_regs.osc_band == 7){
+ uhd::print_warning(
+ str(boost::format(
+ "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n"
+ ) % int(_max2118_write_regs.osc_band))
+ );
+ UHD_ASSERT_THROW(_max2118_read_regs.adc == 0);
+ }
+ if (_max2118_write_regs.osc_band >= 7) break;
+ _max2118_write_regs.osc_band += 1;
+ }
+
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: trying vco %d, vtune adc %d"
+ ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl;
+
+ //update vco selection and check vtune
+ send_reg(0x2, 0x2);
+ read_reg(0x0, 0x0);
+ }
+
+ if(dbsrx_debug) std::cerr << boost::format(
+ "DBSRX: final vco %d, vtune adc %d"
+ ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl;
+
+ //select charge pump bias current
+ if (_max2118_read_regs.adc <= 2) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_100UA;
+ else if (_max2118_read_regs.adc >= 5) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_400UA;
+ else _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_200UA;
+
+ //update charge pump bias current setting
+ send_reg(0x2, 0x2);
+
+ //compute actual tuned frequency
+ _lo_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) / std::pow(2.0,(1 + _max2118_write_regs.r_divider)) * _max2118_write_regs.get_n_divider();
+
+ //debug output of calculated variables
+ if (dbsrx_debug) std::cerr
+ << boost::format("DBSRX tune:\n")
+ << boost::format(" VCO=%d, CP=%d, PFD Freq=%fMHz\n") % int(_max2118_write_regs.osc_band) % _max2118_write_regs.cp_current % (pfd_freq/1e6)
+ << boost::format(" R=%d, N=%f, scaler=%d, div2=%d\n") % R % N % scaler % int(_max2118_write_regs.div2)
+ << boost::format(" Ref Freq=%fMHz\n") % (ref_clock/1e6)
+ << boost::format(" Target Freq=%fMHz\n") % (target_freq/1e6)
+ << boost::format(" Actual Freq=%fMHz\n") % (_lo_freq/1e6)
+ << std::endl;
+
+ if (update_filter_settings) set_bandwidth(_bandwidth);
+ get_locked();
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+/*!
+ * Convert a requested gain for the GC2 vga into the integer register value.
+ * The gain passed into the function will be set to the actual value.
+ * \param gain the requested gain in dB
+ * \return 5 bit the register value
+ */
+static int gain_to_gc2_vga_reg(float &gain){
+ int reg = 0;
+ gain = std::clip<float>(float(boost::math::iround(gain)), dbsrx_gain_ranges["GC2"].min, dbsrx_gain_ranges["GC2"].max);
+
+ // Half dB steps from 0-5dB, 1dB steps from 5-24dB
+ if (gain < 5) {
+ reg = boost::math::iround(31.0 - gain/0.5);
+ gain = float(boost::math::iround(gain) * 0.5);
+ } else {
+ reg = boost::math::iround(22.0 - (gain - 4.0));
+ gain = float(boost::math::iround(gain));
+ }
+
+ if (dbsrx_debug) std::cerr << boost::format(
+ "DBSRX GC2 Gain: %f dB, reg: %d"
+ ) % gain % reg << std::endl;
+
+ return reg;
+}
+
+/*!
+ * Convert a requested gain for the GC1 rf vga into the dac_volts value.
+ * The gain passed into the function will be set to the actual value.
+ * \param gain the requested gain in dB
+ * \return dac voltage value
+ */
+static float gain_to_gc1_rfvga_dac(float &gain){
+ //clip the input
+ gain = std::clip<float>(gain, dbsrx_gain_ranges["GC1"].min, dbsrx_gain_ranges["GC1"].max);
+
+ //voltage level constants
+ static const float max_volts = float(1.2), min_volts = float(2.7);
+ static const float slope = (max_volts-min_volts)/dbsrx_gain_ranges["GC1"].max;
+
+ //calculate the voltage for the aux dac
+ float dac_volts = gain*slope + min_volts;
+
+ if (dbsrx_debug) std::cerr << boost::format(
+ "DBSRX GC1 Gain: %f dB, dac_volts: %f V"
+ ) % gain % dac_volts << std::endl;
+
+ //the actual gain setting
+ gain = (dac_volts - min_volts)/slope;
+
+ return dac_volts;
+}
+
+void dbsrx::set_gain(float gain, const std::string &name){
+ assert_has(dbsrx_gain_ranges.keys(), name, "dbsrx gain name");
+ if (name == "GC2"){
+ _max2118_write_regs.gc2 = gain_to_gc2_vga_reg(gain);
+ send_reg(0x5, 0x5);
+ }
+ else if(name == "GC1"){
+ //write the new voltage to the aux dac
+ this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, gain_to_gc1_rfvga_dac(gain));
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ _gains[name] = gain;
+}
+
+/***********************************************************************
+ * Bandwidth Handling
+ **********************************************************************/
+void dbsrx::set_bandwidth(float bandwidth){
+ //clip the input
+ bandwidth = std::clip<float>(bandwidth, 4e6, 33e6);
+
+ double ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX);
+
+ //NOTE: _max2118_write_regs.m_divider set in set_lo_freq
+
+ //compute f_dac setting
+ _max2118_write_regs.f_dac = std::clip<int>(int((((bandwidth*_max2118_write_regs.m_divider)/ref_clock) - 4)/0.145),0,127);
+
+ //determine actual bandwidth
+ _bandwidth = float((ref_clock/(_max2118_write_regs.m_divider))*(4+0.145*_max2118_write_regs.f_dac));
+
+ if (dbsrx_debug) std::cerr << boost::format(
+ "DBSRX Filter Bandwidth: %f MHz, m: %d, f_dac: %d\n"
+ ) % (_bandwidth/1e6) % int(_max2118_write_regs.m_divider) % int(_max2118_write_regs.f_dac) << std::endl;
+
+ this->send_reg(0x3, 0x4);
+}
+
+/***********************************************************************
+ * RX Get and Set
+ **********************************************************************/
+void dbsrx::rx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = get_rx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ assert_has(_gains.keys(), name, "dbsrx gain name");
+ val = _gains[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ assert_has(dbsrx_gain_ranges.keys(), name, "dbsrx gain name");
+ val = dbsrx_gain_ranges[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(dbsrx_gain_ranges.keys());
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = _lo_freq;
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = dbsrx_freq_range;
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = std::string("J3");
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = dbsrx_antennas;
+ return;
+
+/*
+ case SUBDEV_PROP_QUADRATURE:
+ val = true;
+ return;
+
+ case SUBDEV_PROP_IQ_SWAPPED:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_SPECTRUM_INVERTED:
+ val = false;
+ return;
+*/
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_IQ;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = this->get_locked();
+ return;
+
+/*
+ case SUBDEV_PROP_RSSI:
+ val = this->get_rssi();
+ return;
+*/
+
+ case SUBDEV_PROP_BANDWIDTH:
+ val = _bandwidth;
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void dbsrx::rx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_FREQ:
+ this->set_lo_freq(val.as<double>());
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ this->set_gain(val.as<float>(), name);
+ return;
+
+ case SUBDEV_PROP_BANDWIDTH:
+ this->set_bandwidth(val.as<float>());
+ return;
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
+
diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp
new file mode 100644
index 000000000..b6b44199a
--- /dev/null
+++ b/host/lib/usrp/dboard/db_rfx.cpp
@@ -0,0 +1,563 @@
+//
+// Copyright 2010 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/>.
+//
+
+// IO Pin functions
+#define POWER_IO (1 << 7) // Low enables power supply
+#define ANTSW_IO (1 << 6) // On TX DB, 0 = TX, 1 = RX, on RX DB 0 = main ant, 1 = RX2
+#define MIXER_IO (1 << 5) // Enable appropriate mixer
+#define LOCKDET_MASK (1 << 2) // Input pin
+
+// Mixer constants
+#define MIXER_ENB MIXER_IO
+#define MIXER_DIS 0
+
+// Power constants
+#define POWER_UP 0
+#define POWER_DOWN POWER_IO
+
+// Antenna constants
+#define ANT_TX 0 //the tx line is transmitting
+#define ANT_RX ANTSW_IO //the tx line is receiving
+#define ANT_TXRX 0 //the rx line is on txrx
+#define ANT_RX2 ANTSW_IO //the rx line in on rx2
+#define ANT_XX 0 //dont care how the antenna is set
+
+#include "adf4360_regs.hpp"
+#include <uhd/types/dict.hpp>
+#include <uhd/usrp/subdev_props.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/utils/assert.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/usrp/dboard_id.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/format.hpp>
+#include <boost/math/special_functions/round.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * The RFX Series constants
+ **********************************************************************/
+static const bool rfx_debug = false;
+
+static const prop_names_t rfx_tx_antennas = list_of("TX/RX");
+
+static const prop_names_t rfx_rx_antennas = list_of("TX/RX")("RX2");
+
+static const uhd::dict<std::string, gain_range_t> rfx_tx_gain_ranges; //empty
+
+static const uhd::dict<std::string, gain_range_t> rfx_rx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 70, float(0.022)))
+;
+
+static const uhd::dict<std::string, gain_range_t> rfx400_rx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 45, float(0.022)))
+;
+
+/***********************************************************************
+ * The RFX series of dboards
+ **********************************************************************/
+class rfx_xcvr : public xcvr_dboard_base{
+public:
+ rfx_xcvr(
+ ctor_args_t args,
+ const freq_range_t &freq_range,
+ bool rx_div2, bool tx_div2
+ );
+ ~rfx_xcvr(void);
+
+ void rx_get(const wax::obj &key, wax::obj &val);
+ void rx_set(const wax::obj &key, const wax::obj &val);
+
+ void tx_get(const wax::obj &key, wax::obj &val);
+ void tx_set(const wax::obj &key, const wax::obj &val);
+
+private:
+ freq_range_t _freq_range;
+ uhd::dict<std::string, gain_range_t> _rx_gain_ranges;
+ uhd::dict<dboard_iface::unit_t, bool> _div2;
+ double _rx_lo_freq, _tx_lo_freq;
+ std::string _rx_ant;
+ uhd::dict<std::string, float> _rx_gains;
+
+ void set_rx_lo_freq(double freq);
+ void set_tx_lo_freq(double freq);
+ void set_rx_ant(const std::string &ant);
+ void set_tx_ant(const std::string &ant);
+ void set_rx_gain(float gain, const std::string &name);
+ void set_tx_gain(float gain, const std::string &name);
+
+ /*!
+ * Set the LO frequency for the particular dboard unit.
+ * \param unit which unit rx or tx
+ * \param target_freq the desired frequency in Hz
+ * \return the actual frequency in Hz
+ */
+ double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
+
+ /*!
+ * Get the lock detect status of the LO.
+ * \param unit which unit rx or tx
+ * \return true for locked
+ */
+ bool get_locked(dboard_iface::unit_t unit){
+ return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0;
+ }
+};
+
+/***********************************************************************
+ * Register the RFX dboards (min freq, max freq, rx div2, tx div2)
+ **********************************************************************/
+static dboard_base::sptr make_rfx_flex400(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(400e6, 500e6), false, true));
+}
+
+static dboard_base::sptr make_rfx_flex900(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(750e6, 1050e6), true, true));
+}
+
+static dboard_base::sptr make_rfx_flex1800(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(1500e6, 2100e6), false, false));
+}
+
+static dboard_base::sptr make_rfx_flex1200(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(1150e6, 1450e6), true, true));
+}
+
+static dboard_base::sptr make_rfx_flex2200(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(2000e6, 2400e6), false, false));
+}
+
+static dboard_base::sptr make_rfx_flex2400(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(2300e6, 2900e6), false, false));
+}
+
+UHD_STATIC_BLOCK(reg_rfx_dboards){
+ dboard_manager::register_dboard(0x0024, 0x0028, &make_rfx_flex400, "Flex 400 MIMO B");
+ dboard_manager::register_dboard(0x0025, 0x0029, &make_rfx_flex900, "Flex 900 MIMO B");
+ dboard_manager::register_dboard(0x0034, 0x0035, &make_rfx_flex1800, "Flex 1800 MIMO B");
+ dboard_manager::register_dboard(0x0026, 0x002a, &make_rfx_flex1200, "Flex 1200 MIMO B");
+ dboard_manager::register_dboard(0x002c, 0x002d, &make_rfx_flex2200, "Flex 2200 MIMO B");
+ dboard_manager::register_dboard(0x0027, 0x002b, &make_rfx_flex2400, "Flex 2400 MIMO B");
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+rfx_xcvr::rfx_xcvr(
+ ctor_args_t args,
+ const freq_range_t &freq_range,
+ bool rx_div2, bool tx_div2
+) : xcvr_dboard_base(args){
+ _freq_range = freq_range;
+ _div2[dboard_iface::UNIT_RX] = rx_div2;
+ _div2[dboard_iface::UNIT_TX] = tx_div2;
+
+ if(this->get_rx_id() == 0x0024) { //RFX400
+ _rx_gain_ranges = rfx400_rx_gain_ranges;
+ }
+ else {
+ _rx_gain_ranges = rfx_rx_gain_ranges;
+ }
+
+
+ //enable the clocks that we need
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true);
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true);
+
+ //set the gpio directions and atr controls (identically)
+ boost::uint16_t output_enables = POWER_IO | ANTSW_IO | MIXER_IO;
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, output_enables);
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, output_enables);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, output_enables);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, output_enables);
+
+ //setup the tx atr (this does not change with antenna)
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, POWER_UP | ANT_XX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, POWER_UP | ANT_RX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, POWER_UP | ANT_TX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP | ANT_TX | MIXER_ENB);
+
+ //setup the rx atr (this does not change with antenna)
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, POWER_UP | ANT_XX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, POWER_UP | ANT_XX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP | ANT_RX2| MIXER_ENB);
+
+ //set some default values
+ set_rx_lo_freq((_freq_range.min + _freq_range.max)/2.0);
+ set_tx_lo_freq((_freq_range.min + _freq_range.max)/2.0);
+ set_rx_ant("RX2");
+
+ BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){
+ set_rx_gain(_rx_gain_ranges[name].min, name);
+ }
+}
+
+rfx_xcvr::~rfx_xcvr(void){
+ /* NOP */
+}
+
+/***********************************************************************
+ * Antenna Handling
+ **********************************************************************/
+void rfx_xcvr::set_rx_ant(const std::string &ant){
+ //validate input
+ assert_has(rfx_rx_antennas, ant, "rfx rx antenna name");
+
+ //set the rx atr regs that change with antenna setting
+ this->get_iface()->set_atr_reg(
+ dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY,
+ POWER_UP | MIXER_ENB | ((ant == "TX/RX")? ANT_TXRX : ANT_RX2)
+ );
+
+ //shadow the setting
+ _rx_ant = ant;
+}
+
+void rfx_xcvr::set_tx_ant(const std::string &ant){
+ assert_has(rfx_tx_antennas, ant, "rfx tx antenna name");
+ //only one antenna option, do nothing
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+static float rx_pga0_gain_to_dac_volts(float &gain, float range){
+ //voltage level constants (negative slope)
+ static const float max_volts = float(.2), min_volts = float(1.2);
+ static const float slope = (max_volts-min_volts)/(range);
+
+ //calculate the voltage for the aux dac
+ float dac_volts = std::clip<float>(gain*slope + min_volts, max_volts, min_volts);
+
+ //the actual gain setting
+ gain = (dac_volts - min_volts)/slope;
+
+ return dac_volts;
+}
+
+void rfx_xcvr::set_tx_gain(float, const std::string &name){
+ assert_has(rfx_tx_gain_ranges.keys(), name, "rfx tx gain name");
+ UHD_THROW_INVALID_CODE_PATH(); //no gains to set
+}
+
+void rfx_xcvr::set_rx_gain(float gain, const std::string &name){
+ assert_has(_rx_gain_ranges.keys(), name, "rfx rx gain name");
+ if(name == "PGA0"){
+ float dac_volts = rx_pga0_gain_to_dac_volts(gain,
+ (_rx_gain_ranges["PGA0"].max - _rx_gain_ranges["PGA0"].min));
+ _rx_gains[name] = gain;
+
+ //write the new voltage to the aux dac
+ this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, dac_volts);
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+}
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+void rfx_xcvr::set_rx_lo_freq(double freq){
+ _rx_lo_freq = set_lo_freq(dboard_iface::UNIT_RX, freq);
+}
+
+void rfx_xcvr::set_tx_lo_freq(double freq){
+ _tx_lo_freq = set_lo_freq(dboard_iface::UNIT_TX, freq);
+}
+
+double rfx_xcvr::set_lo_freq(
+ dboard_iface::unit_t unit,
+ double target_freq
+){
+ if (rfx_debug) std::cerr << boost::format(
+ "RFX tune: target frequency %f Mhz"
+ ) % (target_freq/1e6) << std::endl;
+
+ //clip the input
+ target_freq = std::clip(target_freq, _freq_range.min, _freq_range.max);
+ if (_div2[unit]) target_freq *= 2;
+
+ //map prescalers to the register enums
+ static const uhd::dict<int, adf4360_regs_t::prescaler_value_t> prescaler_to_enum = map_list_of
+ (8, adf4360_regs_t::PRESCALER_VALUE_8_9)
+ (16, adf4360_regs_t::PRESCALER_VALUE_16_17)
+ (32, adf4360_regs_t::PRESCALER_VALUE_32_33)
+ ;
+
+ //map band select clock dividers to enums
+ static const uhd::dict<int, adf4360_regs_t::band_select_clock_div_t> bandsel_to_enum = map_list_of
+ (1, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_1)
+ (2, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_2)
+ (4, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_4)
+ (8, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_8)
+ ;
+
+ double actual_freq=0, ref_freq = this->get_iface()->get_clock_rate(unit);
+ int R=0, BS=0, P=0, B=0, A=0;
+
+ /*
+ * The goal here to to loop though possible R dividers,
+ * band select clock dividers, and prescaler values.
+ * Calculate the A and B counters for each set of values.
+ * The loop exists when it meets all of the constraints.
+ * The resulting loop values are loaded into the registers.
+ *
+ * fvco = [P*B + A] * fref/R
+ * fvco*R/fref = P*B + A = N
+ */
+ for(R = 2; R <= 32; R+=2){
+ BOOST_FOREACH(BS, bandsel_to_enum.keys()){
+ if (ref_freq/R/BS > 1e6) continue; //constraint on band select clock
+ BOOST_FOREACH(P, prescaler_to_enum.keys()){
+ //calculate B and A from N
+ double N = target_freq*R/ref_freq;
+ B = int(std::floor(N/P));
+ A = boost::math::iround(N - P*B);
+ if (B < A or B > 8191 or B < 3 or A > 31) continue; //constraints on A, B
+ //calculate the actual frequency
+ actual_freq = double(P*B + A)*ref_freq/R;
+ if (actual_freq/P > 300e6) continue; //constraint on prescaler output
+ //constraints met: exit loop
+ goto done_loop;
+ }
+ }
+ } done_loop:
+
+ if (rfx_debug) std::cerr << boost::format(
+ "RFX tune: R=%d, BS=%d, P=%d, B=%d, A=%d"
+ ) % R % BS % P % B % A << std::endl;
+
+ //load the register values
+ adf4360_regs_t regs;
+ regs.core_power_level = adf4360_regs_t::CORE_POWER_LEVEL_10MA;
+ regs.counter_operation = adf4360_regs_t::COUNTER_OPERATION_NORMAL;
+ regs.muxout_control = adf4360_regs_t::MUXOUT_CONTROL_DLD;
+ regs.phase_detector_polarity = adf4360_regs_t::PHASE_DETECTOR_POLARITY_POS;
+ regs.charge_pump_output = adf4360_regs_t::CHARGE_PUMP_OUTPUT_NORMAL;
+ regs.cp_gain_0 = adf4360_regs_t::CP_GAIN_0_SET1;
+ regs.mute_till_ld = adf4360_regs_t::MUTE_TILL_LD_ENB;
+ regs.output_power_level = adf4360_regs_t::OUTPUT_POWER_LEVEL_3_5MA;
+ regs.current_setting1 = adf4360_regs_t::CURRENT_SETTING1_0_31MA;
+ regs.current_setting2 = adf4360_regs_t::CURRENT_SETTING2_0_31MA;
+ regs.power_down = adf4360_regs_t::POWER_DOWN_NORMAL_OP;
+ regs.prescaler_value = prescaler_to_enum[P];
+ regs.a_counter = A;
+ regs.b_counter = B;
+ regs.cp_gain_1 = adf4360_regs_t::CP_GAIN_1_SET1;
+ regs.divide_by_2_output = (_div2[unit])?
+ adf4360_regs_t::DIVIDE_BY_2_OUTPUT_DIV2 :
+ adf4360_regs_t::DIVIDE_BY_2_OUTPUT_FUND ;
+ regs.divide_by_2_prescaler = adf4360_regs_t::DIVIDE_BY_2_PRESCALER_FUND;
+ regs.r_counter = R;
+ regs.ablpw = adf4360_regs_t::ABLPW_3_0NS;
+ regs.lock_detect_precision = adf4360_regs_t::LOCK_DETECT_PRECISION_5CYCLES;
+ regs.test_mode_bit = 0;
+ regs.band_select_clock_div = bandsel_to_enum[BS];
+
+ //write the registers
+ std::vector<adf4360_regs_t::addr_t> addrs = list_of //correct power-up sequence to write registers (R, C, N)
+ (adf4360_regs_t::ADDR_RCOUNTER)
+ (adf4360_regs_t::ADDR_CONTROL)
+ (adf4360_regs_t::ADDR_NCOUNTER)
+ ;
+ BOOST_FOREACH(adf4360_regs_t::addr_t addr, addrs){
+ this->get_iface()->write_spi(
+ unit, spi_config_t::EDGE_RISE,
+ regs.get_reg(addr), 24
+ );
+ }
+
+ //return the actual frequency
+ if (_div2[unit]) actual_freq /= 2;
+ if (rfx_debug) std::cerr << boost::format(
+ "RFX tune: actual frequency %f Mhz"
+ ) % (actual_freq/1e6) << std::endl;
+ return actual_freq;
+}
+
+/***********************************************************************
+ * RX Get and Set
+ **********************************************************************/
+void rfx_xcvr::rx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = get_rx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ assert_has(_rx_gains.keys(), name, "rfx rx gain name");
+ val = _rx_gains[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ assert_has(_rx_gain_ranges.keys(), name, "rfx rx gain name");
+ val = _rx_gain_ranges[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(_rx_gain_ranges.keys());
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = _rx_lo_freq;
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = _freq_range;
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = _rx_ant;
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = rfx_rx_antennas;
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_QI;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = this->get_locked(dboard_iface::UNIT_RX);
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void rfx_xcvr::rx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_FREQ:
+ this->set_rx_lo_freq(val.as<double>());
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ this->set_rx_gain(val.as<float>(), name);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ this->set_rx_ant(val.as<std::string>());
+ return;
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
+
+/***********************************************************************
+ * TX Get and Set
+ **********************************************************************/
+void rfx_xcvr::tx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = get_tx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ case SUBDEV_PROP_GAIN_RANGE:
+ assert_has(rfx_tx_gain_ranges.keys(), name, "rfx tx gain name");
+ //no controllable tx gains, will not get here
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(rfx_tx_gain_ranges.keys());
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = _tx_lo_freq;
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = _freq_range;
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = std::string("TX/RX");
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = rfx_tx_antennas;
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_IQ;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = true;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = this->get_locked(dboard_iface::UNIT_TX);
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void rfx_xcvr::tx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_FREQ:
+ this->set_tx_lo_freq(val.as<double>());
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ this->set_tx_gain(val.as<float>(), name);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ this->set_tx_ant(val.as<std::string>());
+ return;
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
diff --git a/host/lib/usrp/dboard/db_unknown.cpp b/host/lib/usrp/dboard/db_unknown.cpp
new file mode 100644
index 000000000..9dd9b550b
--- /dev/null
+++ b/host/lib/usrp/dboard/db_unknown.cpp
@@ -0,0 +1,249 @@
+//
+// Copyright 2010 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 <uhd/usrp/subdev_props.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/utils/assert.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/format.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * The unknown boards:
+ * Like a basic board, but with only one subdev.
+ **********************************************************************/
+class unknown_rx : public rx_dboard_base{
+public:
+ unknown_rx(ctor_args_t args);
+ ~unknown_rx(void);
+
+ void rx_get(const wax::obj &key, wax::obj &val);
+ void rx_set(const wax::obj &key, const wax::obj &val);
+};
+
+class unknown_tx : public tx_dboard_base{
+public:
+ unknown_tx(ctor_args_t args);
+ ~unknown_tx(void);
+
+ void tx_get(const wax::obj &key, wax::obj &val);
+ void tx_set(const wax::obj &key, const wax::obj &val);
+};
+
+/***********************************************************************
+ * Register the unknown dboards
+ **********************************************************************/
+static dboard_base::sptr make_unknown_rx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new unknown_rx(args));
+}
+
+static dboard_base::sptr make_unknown_tx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new unknown_tx(args));
+}
+
+UHD_STATIC_BLOCK(reg_unknown_dboards){
+ dboard_manager::register_dboard(0xfff0, &make_unknown_tx, "Unknown TX");
+ dboard_manager::register_dboard(0xfff1, &make_unknown_rx, "Unknown RX");
+}
+
+/***********************************************************************
+ * Unknown RX dboard
+ **********************************************************************/
+unknown_rx::unknown_rx(ctor_args_t args) : rx_dboard_base(args){
+ /* NOP */
+}
+
+unknown_rx::~unknown_rx(void){
+ /* NOP */
+}
+
+void unknown_rx::rx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = "Unknown - " + get_rx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ val = float(0);
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ val = gain_range_t(0, 0, 0);
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = double(0);
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = freq_range_t(0, 0);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = std::string("");
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = prop_names_t(1, ""); //vector of 1 empty string
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_IQ;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = true; //there is no LO, so it must be true!
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void unknown_rx::rx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_GAIN:
+ UHD_ASSERT_THROW(val.as<float>() == float(0));
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ UHD_ASSERT_THROW(val.as<std::string>() == std::string(""));
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ return; // it wont do you much good, but you can set it
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
+
+/***********************************************************************
+ * Basic and LF TX dboard
+ **********************************************************************/
+unknown_tx::unknown_tx(ctor_args_t args) : tx_dboard_base(args){
+ /* NOP */
+}
+
+unknown_tx::~unknown_tx(void){
+ /* NOP */
+}
+
+void unknown_tx::tx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = "Unknown - " + get_tx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ val = float(0);
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ val = gain_range_t(0, 0, 0);
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = double(0);
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = freq_range_t(0, 0);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = std::string("");
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = prop_names_t(1, ""); //vector of 1 empty string
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_IQ;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = true; //there is no LO, so it must be true!
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void unknown_tx::tx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_GAIN:
+ UHD_ASSERT_THROW(val.as<float>() == float(0));
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ UHD_ASSERT_THROW(val.as<std::string>() == std::string(""));
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ return; // it wont do you much good, but you can set it
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
diff --git a/host/lib/usrp/dboard/db_wbx.cpp b/host/lib/usrp/dboard/db_wbx.cpp
new file mode 100644
index 000000000..3038ce30b
--- /dev/null
+++ b/host/lib/usrp/dboard/db_wbx.cpp
@@ -0,0 +1,636 @@
+//
+// Copyright 2010 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/>.
+//
+
+// Common IO Pins
+#define ANTSW_IO ((1 << 5)|(1 << 15)) // on UNIT_TX, 0 = TX, 1 = RX, on UNIT_RX 0 = main ant, 1 = RX2
+#define ADF4350_CE (1 << 3)
+#define ADF4350_PDBRF (1 << 2)
+#define ADF4350_MUXOUT (1 << 1) // INPUT!!!
+#define LOCKDET_MASK (1 << 0) // INPUT!!!
+
+// TX IO Pins
+#define TX_PUP_5V (1 << 7) // enables 5.0V power supply
+#define TX_PUP_3V (1 << 6) // enables 3.3V supply
+#define TXMOD_EN (1 << 4) // on UNIT_TX, 1 enables TX Modulator
+
+// RX IO Pins
+#define RX_PUP_5V (1 << 7) // enables 5.0V power supply
+#define RX_PUP_3V (1 << 6) // enables 3.3V supply
+#define RXBB_PDB (1 << 4) // on UNIT_RX, 1 powers up RX baseband
+
+// RX Attenuator Pins
+#define RX_ATTN_SHIFT 8 // lsb of RX Attenuator Control
+#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) // valid bits of RX Attenuator Control
+
+// Mixer functions
+#define TX_MIXER_ENB (TXMOD_EN|ADF4350_PDBRF)
+#define TX_MIXER_DIS 0
+
+#define RX_MIXER_ENB (RXBB_PDB|ADF4350_PDBRF)
+#define RX_MIXER_DIS 0
+
+// Pin functions
+#define TX_POWER_IO (TX_PUP_5V|TX_PUP_3V) // high enables power supply
+#define TXIO_MASK (TX_POWER_IO|ANTSW_IO|ADF4350_CE|ADF4350_PDBRF|TXMOD_EN)
+
+#define RX_POWER_IO (RX_PUP_5V|RX_PUP_3V) // high enables power supply
+#define RXIO_MASK (RX_POWER_IO|ANTSW_IO|ADF4350_CE|ADF4350_PDBRF|RXBB_PDB|RX_ATTN_MASK)
+
+// Power functions
+#define TX_POWER_UP (TX_POWER_IO|ADF4350_CE)
+#define TX_POWER_DOWN 0
+
+#define RX_POWER_UP (RX_POWER_IO|ADF4350_CE)
+#define RX_POWER_DOWN 0
+
+// Antenna constants
+#define ANT_TX 0 //the tx line is transmitting
+#define ANT_RX ANTSW_IO //the tx line is receiving
+#define ANT_TXRX 0 //the rx line is on txrx
+#define ANT_RX2 ANTSW_IO //the rx line in on rx2
+#define ANT_XX 0 //dont care how the antenna is set
+
+#include "adf4350_regs.hpp"
+#include <uhd/types/dict.hpp>
+#include <uhd/usrp/subdev_props.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/utils/assert.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/format.hpp>
+#include <boost/math/special_functions/round.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * The WBX dboard constants
+ **********************************************************************/
+static const bool wbx_debug = false;
+
+static const freq_range_t wbx_freq_range(68.75e6, 2.2e9);
+
+static const prop_names_t wbx_tx_antennas = list_of("TX/RX");
+
+static const prop_names_t wbx_rx_antennas = list_of("TX/RX")("RX2");
+
+static const uhd::dict<std::string, gain_range_t> wbx_tx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 25, float(0.05)))
+;
+
+static const uhd::dict<std::string, gain_range_t> wbx_rx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 31.5, float(0.5)))
+;
+
+/***********************************************************************
+ * The WBX dboard
+ **********************************************************************/
+class wbx_xcvr : public xcvr_dboard_base{
+public:
+ wbx_xcvr(ctor_args_t args);
+ ~wbx_xcvr(void);
+
+ void rx_get(const wax::obj &key, wax::obj &val);
+ void rx_set(const wax::obj &key, const wax::obj &val);
+
+ void tx_get(const wax::obj &key, wax::obj &val);
+ void tx_set(const wax::obj &key, const wax::obj &val);
+
+private:
+ uhd::dict<std::string, float> _tx_gains, _rx_gains;
+ double _rx_lo_freq, _tx_lo_freq;
+ std::string _tx_ant, _rx_ant;
+
+ void set_rx_lo_freq(double freq);
+ void set_tx_lo_freq(double freq);
+ void set_rx_ant(const std::string &ant);
+ void set_tx_ant(const std::string &ant);
+ void set_rx_gain(float gain, const std::string &name);
+ void set_tx_gain(float gain, const std::string &name);
+
+ void update_atr(void);
+
+ /*!
+ * Set the LO frequency for the particular dboard unit.
+ * \param unit which unit rx or tx
+ * \param target_freq the desired frequency in Hz
+ * \return the actual frequency in Hz
+ */
+ double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
+
+ /*!
+ * Get the lock detect status of the LO.
+ * \param unit which unit rx or tx
+ * \return true for locked
+ */
+ bool get_locked(dboard_iface::unit_t unit){
+ return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0;
+ }
+};
+
+/***********************************************************************
+ * Register the WBX dboard (min freq, max freq, rx div2, tx div2)
+ **********************************************************************/
+static dboard_base::sptr make_wbx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new wbx_xcvr(args));
+}
+
+UHD_STATIC_BLOCK(reg_wbx_dboards){
+ dboard_manager::register_dboard(0x0052, 0x0053, &make_wbx, "WBX");
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+wbx_xcvr::wbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){
+
+ //enable the clocks that we need
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true);
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true);
+
+ //set the gpio directions and atr controls (identically)
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK);
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TXIO_MASK);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RXIO_MASK);
+ if (wbx_debug) std::cerr << boost::format(
+ "WBX GPIO Direction: RX: 0x%08x, TX: 0x%08x"
+ ) % RXIO_MASK % TXIO_MASK << std::endl;
+
+ //set some default values
+ set_rx_lo_freq((wbx_freq_range.min + wbx_freq_range.max)/2.0);
+ set_tx_lo_freq((wbx_freq_range.min + wbx_freq_range.max)/2.0);
+ set_rx_ant("RX2");
+
+ BOOST_FOREACH(const std::string &name, wbx_tx_gain_ranges.keys()){
+ set_tx_gain(wbx_tx_gain_ranges[name].min, name);
+ }
+ BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){
+ set_rx_gain(wbx_rx_gain_ranges[name].min, name);
+ }
+}
+
+wbx_xcvr::~wbx_xcvr(void){
+ /* NOP */
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+static int rx_pga0_gain_to_iobits(float &gain){
+ //clip the input
+ gain = std::clip<float>(gain, wbx_rx_gain_ranges["PGA0"].min, wbx_rx_gain_ranges["PGA0"].max);
+
+ //convert to attenuation and update iobits for atr
+ float attn = wbx_rx_gain_ranges["PGA0"].max - gain;
+
+ //calculate the attenuation
+ int attn_code = int(floor(attn*2));
+ int iobits = ((~attn_code) << RX_ATTN_SHIFT) & RX_ATTN_MASK;
+
+
+ if (wbx_debug) std::cerr << boost::format(
+ "WBX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x"
+ ) % attn % attn_code % (iobits & RX_ATTN_MASK) % RX_ATTN_MASK << std::endl;
+
+ //the actual gain setting
+ gain = wbx_rx_gain_ranges["PGA0"].max - float(attn_code)/2;
+
+ return iobits;
+}
+
+static float tx_pga0_gain_to_dac_volts(float &gain){
+ //clip the input
+ gain = std::clip<float>(gain, wbx_tx_gain_ranges["PGA0"].min, wbx_tx_gain_ranges["PGA0"].max);
+
+ //voltage level constants
+ static const float max_volts = float(0.5), min_volts = float(1.4);
+ static const float slope = (max_volts-min_volts)/wbx_tx_gain_ranges["PGA0"].max;
+
+ //calculate the voltage for the aux dac
+ float dac_volts = gain*slope + min_volts;
+
+ if (wbx_debug) std::cerr << boost::format(
+ "WBX TX Gain: %f dB, dac_volts: %f V"
+ ) % gain % dac_volts << std::endl;
+
+ //the actual gain setting
+ gain = (dac_volts - min_volts)/slope;
+
+ return dac_volts;
+}
+
+void wbx_xcvr::set_tx_gain(float gain, const std::string &name){
+ assert_has(wbx_tx_gain_ranges.keys(), name, "wbx tx gain name");
+ if(name == "PGA0"){
+ float dac_volts = tx_pga0_gain_to_dac_volts(gain);
+ _tx_gains[name] = gain;
+
+ //write the new voltage to the aux dac
+ this->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts);
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+}
+
+void wbx_xcvr::set_rx_gain(float gain, const std::string &name){
+ assert_has(wbx_rx_gain_ranges.keys(), name, "wbx rx gain name");
+ if(name == "PGA0"){
+ rx_pga0_gain_to_iobits(gain);
+ _rx_gains[name] = gain;
+
+ //write the new gain to atr regs
+ update_atr();
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+}
+
+/***********************************************************************
+ * Antenna Handling
+ **********************************************************************/
+void wbx_xcvr::update_atr(void){
+ //calculate atr pins
+ int pga0_iobits = rx_pga0_gain_to_iobits(_rx_gains["PGA0"]);
+
+ //setup the tx atr (this does not change with antenna)
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_POWER_UP | ANT_XX | TX_MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, TX_POWER_UP | ANT_RX | TX_MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, TX_POWER_UP | ANT_TX | TX_MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, TX_POWER_UP | ANT_TX | TX_MIXER_ENB);
+
+ //setup the rx atr (this does not change with antenna)
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE,
+ pga0_iobits | RX_POWER_UP | ANT_XX | RX_MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY,
+ pga0_iobits | RX_POWER_UP | ANT_XX | RX_MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX,
+ pga0_iobits | RX_POWER_UP | ANT_RX2| RX_MIXER_ENB);
+
+ //set the rx atr regs that change with antenna setting
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY,
+ pga0_iobits | RX_POWER_UP | RX_MIXER_ENB | ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2));
+ if (wbx_debug) std::cerr << boost::format(
+ "WBX RXONLY ATR REG: 0x%08x"
+ ) % (pga0_iobits | RX_POWER_UP | RX_MIXER_ENB | ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2)) << std::endl;
+}
+
+void wbx_xcvr::set_rx_ant(const std::string &ant){
+ //validate input
+ assert_has(wbx_rx_antennas, ant, "wbx rx antenna name");
+
+ //shadow the setting
+ _rx_ant = ant;
+
+ //write the new antenna setting to atr regs
+ update_atr();
+}
+
+void wbx_xcvr::set_tx_ant(const std::string &ant){
+ assert_has(wbx_tx_antennas, ant, "wbx tx antenna name");
+ //only one antenna option, do nothing
+}
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+void wbx_xcvr::set_rx_lo_freq(double freq){
+ _rx_lo_freq = set_lo_freq(dboard_iface::UNIT_RX, freq);
+}
+
+void wbx_xcvr::set_tx_lo_freq(double freq){
+ _tx_lo_freq = set_lo_freq(dboard_iface::UNIT_TX, freq);
+}
+
+double wbx_xcvr::set_lo_freq(
+ dboard_iface::unit_t unit,
+ double target_freq
+){
+ if (wbx_debug) std::cerr << boost::format(
+ "WBX tune: target frequency %f Mhz"
+ ) % (target_freq/1e6) << std::endl;
+
+ //clip the input
+ target_freq = std::clip(target_freq, wbx_freq_range.min, wbx_freq_range.max);
+
+ //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler)
+ static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of
+ (0,23) //adf4350_regs_t::PRESCALER_4_5
+ (1,75) //adf4350_regs_t::PRESCALER_8_9
+ ;
+
+ //map rf divider select output dividers to enums
+ static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
+ (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1)
+ (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2)
+ (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4)
+ (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8)
+ (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16)
+ ;
+
+ double actual_freq, pfd_freq;
+ double ref_freq = this->get_iface()->get_clock_rate(unit);
+ int R=0, BS=0, N=0, FRAC=0, MOD=0;
+ int RFdiv = 1;
+ adf4350_regs_t::reference_divide_by_2_t T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
+ adf4350_regs_t::reference_doubler_t D = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;
+
+ //Reference doubler for 50% duty cycle
+ // if ref_freq < 12.5MHz enable regs.reference_divide_by_2
+ if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED;
+
+ //increase RF divider until acceptable VCO frequency
+ //start with target_freq*2 because mixer has divide by 2
+ double vco_freq = target_freq*2;
+ while (vco_freq < 2.2e9) {
+ vco_freq *= 2;
+ RFdiv *= 2;
+ }
+
+ //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
+ adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5;
+
+ /*
+ * 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 exists when it meets all of the constraints.
+ * The resulting loop values are loaded into the registers.
+ *
+ * from pg.21
+ *
+ * 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)
+ * f_actual = f_rf/2
+ */
+ for(R = 1; R <= 1023; R+=1){
+ //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 25MHz (Loop Filter Bandwidth)
+ if (pfd_freq > 25e6) continue;
+
+ //ignore fractional part of tuning
+ N = int(std::floor(vco_freq/pfd_freq));
+
+ //keep N > minimum int divider requirement
+ if (N < prescaler_to_min_int_div[prescaler]) continue;
+
+ for(BS=1; BS <= 255; BS+=1){
+ //keep the band select frequency at or below 100KHz
+ //constraint on band select clock
+ if (pfd_freq/BS > 100e3) continue;
+ goto done_loop;
+ }
+ } done_loop:
+
+ //Fractional-N calculation
+ MOD = 4095; //max fractional accuracy
+ FRAC = int((vco_freq/pfd_freq - N)*MOD);
+
+ //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 = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED;
+ R /= 2;
+ }
+
+ //actual frequency calculation
+ actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2);
+
+
+ if (wbx_debug) {
+ std::cerr << boost::format("WBX 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;
+
+ std::cerr << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%d"
+ ) % R % BS % N % FRAC % MOD % T % D % RFdiv % get_locked(unit)<< std::endl
+ << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f"
+ ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl;
+ }
+
+ //load the register values
+ adf4350_regs_t regs;
+
+ regs.frac_12_bit = FRAC;
+ regs.int_16_bit = N;
+ regs.mod_12_bit = MOD;
+ regs.prescaler = prescaler;
+ regs.r_counter_10_bit = R;
+ regs.reference_divide_by_2 = T;
+ regs.reference_doubler = D;
+ regs.band_select_clock_div = BS;
+ UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv));
+ regs.rf_divider_select = rfdivsel_to_enum[RFdiv];
+
+ //write the registers
+ //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
+ int addr;
+
+ for(addr=5; addr>=0; addr--){
+ if (wbx_debug) std::cerr << boost::format(
+ "WBX SPI Reg (0x%02x): 0x%08x"
+ ) % addr % regs.get_reg(addr) << std::endl;
+ this->get_iface()->write_spi(
+ unit, spi_config_t::EDGE_RISE,
+ regs.get_reg(addr), 32
+ );
+ }
+
+ //return the actual frequency
+ if (wbx_debug) std::cerr << boost::format(
+ "WBX tune: actual frequency %f Mhz"
+ ) % (actual_freq/1e6) << std::endl;
+ return actual_freq;
+}
+
+/***********************************************************************
+ * RX Get and Set
+ **********************************************************************/
+void wbx_xcvr::rx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = get_rx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ assert_has(_rx_gains.keys(), name, "wbx rx gain name");
+ val = _rx_gains[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ assert_has(wbx_rx_gain_ranges.keys(), name, "wbx rx gain name");
+ val = wbx_rx_gain_ranges[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(wbx_rx_gain_ranges.keys());
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = _rx_lo_freq;
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = wbx_freq_range;
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = _rx_ant;
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = wbx_rx_antennas;
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_IQ;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = this->get_locked(dboard_iface::UNIT_RX);
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void wbx_xcvr::rx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_FREQ:
+ this->set_rx_lo_freq(val.as<double>());
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ this->set_rx_gain(val.as<float>(), name);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ this->set_rx_ant(val.as<std::string>());
+ return;
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
+
+/***********************************************************************
+ * TX Get and Set
+ **********************************************************************/
+void wbx_xcvr::tx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = get_tx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ assert_has(_tx_gains.keys(), name, "wbx tx gain name");
+ val = _tx_gains[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ assert_has(wbx_tx_gain_ranges.keys(), name, "wbx tx gain name");
+ val = wbx_tx_gain_ranges[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(wbx_tx_gain_ranges.keys());
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = _tx_lo_freq;
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = wbx_freq_range;
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = std::string("TX/RX");
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = wbx_tx_antennas;
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_IQ;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = this->get_locked(dboard_iface::UNIT_TX);
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void wbx_xcvr::tx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_FREQ:
+ this->set_tx_lo_freq(val.as<double>());
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ this->set_tx_gain(val.as<float>(), name);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ this->set_tx_ant(val.as<std::string>());
+ return;
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp
new file mode 100644
index 000000000..2c94bcd2d
--- /dev/null
+++ b/host/lib/usrp/dboard/db_xcvr2450.cpp
@@ -0,0 +1,611 @@
+//
+// Copyright 2010 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/>.
+//
+
+// TX IO Pins
+#define HB_PA_OFF_TXIO (1 << 15) // 5GHz PA, 1 = off, 0 = on
+#define LB_PA_OFF_TXIO (1 << 14) // 2.4GHz PA, 1 = off, 0 = on
+#define ANTSEL_TX1_RX2_TXIO (1 << 13) // 1 = Ant 1 to TX, Ant 2 to RX
+#define ANTSEL_TX2_RX1_TXIO (1 << 12) // 1 = Ant 2 to TX, Ant 1 to RX
+#define TX_EN_TXIO (1 << 11) // 1 = TX on, 0 = TX off
+#define AD9515DIV_TXIO (1 << 4) // 1 = Div by 3, 0 = Div by 2
+
+#define TXIO_MASK (HB_PA_OFF_TXIO | LB_PA_OFF_TXIO | ANTSEL_TX1_RX2_TXIO | ANTSEL_TX2_RX1_TXIO | TX_EN_TXIO | AD9515DIV_TXIO)
+
+// TX IO Functions
+#define HB_PA_TXIO LB_PA_OFF_TXIO
+#define LB_PA_TXIO HB_PA_OFF_TXIO
+#define TX_ENB_TXIO TX_EN_TXIO
+#define TX_DIS_TXIO 0
+#define AD9515DIV_3_TXIO AD9515DIV_TXIO
+#define AD9515DIV_2_TXIO 0
+
+// RX IO Pins
+#define LOCKDET_RXIO (1 << 15) // This is an INPUT!!!
+#define POWER_RXIO (1 << 14) // 1 = power on, 0 = shutdown
+#define RX_EN_RXIO (1 << 13) // 1 = RX on, 0 = RX off
+#define RX_HP_RXIO (1 << 12) // 0 = Fc set by rx_hpf, 1 = 600 KHz
+
+#define RXIO_MASK (POWER_RXIO | RX_EN_RXIO | RX_HP_RXIO)
+
+// RX IO Functions
+#define POWER_UP_RXIO POWER_RXIO
+#define POWER_DOWN_RXIO 0
+#define RX_ENB_RXIO RX_EN_RXIO
+#define RX_DIS_RXIO 0
+
+#include "max2829_regs.hpp"
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/assert.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/usrp/subdev_props.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/format.hpp>
+#include <boost/thread.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <utility>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * The XCVR 2450 constants
+ **********************************************************************/
+static const bool xcvr2450_debug = false;
+
+static const freq_range_t xcvr_freq_range(2.4e9, 6.0e9);
+
+static const prop_names_t xcvr_antennas = list_of("J1")("J2");
+
+static const uhd::dict<std::string, gain_range_t> xcvr_tx_gain_ranges = map_list_of
+ ("VGA", gain_range_t(0, 30, 0.5))
+ ("BB", gain_range_t(0, 5, 1.5))
+;
+static const uhd::dict<std::string, gain_range_t> xcvr_rx_gain_ranges = map_list_of
+ ("LNA", gain_range_t(0, 30.5, 15))
+ ("VGA", gain_range_t(0, 62, 2.0))
+;
+
+/***********************************************************************
+ * The XCVR 2450 dboard class
+ **********************************************************************/
+class xcvr2450 : public xcvr_dboard_base{
+public:
+ xcvr2450(ctor_args_t args);
+ ~xcvr2450(void);
+
+ void rx_get(const wax::obj &key, wax::obj &val);
+ void rx_set(const wax::obj &key, const wax::obj &val);
+
+ void tx_get(const wax::obj &key, wax::obj &val);
+ void tx_set(const wax::obj &key, const wax::obj &val);
+
+private:
+ double _lo_freq;
+ uhd::dict<std::string, float> _tx_gains, _rx_gains;
+ std::string _tx_ant, _rx_ant;
+ int _ad9515div;
+ max2829_regs_t _max2829_regs;
+
+ void set_lo_freq(double target_freq);
+ void set_tx_ant(const std::string &ant);
+ void set_rx_ant(const std::string &ant);
+ void set_tx_gain(float gain, const std::string &name);
+ void set_rx_gain(float gain, const std::string &name);
+
+ void update_atr(void);
+ void spi_reset(void);
+ void send_reg(boost::uint8_t addr){
+ boost::uint32_t value = _max2829_regs.get_reg(addr);
+ if(xcvr2450_debug) std::cerr << boost::format(
+ "XCVR2450: send reg 0x%02x, value 0x%05x"
+ ) % int(addr) % value << std::endl;
+ this->get_iface()->write_spi(
+ dboard_iface::UNIT_RX,
+ spi_config_t::EDGE_RISE,
+ value, 24
+ );
+ }
+
+ static bool is_highband(double freq){return freq > 3e9;}
+
+ /*!
+ * Is the LO locked?
+ * \return true for locked
+ */
+ bool get_locked(void){
+ return (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & LOCKDET_RXIO) != 0;
+ }
+
+ /*!
+ * Read the RSSI from the aux adc
+ * \return the rssi in dB
+ */
+ float get_rssi(void){
+ //constants for the rssi calculation
+ static const float min_v = float(0.5), max_v = float(2.5);
+ static const float rssi_dyn_range = 60;
+ //calculate the rssi from the voltage
+ float voltage = this->get_iface()->read_aux_adc(dboard_iface::UNIT_RX, dboard_iface::AUX_ADC_B);
+ return rssi_dyn_range*(voltage - min_v)/(max_v - min_v);
+ }
+};
+
+/***********************************************************************
+ * Register the XCVR 2450 dboard
+ **********************************************************************/
+static dboard_base::sptr make_xcvr2450(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new xcvr2450(args));
+}
+
+UHD_STATIC_BLOCK(reg_xcvr2450_dboard){
+ //register the factory function for the rx and tx dbids
+ dboard_manager::register_dboard(0x0061, 0x0060, &make_xcvr2450, "XCVR2450");
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){
+ //enable only the clocks we need
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true);
+
+ //set the gpio directions and atr controls (identically)
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK);
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TXIO_MASK);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RXIO_MASK);
+
+ spi_reset(); //prepare the spi
+
+ //setup the misc max2829 registers
+ _max2829_regs.mimo_select = max2829_regs_t::MIMO_SELECT_MIMO;
+ _max2829_regs.band_sel_mimo = max2829_regs_t::BAND_SEL_MIMO_MIMO;
+ _max2829_regs.pll_cp_select = max2829_regs_t::PLL_CP_SELECT_4MA;
+ _max2829_regs.rssi_high_bw = max2829_regs_t::RSSI_HIGH_BW_6MHZ;
+ _max2829_regs.tx_lpf_coarse_adj = max2829_regs_t::TX_LPF_COARSE_ADJ_12MHZ;
+ _max2829_regs.rx_lpf_coarse_adj = max2829_regs_t::RX_LPF_COARSE_ADJ_9_5MHZ;
+ _max2829_regs.rx_lpf_fine_adj = max2829_regs_t::RX_LPF_FINE_ADJ_95;
+ _max2829_regs.rx_vga_gain_spi = max2829_regs_t::RX_VGA_GAIN_SPI_SPI;
+ _max2829_regs.rssi_output_range = max2829_regs_t::RSSI_OUTPUT_RANGE_HIGH;
+ _max2829_regs.rssi_op_mode = max2829_regs_t::RSSI_OP_MODE_ENABLED;
+ _max2829_regs.rssi_pin_fcn = max2829_regs_t::RSSI_PIN_FCN_RSSI;
+ _max2829_regs.rx_highpass = max2829_regs_t::RX_HIGHPASS_100HZ;
+ _max2829_regs.tx_vga_gain_spi = max2829_regs_t::TX_VGA_GAIN_SPI_SPI;
+ _max2829_regs.pa_driver_linearity = max2829_regs_t::PA_DRIVER_LINEARITY_78;
+ _max2829_regs.tx_vga_linearity = max2829_regs_t::TX_VGA_LINEARITY_78;
+ _max2829_regs.tx_upconv_linearity = max2829_regs_t::TX_UPCONV_LINEARITY_78;
+
+ //send initial register settings
+ for(boost::uint8_t reg = 0x2; reg <= 0xC; reg++){
+ this->send_reg(reg);
+ }
+
+ //set defaults for LO, gains, antennas
+ set_lo_freq(2.45e9);
+ set_rx_ant(xcvr_antennas.at(0));
+ set_tx_ant(xcvr_antennas.at(1));
+ BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){
+ set_tx_gain(xcvr_tx_gain_ranges[name].min, name);
+ }
+ BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){
+ set_rx_gain(xcvr_rx_gain_ranges[name].min, name);
+ }
+}
+
+xcvr2450::~xcvr2450(void){
+ spi_reset();
+}
+
+void xcvr2450::spi_reset(void){
+ //spi reset mode: global enable = off, tx and rx enable = on
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_ENB_TXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(10));
+
+ //take it back out of spi reset mode and wait a bit
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(10));
+}
+
+void xcvr2450::update_atr(void){
+ //calculate tx atr pins
+ int band_sel = (xcvr2450::is_highband(_lo_freq))? HB_PA_TXIO : LB_PA_TXIO;
+ int tx_ant_sel = (_tx_ant == "J1")? ANTSEL_TX1_RX2_TXIO : ANTSEL_TX2_RX1_TXIO;
+ int rx_ant_sel = (_rx_ant == "J2")? ANTSEL_TX1_RX2_TXIO : ANTSEL_TX2_RX1_TXIO;
+ int xx_ant_sel = tx_ant_sel; //prefer the tx antenna selection for full duplex (rx will get the other antenna)
+ int ad9515div = (_ad9515div == 3)? AD9515DIV_3_TXIO : AD9515DIV_2_TXIO;
+
+ //set the tx registers
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel);
+
+ //set the rx registers
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_ENB_RXIO);
+}
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+void xcvr2450::set_lo_freq(double target_freq){
+ target_freq = std::clip(target_freq, xcvr_freq_range.min, xcvr_freq_range.max);
+ //TODO: clip for highband and lowband
+
+ //variables used in the calculation below
+ double scaler = xcvr2450::is_highband(target_freq)? (4.0/5.0) : (4.0/3.0);
+ double ref_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_TX);
+ int R, intdiv, fracdiv;
+
+ //loop through values until we get a match
+ for(_ad9515div = 2; _ad9515div <= 3; _ad9515div++){
+ for(R = 1; R <= 7; R++){
+ double N = (target_freq*scaler*R*_ad9515div)/ref_freq;
+ intdiv = int(std::floor(N));
+ fracdiv = boost::math::iround((N - intdiv)*double(1 << 16));
+ //actual minimum is 128, but most chips seems to require higher to lock
+ if (intdiv < 131 or intdiv > 255) continue;
+ //constraints met: exit loop
+ goto done_loop;
+ }
+ } done_loop:
+
+ //calculate the actual freq from the values above
+ double N = double(intdiv) + double(fracdiv)/double(1 << 16);
+ _lo_freq = (N*ref_freq)/(scaler*R*_ad9515div);
+
+ if (xcvr2450_debug) std::cerr
+ << boost::format("XCVR2450 tune:\n")
+ << boost::format(" R=%d, N=%f, ad9515=%d, scaler=%f\n") % R % N % _ad9515div % scaler
+ << boost::format(" Ref Freq=%fMHz\n") % (ref_freq/1e6)
+ << boost::format(" Target Freq=%fMHz\n") % (target_freq/1e6)
+ << boost::format(" Actual Freq=%fMHz\n") % (_lo_freq/1e6)
+ << std::endl;
+
+ //high-high band or low-high band?
+ if(_lo_freq > (5.35e9 + 5.47e9)/2.0){
+ if (xcvr2450_debug) std::cerr << "XCVR2450 tune: Using high-high band" << std::endl;
+ _max2829_regs.band_select_802_11a = max2829_regs_t::BAND_SELECT_802_11A_5_47GHZ_TO_5_875GHZ;
+ }else{
+ if (xcvr2450_debug) std::cerr << "XCVR2450 tune: Using low-high band" << std::endl;
+ _max2829_regs.band_select_802_11a = max2829_regs_t::BAND_SELECT_802_11A_4_9GHZ_TO_5_35GHZ;
+ }
+
+ //new band select settings and ad9515 divider
+ this->update_atr();
+
+ //load new counters into registers
+ _max2829_regs.int_div_ratio_word = intdiv;
+ _max2829_regs.frac_div_ratio_lsb = fracdiv & 0x3;
+ _max2829_regs.frac_div_ratio_msb = fracdiv >> 2;
+ this->send_reg(0x3); //integer
+ this->send_reg(0x4); //fractional
+
+ //load the reference divider and band select into registers
+ //toggle the bandswitch from off to automatic (which really means start)
+ _max2829_regs.ref_divider = R;
+ _max2829_regs.band_select = (xcvr2450::is_highband(_lo_freq))?
+ max2829_regs_t::BAND_SELECT_5GHZ :
+ max2829_regs_t::BAND_SELECT_2_4GHZ ;
+ _max2829_regs.vco_bandswitch = max2829_regs_t::VCO_BANDSWITCH_DISABLE;
+ this->send_reg(0x5);
+ _max2829_regs.vco_bandswitch = max2829_regs_t::VCO_BANDSWITCH_AUTOMATIC;;
+ this->send_reg(0x5);
+}
+
+/***********************************************************************
+ * Antenna Handling
+ **********************************************************************/
+void xcvr2450::set_tx_ant(const std::string &ant){
+ assert_has(xcvr_antennas, ant, "xcvr antenna name");
+ _tx_ant = ant;
+ this->update_atr(); //sets the atr to the new antenna setting
+}
+
+void xcvr2450::set_rx_ant(const std::string &ant){
+ assert_has(xcvr_antennas, ant, "xcvr antenna name");
+ _rx_ant = ant;
+ this->update_atr(); //sets the atr to the new antenna setting
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+/*!
+ * Convert a requested gain for the tx vga into the integer register value.
+ * The gain passed into the function will be set to the actual value.
+ * \param gain the requested gain in dB
+ * \return 6 bit the register value
+ */
+static int gain_to_tx_vga_reg(float &gain){
+ //calculate the register value
+ int reg = std::clip(boost::math::iround(gain*60/30.0) + 3, 0, 63);
+
+ //calculate the actual gain value
+ if (reg < 4) gain = 0;
+ else if (reg < 48) gain = float(reg/2 - 1);
+ else gain = float(reg/2.0 - 1.5);
+
+ //return register value
+ return reg;
+}
+
+/*!
+ * Convert a requested gain for the tx bb into the integer register value.
+ * The gain passed into the function will be set to the actual value.
+ * \param gain the requested gain in dB
+ * \return gain enum value
+ */
+static max2829_regs_t::tx_baseband_gain_t gain_to_tx_bb_reg(float &gain){
+ int reg = std::clip(boost::math::iround(gain*3/5.0), 0, 3);
+ switch(reg){
+ case 0:
+ gain = 0;
+ return max2829_regs_t::TX_BASEBAND_GAIN_0DB;
+ case 1:
+ gain = 2;
+ return max2829_regs_t::TX_BASEBAND_GAIN_2DB;
+ case 2:
+ gain = 3.5;
+ return max2829_regs_t::TX_BASEBAND_GAIN_3_5DB;
+ case 3:
+ gain = 5;
+ return max2829_regs_t::TX_BASEBAND_GAIN_5DB;
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+/*!
+ * Convert a requested gain for the rx vga into the integer register value.
+ * The gain passed into the function will be set to the actual value.
+ * \param gain the requested gain in dB
+ * \return 5 bit the register value
+ */
+static int gain_to_rx_vga_reg(float &gain){
+ int reg = std::clip(boost::math::iround(gain/2.0), 0, 31);
+ gain = float(reg*2);
+ return reg;
+}
+
+/*!
+ * Convert a requested gain for the rx lna into the integer register value.
+ * The gain passed into the function will be set to the actual value.
+ * \param gain the requested gain in dB
+ * \return 2 bit the register value
+ */
+static int gain_to_rx_lna_reg(float &gain){
+ int reg = std::clip(boost::math::iround(gain*2/30.5) + 1, 0, 3);
+ switch(reg){
+ case 0:
+ case 1: gain = 0; break;
+ case 2: gain = 15; break;
+ case 3: gain = 30.5; break;
+ }
+ return reg;
+}
+
+void xcvr2450::set_tx_gain(float gain, const std::string &name){
+ assert_has(xcvr_tx_gain_ranges.keys(), name, "xcvr tx gain name");
+ if (name == "VGA"){
+ _max2829_regs.tx_vga_gain = gain_to_tx_vga_reg(gain);
+ send_reg(0xC);
+ }
+ else if(name == "BB"){
+ _max2829_regs.tx_baseband_gain = gain_to_tx_bb_reg(gain);
+ send_reg(0x9);
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ _tx_gains[name] = gain;
+}
+
+void xcvr2450::set_rx_gain(float gain, const std::string &name){
+ assert_has(xcvr_rx_gain_ranges.keys(), name, "xcvr rx gain name");
+ if (name == "VGA"){
+ _max2829_regs.rx_vga_gain = gain_to_rx_vga_reg(gain);
+ send_reg(0xB);
+ }
+ else if(name == "LNA"){
+ _max2829_regs.rx_lna_gain = gain_to_rx_lna_reg(gain);
+ send_reg(0xB);
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ _rx_gains[name] = gain;
+}
+
+/***********************************************************************
+ * RX Get and Set
+ **********************************************************************/
+void xcvr2450::rx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = get_rx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ assert_has(_rx_gains.keys(), name, "xcvr rx gain name");
+ val = _rx_gains[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ assert_has(xcvr_rx_gain_ranges.keys(), name, "xcvr rx gain name");
+ val = xcvr_rx_gain_ranges[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(xcvr_rx_gain_ranges.keys());
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = _lo_freq;
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = xcvr_freq_range;
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = _rx_ant;
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = xcvr_antennas;
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_IQ;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = this->get_locked();
+ return;
+
+ case SUBDEV_PROP_RSSI:
+ val = this->get_rssi();
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void xcvr2450::rx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_FREQ:
+ this->set_lo_freq(val.as<double>());
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ this->set_rx_gain(val.as<float>(), name);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ this->set_rx_ant(val.as<std::string>());
+ return;
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}
+
+/***********************************************************************
+ * TX Get and Set
+ **********************************************************************/
+void xcvr2450::tx_get(const wax::obj &key_, wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+ case SUBDEV_PROP_NAME:
+ val = get_tx_id().to_pp_string();
+ return;
+
+ case SUBDEV_PROP_OTHERS:
+ val = prop_names_t(); //empty
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ assert_has(_tx_gains.keys(), name, "xcvr tx gain name");
+ val = _tx_gains[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_RANGE:
+ assert_has(xcvr_tx_gain_ranges.keys(), name, "xcvr tx gain name");
+ val = xcvr_tx_gain_ranges[name];
+ return;
+
+ case SUBDEV_PROP_GAIN_NAMES:
+ val = prop_names_t(xcvr_tx_gain_ranges.keys());
+ return;
+
+ case SUBDEV_PROP_FREQ:
+ val = _lo_freq;
+ return;
+
+ case SUBDEV_PROP_FREQ_RANGE:
+ val = xcvr_freq_range;
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ val = _tx_ant;
+ return;
+
+ case SUBDEV_PROP_ANTENNA_NAMES:
+ val = xcvr_antennas;
+ return;
+
+ case SUBDEV_PROP_CONNECTION:
+ val = SUBDEV_CONN_COMPLEX_QI;
+ return;
+
+ case SUBDEV_PROP_USE_LO_OFFSET:
+ val = false;
+ return;
+
+ case SUBDEV_PROP_LO_LOCKED:
+ val = this->get_locked();
+ return;
+
+ default: UHD_THROW_PROP_GET_ERROR();
+ }
+}
+
+void xcvr2450::tx_set(const wax::obj &key_, const wax::obj &val){
+ wax::obj key; std::string name;
+ boost::tie(key, name) = extract_named_prop(key_);
+
+ //handle the get request conditioned on the key
+ switch(key.as<subdev_prop_t>()){
+
+ case SUBDEV_PROP_FREQ:
+ set_lo_freq(val.as<double>());
+ return;
+
+ case SUBDEV_PROP_GAIN:
+ this->set_tx_gain(val.as<float>(), name);
+ return;
+
+ case SUBDEV_PROP_ANTENNA:
+ this->set_tx_ant(val.as<std::string>());
+ return;
+
+ default: UHD_THROW_PROP_SET_ERROR();
+ }
+}