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.txt40
-rw-r--r--host/lib/usrp/dboard/db_basic_and_lf.cpp209
-rw-r--r--host/lib/usrp/dboard/db_dbsrx.cpp544
-rw-r--r--host/lib/usrp/dboard/db_dbsrx2.cpp379
-rw-r--r--host/lib/usrp/dboard/db_rfx.cpp449
-rw-r--r--host/lib/usrp/dboard/db_sbx_common.cpp356
-rw-r--r--host/lib/usrp/dboard/db_sbx_common.hpp226
-rw-r--r--host/lib/usrp/dboard/db_sbx_version3.cpp193
-rw-r--r--host/lib/usrp/dboard/db_sbx_version4.cpp196
-rw-r--r--host/lib/usrp/dboard/db_tvrx.cpp411
-rw-r--r--host/lib/usrp/dboard/db_tvrx2.cpp1844
-rw-r--r--host/lib/usrp/dboard/db_unknown.cpp160
-rw-r--r--host/lib/usrp/dboard/db_wbx_common.cpp151
-rw-r--r--host/lib/usrp/dboard/db_wbx_common.hpp204
-rw-r--r--host/lib/usrp/dboard/db_wbx_simple.cpp162
-rw-r--r--host/lib/usrp/dboard/db_wbx_version2.cpp343
-rw-r--r--host/lib/usrp/dboard/db_wbx_version3.cpp375
-rw-r--r--host/lib/usrp/dboard/db_wbx_version4.cpp358
-rw-r--r--host/lib/usrp/dboard/db_xcvr2450.cpp673
19 files changed, 7273 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..b000c7f33
--- /dev/null
+++ b/host/lib/usrp/dboard/CMakeLists.txt
@@ -0,0 +1,40 @@
+#
+# Copyright 2010-2011 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+########################################################################
+# This file included, use CMake directory variables
+########################################################################
+
+LIBUHD_APPEND_SOURCES(
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_basic_and_lf.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_rfx.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_xcvr2450.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_common.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_version3.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_version4.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_common.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version2.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version3.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version4.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_simple.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_unknown.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.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..2b30dab52
--- /dev/null
+++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp
@@ -0,0 +1,209 @@
+//
+// Copyright 2010-2012 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/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+//! provider function for the always zero freq
+static double always_zero_freq(void){return 0.0;}
+
+/***********************************************************************
+ * Constants
+ **********************************************************************/
+static const uhd::dict<std::string, double> subdev_bandwidth_scalar = map_list_of
+ ("A", 1.0)
+ ("B", 1.0)
+ ("AB", 2.0)
+ ("BA", 2.0)
+;
+
+/***********************************************************************
+ * 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);
+
+private:
+ double _max_freq;
+};
+
+class basic_tx : public tx_dboard_base{
+public:
+ basic_tx(ctor_args_t args, double max_freq);
+ ~basic_tx(void);
+
+private:
+ double _max_freq;
+};
+
+static const uhd::dict<std::string, std::string> sd_name_to_conn = map_list_of
+ ("AB", "IQ")
+ ("BA", "QI")
+ ("A", "I")
+ ("B", "Q")
+;
+
+/***********************************************************************
+ * 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, 250e6));
+}
+
+static dboard_base::sptr make_basic_tx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new basic_tx(args, 250e6));
+}
+
+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", sd_name_to_conn.keys());
+ dboard_manager::register_dboard(0x0001, &make_basic_rx, "Basic RX", sd_name_to_conn.keys());
+ dboard_manager::register_dboard(0x000e, &make_lf_tx, "LF TX", sd_name_to_conn.keys());
+ dboard_manager::register_dboard(0x000f, &make_lf_rx, "LF RX", sd_name_to_conn.keys());
+}
+
+/***********************************************************************
+ * Basic and LF RX dboard
+ **********************************************************************/
+basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){
+ _max_freq = max_freq;
+ //this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register properties
+ ////////////////////////////////////////////////////////////////////
+ if(get_rx_id() == 0x0001){
+ this->get_rx_subtree()->create<std::string>("name").set(
+ std::string(str(boost::format("BasicRX (%s)") % get_subdev_name()
+ )));
+ }
+ else{
+ this->get_rx_subtree()->create<std::string>("name").set(
+ std::string(str(boost::format("LFRX (%s)") % get_subdev_name()
+ )));
+ }
+
+ this->get_rx_subtree()->create<int>("gains"); //phony property so this dir exists
+ this->get_rx_subtree()->create<double>("freq/value")
+ .publish(&always_zero_freq);
+ this->get_rx_subtree()->create<meta_range_t>("freq/range")
+ .set(freq_range_t(-_max_freq, +_max_freq));
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .set("");
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(list_of(""));
+ this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists
+ this->get_rx_subtree()->create<std::string>("connection")
+ .set(sd_name_to_conn[get_subdev_name()]);
+ this->get_rx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value")
+ .set(subdev_bandwidth_scalar[get_subdev_name()]*_max_freq);
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(subdev_bandwidth_scalar[get_subdev_name()]*_max_freq, subdev_bandwidth_scalar[get_subdev_name()]*_max_freq));
+
+ //disable RX dboard clock by default
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, false);
+
+ //set GPIOs to output 0x0000 to decrease noise pickup
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0000);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0xFFFF);
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0x0000);
+}
+
+basic_rx::~basic_rx(void){
+ /* NOP */
+}
+
+/***********************************************************************
+ * Basic and LF TX dboard
+ **********************************************************************/
+basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){
+ _max_freq = max_freq;
+ //this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register properties
+ ////////////////////////////////////////////////////////////////////
+ if(get_tx_id() == 0x0000){
+ this->get_tx_subtree()->create<std::string>("name").set(
+ std::string(str(boost::format("BasicTX (%s)") % get_subdev_name()
+ )));
+ }
+ else{
+ this->get_tx_subtree()->create<std::string>("name").set(
+ std::string(str(boost::format("LFTX (%s)") % get_subdev_name()
+ )));
+ }
+
+ this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists
+ this->get_tx_subtree()->create<double>("freq/value")
+ .publish(&always_zero_freq);
+ this->get_tx_subtree()->create<meta_range_t>("freq/range")
+ .set(freq_range_t(-_max_freq, +_max_freq));
+ this->get_tx_subtree()->create<std::string>("antenna/value")
+ .set("");
+ this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(list_of(""));
+ this->get_tx_subtree()->create<int>("sensors"); //phony property so this dir exists
+ this->get_tx_subtree()->create<std::string>("connection")
+ .set(sd_name_to_conn[get_subdev_name()]);
+ this->get_tx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_tx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_tx_subtree()->create<double>("bandwidth/value")
+ .set(subdev_bandwidth_scalar[get_subdev_name()]*_max_freq);
+ this->get_tx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(subdev_bandwidth_scalar[get_subdev_name()]*_max_freq, subdev_bandwidth_scalar[get_subdev_name()]*_max_freq));
+
+ //disable TX dboard clock by default
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, false);
+
+ //set GPIOs to output 0x0000 to decrease noise pickup
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, 0x0000);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, 0xFFFF);
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0x0000);
+}
+
+basic_tx::~basic_tx(void){
+ /* NOP */
+}
diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp
new file mode 100644
index 000000000..b1cee4aa7
--- /dev/null
+++ b/host/lib/usrp/dboard/db_dbsrx.cpp
@@ -0,0 +1,544 @@
+//
+// Copyright 2010-2012 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/log.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/types/dict.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 freq_range_t dbsrx_freq_range(0.8e9, 2.4e9);
+
+//Multiplied by 2.0 for conversion to complex bandpass from lowpass
+static const freq_range_t dbsrx_bandwidth_range(2.0*4.0e6, 2.0*33.0e6);
+
+static const freq_range_t dbsrx_pfd_freq_range(0.15e6, 2.01e6);
+
+static const std::vector<std::string> 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))
+;
+
+static const double usrp1_gpio_clock_rate_limit = 4e6;
+
+/***********************************************************************
+ * The DBSRX dboard class
+ **********************************************************************/
+class dbsrx : public rx_dboard_base{
+public:
+ dbsrx(ctor_args_t args);
+ ~dbsrx(void);
+
+private:
+ double _lo_freq;
+ double _bandwidth;
+ uhd::dict<std::string, double> _gains;
+ max2118_write_regs_t _max2118_write_regs;
+ max2118_read_regs_t _max2118_read_regs;
+ boost::uint8_t _max2118_addr(void){
+ return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x65 : 0x67;
+ };
+
+ double set_lo_freq(double target_freq);
+ double set_gain(double gain, const std::string &name);
+ double set_bandwidth(double bandwidth);
+
+ void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){
+ start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x5));
+ stop_reg = boost::uint8_t(uhd::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);
+ UHD_LOGV(often) << 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(uhd::clip(int(start_reg), 0x0, 0x1));
+ stop_reg = boost::uint8_t(uhd::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]);
+ }
+ UHD_LOGV(often) << 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;
+ }
+ }
+ }
+
+ /*!
+ * Get the lock detect status of the LO.
+ * \return sensor for locked
+ */
+ sensor_value_t 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;
+
+ UHD_LOGV(often) << boost::format(
+ "DBSRX: locked %d"
+ ) % locked << std::endl;
+
+ return sensor_value_t("LO", locked, "locked", "unlocked");
+ }
+};
+
+/***********************************************************************
+ * Register the DBSRX dboard
+ **********************************************************************/
+static dboard_base::sptr make_dbsrx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new dbsrx(args));
+}
+
+UHD_STATIC_BLOCK(reg_dbsrx_dboard){
+ //register the factory function for the rx dbid (others version)
+ dboard_manager::register_dboard(0x000D, &make_dbsrx, "DBSRX");
+ //register the factory function for the rx dbid (USRP1 version)
+ dboard_manager::register_dboard(0x0002, &make_dbsrx, "DBSRX");
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){
+ //warn user about incorrect DBID on USRP1, requires R193 populated
+ if (this->get_iface()->get_special_props().soft_clock_divider and this->get_rx_id() == 0x000D)
+ UHD_MSG(warning) << boost::format(
+ "DBSRX: incorrect dbid\n"
+ "Expected dbid 0x0002 and R193\n"
+ "found dbid == %d\n"
+ "Please see the daughterboard app notes"
+ ) % this->get_rx_id().to_pp_string();
+
+ //warn user about incorrect DBID on non-USRP1, requires R194 populated
+ if (not this->get_iface()->get_special_props().soft_clock_divider and this->get_rx_id() == 0x0002)
+ UHD_MSG(warning) << boost::format(
+ "DBSRX: incorrect dbid\n"
+ "Expected dbid 0x000D and R194\n"
+ "found dbid == %d\n"
+ "Please see the daughterboard app notes"
+ ) % this->get_rx_id().to_pp_string();
+
+ //send initial register settings
+ this->send_reg(0x0, 0x5);
+
+ //set defaults for LO, gains, and filter bandwidth
+ double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX);
+ _bandwidth = 0.8*codec_rate/2.0; // default to anti-alias at different codec_rate
+
+ ////////////////////////////////////////////////////////////////////
+ // Register properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name")
+ .set("DBSRX");
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&dbsrx::get_locked, this));
+ BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){
+ this->get_rx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&dbsrx::set_gain, this, _1, name))
+ .set(dbsrx_gain_ranges[name].start());
+ this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(dbsrx_gain_ranges[name]);
+ }
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&dbsrx::set_lo_freq, this, _1));
+ this->get_rx_subtree()->create<meta_range_t>("freq/range")
+ .set(dbsrx_freq_range);
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .set(dbsrx_antennas.at(0));
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(dbsrx_antennas);
+ this->get_rx_subtree()->create<std::string>("connection")
+ .set("IQ");
+ this->get_rx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value")
+ .coerce(boost::bind(&dbsrx::set_bandwidth, this, _1));
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(dbsrx_bandwidth_range);
+
+ //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
+ if (this->get_iface()->get_special_props().soft_clock_divider){
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x1); // GPIO0 is clock when on USRP1
+ }
+ else{
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs
+ }
+
+ //now its safe to set inital freq and bw
+ this->get_rx_subtree()->access<double>("freq/value")
+ .set(dbsrx_freq_range.start());
+ this->get_rx_subtree()->access<double>("bandwidth/value")
+ .set(2.0*_bandwidth); //_bandwidth in lowpass, convert to complex bandpass
+}
+
+dbsrx::~dbsrx(void){
+}
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double dbsrx::set_lo_freq(double target_freq){
+ target_freq = dbsrx_freq_range.clip(target_freq);
+
+ 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);
+ const double max_clock_rate = uhd::sorted(clock_rates).back();
+ BOOST_FOREACH(ref_clock, uhd::reversed(uhd::sorted(clock_rates))){
+ //USRP1 feeds the DBSRX clock from a FPGA GPIO line.
+ //make sure that this clock does not exceed rate limit.
+ if (this->get_iface()->get_special_props().soft_clock_divider){
+ if (ref_clock > usrp1_gpio_clock_rate_limit) continue;
+ }
+ if (ref_clock > 27.0e6) continue;
+ if (size_t(max_clock_rate/ref_clock)%2 == 1) continue; //reject asymmetric clocks (odd divisors)
+
+ //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--; }
+
+ UHD_LOGV(often) << boost::format(
+ "DBSRX: trying ref_clock %f and m_divider %d"
+ ) % (ref_clock) % m << std::endl;
+
+ if (m >= 32) continue;
+
+ //choose R
+ for(r = 0; r <= 6; r += 1) {
+ //compute divider from setting
+ R = 1 << (r+1);
+ UHD_LOGV(often) << 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.start()) or (pfd_freq > dbsrx_pfd_freq_range.stop())) 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;
+ }
+ }
+
+ done_loop:
+
+ //Assert because we failed to find a suitable combination of ref_clock, R and N
+ UHD_ASSERT_THROW(ref_clock <= 27.0e6 and ref_clock >= 0.0);
+ UHD_ASSERT_THROW(ref_clock/m >= 1e6 and ref_clock/m <= 2.5e6);
+ UHD_ASSERT_THROW((pfd_freq >= dbsrx_pfd_freq_range.start()) and (pfd_freq <= dbsrx_pfd_freq_range.stop()));
+ UHD_ASSERT_THROW((N >= 256) and (N <= 32768));
+
+ UHD_LOGV(often) << boost::format(
+ "DBSRX: choose ref_clock (current: %f, new: %f) and m_divider %d"
+ ) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % ref_clock % 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;
+
+ UHD_LOGV(often) << 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);
+
+ UHD_LOGV(often) << 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_MSG(warning) << 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); //just to cause a throw
+ }
+ 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_MSG(warning) << 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 != 7); //just to cause a throw
+ }
+ if (_max2118_write_regs.osc_band >= 7) break;
+ _max2118_write_regs.osc_band += 1;
+ }
+
+ UHD_LOGV(often) << 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);
+
+ //allow for setup time before checking condition again
+ boost::this_thread::sleep(boost::posix_time::milliseconds(10));
+ }
+
+ UHD_LOGV(often) << 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
+ UHD_LOGV(often)
+ << 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)
+ << boost::format(" VCO Freq=%fMHz\n") % (vco_freq/1e6)
+ << std::endl;
+
+ if (update_filter_settings) set_bandwidth(_bandwidth);
+ get_locked();
+
+ return _lo_freq;
+}
+
+/***********************************************************************
+ * 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(double &gain){
+ int reg = 0;
+ gain = dbsrx_gain_ranges["GC2"].clip(gain);
+
+ // Half dB steps from 0-5dB, 1dB steps from 5-24dB
+ if (gain < 5) {
+ reg = boost::math::iround(31.0 - gain/0.5);
+ gain = double(boost::math::iround(gain) * 0.5);
+ } else {
+ reg = boost::math::iround(22.0 - (gain - 4.0));
+ gain = double(boost::math::iround(gain));
+ }
+
+ UHD_LOGV(often) << 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 double gain_to_gc1_rfvga_dac(double &gain){
+ //clip the input
+ gain = dbsrx_gain_ranges["GC1"].clip(gain);
+
+ //voltage level constants
+ static const double max_volts = 1.2, min_volts = 2.7;
+ static const double slope = (max_volts-min_volts)/dbsrx_gain_ranges["GC1"].stop();
+
+ //calculate the voltage for the aux dac
+ double dac_volts = gain*slope + min_volts;
+
+ UHD_LOGV(often) << 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;
+}
+
+double dbsrx::set_gain(double 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;
+
+ return gain;
+}
+
+/***********************************************************************
+ * Bandwidth Handling
+ **********************************************************************/
+double dbsrx::set_bandwidth(double bandwidth){
+ //convert complex bandpass to lowpass bandwidth
+ bandwidth = bandwidth/2.0;
+
+ //clip the input
+ bandwidth = dbsrx_bandwidth_range.clip(bandwidth);
+
+ 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 = uhd::clip<int>(int((((bandwidth*_max2118_write_regs.m_divider)/ref_clock) - 4)/0.145),0,127);
+
+ //determine actual bandwidth
+ _bandwidth = double((ref_clock/(_max2118_write_regs.m_divider))*(4+0.145*_max2118_write_regs.f_dac));
+
+ UHD_LOGV(often) << 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);
+
+ //convert lowpass back to complex bandpass bandwidth
+ return 2.0*_bandwidth;
+}
diff --git a/host/lib/usrp/dboard/db_dbsrx2.cpp b/host/lib/usrp/dboard/db_dbsrx2.cpp
new file mode 100644
index 000000000..013f3178a
--- /dev/null
+++ b/host/lib/usrp/dboard/db_dbsrx2.cpp
@@ -0,0 +1,379 @@
+//
+// Copyright 2010-2012 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
+
+#include "max2112_regs.hpp"
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/types/dict.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 DBSRX2 constants
+ **********************************************************************/
+static const freq_range_t dbsrx2_freq_range(0.8e9, 2.4e9);
+
+//Multiplied by 2.0 for conversion to complex bandpass from lowpass
+static const freq_range_t dbsrx2_bandwidth_range(2.0*4.0e6, 2.0*40.0e6);
+
+static const int dbsrx2_ref_divider = 4; // Hitachi HMC426 divider (U7)
+
+static const std::vector<std::string> dbsrx2_antennas = list_of("J3");
+
+static const uhd::dict<std::string, gain_range_t> dbsrx2_gain_ranges = map_list_of
+ ("GC1", gain_range_t(0, 73, 0.05))
+ ("BBG", gain_range_t(0, 15, 1))
+;
+
+/***********************************************************************
+ * The DBSRX2 dboard class
+ **********************************************************************/
+class dbsrx2 : public rx_dboard_base{
+public:
+ dbsrx2(ctor_args_t args);
+ ~dbsrx2(void);
+
+private:
+ double _lo_freq;
+ double _bandwidth;
+ uhd::dict<std::string, double> _gains;
+ max2112_write_regs_t _max2112_write_regs;
+ max2112_read_regs_t _max2112_read_regs;
+ boost::uint8_t _max2112_addr(){ //0x60 or 0x61 depending on which side
+ return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x60 : 0x61;
+ }
+
+ double set_lo_freq(double target_freq);
+ double set_gain(double gain, const std::string &name);
+ double set_bandwidth(double bandwidth);
+
+ void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){
+ start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0xB));
+ stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0xB));
+
+ 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] = _max2112_write_regs.get_reg(start_addr+i);
+ UHD_LOGV(often) << boost::format(
+ "DBSRX2: 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(
+ _max2112_addr(), regs_vector
+ );
+ }
+ }
+
+ void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){
+ static const boost::uint8_t status_addr = 0xC;
+ start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0xD));
+ stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0xD));
+
+ 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 address to start reading register data
+ byte_vector_t address_vector(1);
+ address_vector[0] = start_addr;
+
+ //send the address
+ this->get_iface()->write_i2c(
+ _max2112_addr(), address_vector
+ );
+
+ //create buffer for register data
+ byte_vector_t regs_vector(num_bytes);
+
+ //read from i2c
+ regs_vector = this->get_iface()->read_i2c(
+ _max2112_addr(), num_bytes
+ );
+
+ for(boost::uint8_t i=0; i < num_bytes; i++){
+ if (i + start_addr >= status_addr){
+ _max2112_read_regs.set_reg(i + start_addr, regs_vector[i]);
+ /*
+ UHD_LOGV(always) << boost::format(
+ "DBSRX2: set reg 0x%02x, value 0x%04x"
+ ) % int(i + start_addr) % int(_max2112_read_regs.get_reg(i + start_addr)) << std::endl;
+ */
+ }
+ UHD_LOGV(often) << boost::format(
+ "DBSRX2: 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;
+ }
+ }
+ }
+
+ /*!
+ * Get the lock detect status of the LO.
+ * \return sensor for locked
+ */
+ sensor_value_t get_locked(void){
+ read_reg(0xC, 0xD);
+
+ //mask and return lock detect
+ bool locked = (_max2112_read_regs.ld & _max2112_read_regs.vasa & _max2112_read_regs.vase) != 0;
+
+ UHD_LOGV(often) << boost::format(
+ "DBSRX2 locked: %d"
+ ) % locked << std::endl;
+
+ return sensor_value_t("LO", locked, "locked", "unlocked");
+ }
+};
+
+/***********************************************************************
+ * Register the DBSRX2 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_dbsrx2(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new dbsrx2(args));
+}
+
+UHD_STATIC_BLOCK(reg_dbsrx2_dboard){
+ //register the factory function for the rx dbid
+ dboard_manager::register_dboard(0x0012, &make_dbsrx2, "DBSRX2");
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){
+ //send initial register settings
+ send_reg(0x0, 0xB);
+ //for (boost::uint8_t addr=0; addr<=12; addr++) this->send_reg(addr, addr);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name")
+ .set("DBSRX2");
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&dbsrx2::get_locked, this));
+ BOOST_FOREACH(const std::string &name, dbsrx2_gain_ranges.keys()){
+ this->get_rx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&dbsrx2::set_gain, this, _1, name))
+ .set(dbsrx2_gain_ranges[name].start());
+ this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(dbsrx2_gain_ranges[name]);
+ }
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&dbsrx2::set_lo_freq, this, _1))
+ .set(dbsrx2_freq_range.start());
+ this->get_rx_subtree()->create<meta_range_t>("freq/range")
+ .set(dbsrx2_freq_range);
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .set(dbsrx2_antennas.at(0));
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(dbsrx2_antennas);
+ this->get_rx_subtree()->create<std::string>("connection")
+ .set("QI");
+ this->get_rx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+
+ double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX);
+
+ this->get_rx_subtree()->create<double>("bandwidth/value")
+ .coerce(boost::bind(&dbsrx2::set_bandwidth, this, _1))
+ .set(2.0*(0.8*codec_rate/2.0)); //bandwidth in lowpass, convert to complex bandpass
+ //default to anti-alias at different codec_rate
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(dbsrx2_bandwidth_range);
+
+ //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
+
+ get_locked();
+}
+
+dbsrx2::~dbsrx2(void){
+}
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double dbsrx2::set_lo_freq(double target_freq){
+ //target_freq = dbsrx2_freq_range.clip(target_freq);
+
+ //variables used in the calculation below
+ int scaler = target_freq > 1125e6 ? 2 : 4;
+ double ref_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX);
+ int R, intdiv, fracdiv, ext_div;
+ double N;
+
+ //compute tuning variables
+ ext_div = dbsrx2_ref_divider; // 12MHz < ref_freq/ext_divider < 30MHz
+
+ R = 1; //Divide by 1 is the only tested value
+
+ N = (target_freq*R*ext_div)/(ref_freq); //actual spec range is (19, 251)
+ intdiv = int(std::floor(N)); // if (intdiv < 19 or intdiv > 251) continue;
+ fracdiv = boost::math::iround((N - intdiv)*double(1 << 20));
+
+ //calculate the actual freq from the values above
+ N = double(intdiv) + double(fracdiv)/double(1 << 20);
+ _lo_freq = (N*ref_freq)/(R*ext_div);
+
+ //load new counters into registers
+ _max2112_write_regs.set_n_divider(intdiv);
+ _max2112_write_regs.set_f_divider(fracdiv);
+ _max2112_write_regs.r_divider = R;
+ _max2112_write_regs.d24 = scaler == 4 ? max2112_write_regs_t::D24_DIV4 : max2112_write_regs_t::D24_DIV2;
+
+ //debug output of calculated variables
+ UHD_LOGV(often)
+ << boost::format("DBSRX2 tune:\n")
+ << boost::format(" R=%d, N=%f, scaler=%d, ext_div=%d\n") % R % N % scaler % ext_div
+ << boost::format(" int=%d, frac=%d, d24=%d\n") % intdiv % fracdiv % int(_max2112_write_regs.d24)
+ << 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;
+
+ //send the registers
+ send_reg(0x0, 0x7);
+
+ //FIXME: probably unnecessary to call get_locked here
+ //get_locked();
+
+ return _lo_freq;
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+/*!
+ * Convert a requested gain for the BBG 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 4 bit the register value
+ */
+static int gain_to_bbg_vga_reg(double &gain){
+ int reg = boost::math::iround(dbsrx2_gain_ranges["BBG"].clip(gain));
+
+ gain = double(reg);
+
+ UHD_LOGV(often)
+ << boost::format("DBSRX2 BBG Gain:\n")
+ << boost::format(" %f dB, bbg: %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 double gain_to_gc1_rfvga_dac(double &gain){
+ //clip the input
+ gain = dbsrx2_gain_ranges["GC1"].clip(gain);
+
+ //voltage level constants
+ static const double max_volts = 0.5, min_volts = 2.7;
+ static const double slope = (max_volts-min_volts)/dbsrx2_gain_ranges["GC1"].stop();
+
+ //calculate the voltage for the aux dac
+ double dac_volts = gain*slope + min_volts;
+
+ UHD_LOGV(often)
+ << boost::format("DBSRX2 GC1 Gain:\n")
+ << boost::format(" %f dB, dac_volts: %f V") % gain % dac_volts
+ << std::endl;
+
+ //the actual gain setting
+ gain = (dac_volts - min_volts)/slope;
+
+ return dac_volts;
+}
+
+double dbsrx2::set_gain(double gain, const std::string &name){
+ assert_has(dbsrx2_gain_ranges.keys(), name, "dbsrx2 gain name");
+ if (name == "BBG"){
+ _max2112_write_regs.bbg = gain_to_bbg_vga_reg(gain);
+ send_reg(0x9, 0x9);
+ }
+ 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;
+
+ return gain;
+}
+
+/***********************************************************************
+ * Bandwidth Handling
+ **********************************************************************/
+double dbsrx2::set_bandwidth(double bandwidth){
+ //convert complex bandpass to lowpass bandwidth
+ bandwidth = bandwidth/2.0;
+
+ //clip the input
+ bandwidth = dbsrx2_bandwidth_range.clip(bandwidth);
+
+ _max2112_write_regs.lp = int((bandwidth/1e6 - 4)/0.29 + 12);
+ _bandwidth = double(4 + (_max2112_write_regs.lp - 12) * 0.29)*1e6;
+
+ UHD_LOGV(often)
+ << boost::format("DBSRX2 Bandwidth:\n")
+ << boost::format(" %f MHz, lp: %f V") % (_bandwidth/1e6) % int(_max2112_write_regs.lp)
+ << std::endl;
+
+ this->send_reg(0x8, 0x8);
+
+ //convert lowpass back to complex bandpass bandwidth
+ return 2.0*_bandwidth;
+}
diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp
new file mode 100644
index 000000000..cf3b29ddc
--- /dev/null
+++ b/host/lib/usrp/dboard/db_rfx.cpp
@@ -0,0 +1,449 @@
+//
+// Copyright 2010-2012 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
+
+// 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/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/msg.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/bind.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 std::vector<std::string> rfx_tx_antennas = list_of("TX/RX")("CAL");
+
+static const std::vector<std::string> rfx_rx_antennas = list_of("TX/RX")("RX2")("CAL");
+
+static const uhd::dict<std::string, gain_range_t> rfx_rx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 70, 0.022))
+;
+
+static const uhd::dict<std::string, gain_range_t> rfx400_rx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 45, 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);
+
+private:
+ const freq_range_t _freq_range;
+ const uhd::dict<std::string, gain_range_t> _rx_gain_ranges;
+ const uhd::dict<dboard_iface::unit_t, bool> _div2;
+ std::string _rx_ant;
+ uhd::dict<std::string, double> _rx_gains;
+ boost::uint16_t _power_up;
+
+ void set_rx_ant(const std::string &ant);
+ void set_tx_ant(const std::string &ant);
+ double set_rx_gain(double 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 sensor for locked
+ */
+ sensor_value_t get_locked(dboard_iface::unit_t unit){
+ const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0;
+ return sensor_value_t("LO", locked, "locked", "unlocked");
+ }
+
+ /*!
+ * Removed incorrect/confusing RSSI calculation
+ * Limited dynamic range of sensor makes this less useful
+ */
+};
+
+/***********************************************************************
+ * 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), true, 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, "RFX400");
+ dboard_manager::register_dboard(0x0025, 0x0029, &make_rfx_flex900, "RFX900");
+ dboard_manager::register_dboard(0x0034, 0x0035, &make_rfx_flex1800, "RFX1800");
+ dboard_manager::register_dboard(0x0026, 0x002a, &make_rfx_flex1200, "RFX1200");
+ dboard_manager::register_dboard(0x002c, 0x002d, &make_rfx_flex2200, "RFX2200");
+ dboard_manager::register_dboard(0x0027, 0x002b, &make_rfx_flex2400, "RFX2400");
+}
+
+/***********************************************************************
+ * 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),
+ _rx_gain_ranges((get_rx_id() == 0x0024)?
+ rfx400_rx_gain_ranges : rfx_rx_gain_ranges
+ ),
+ _div2(map_list_of
+ (dboard_iface::UNIT_RX, rx_div2)
+ (dboard_iface::UNIT_TX, tx_div2)
+ ),
+ _power_up((get_rx_id() == 0x0024 && get_tx_id() == 0x0028) ? POWER_IO : 0)
+{
+ ////////////////////////////////////////////////////////////////////
+ // Register RX properties
+ ////////////////////////////////////////////////////////////////////
+ if(get_rx_id() == 0x0024) this->get_rx_subtree()->create<std::string>("name").set("RFX400 RX");
+ else if(get_rx_id() == 0x0025) this->get_rx_subtree()->create<std::string>("name").set("RFX900 RX");
+ else if(get_rx_id() == 0x0034) this->get_rx_subtree()->create<std::string>("name").set("RFX1800 RX");
+ else if(get_rx_id() == 0x0026) this->get_rx_subtree()->create<std::string>("name").set("RFX1200 RX");
+ else if(get_rx_id() == 0x002c) this->get_rx_subtree()->create<std::string>("name").set("RFX2200 RX");
+ else if(get_rx_id() == 0x0027) this->get_rx_subtree()->create<std::string>("name").set("RFX2400 RX");
+ else this->get_rx_subtree()->create<std::string>("name").set("RFX RX");
+
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX));
+ BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){
+ this->get_rx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name))
+ .set(_rx_gain_ranges[name].start());
+ this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(_rx_gain_ranges[name]);
+ }
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set((_freq_range.start() + _freq_range.stop())/2.0);
+ this->get_rx_subtree()->create<meta_range_t>("freq/range").set(_freq_range);
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .subscribe(boost::bind(&rfx_xcvr::set_rx_ant, this, _1))
+ .set("RX2");
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(rfx_rx_antennas);
+ this->get_rx_subtree()->create<std::string>("connection").set("QI");
+ this->get_rx_subtree()->create<bool>("enabled").set(true); //always enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset").set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(2*20.0e6, 2*20.0e6));
+
+ ////////////////////////////////////////////////////////////////////
+ // Register TX properties
+ ////////////////////////////////////////////////////////////////////
+ if(get_tx_id() == 0x0028) this->get_tx_subtree()->create<std::string>("name").set("RFX400 TX");
+ else if(get_tx_id() == 0x0029) this->get_tx_subtree()->create<std::string>("name").set("RFX900 TX");
+ else if(get_tx_id() == 0x0035) this->get_tx_subtree()->create<std::string>("name").set("RFX1800 TX");
+ else if(get_tx_id() == 0x002a) this->get_tx_subtree()->create<std::string>("name").set("RFX1200 TX");
+ else if(get_tx_id() == 0x002d) this->get_tx_subtree()->create<std::string>("name").set("RFX2200 TX");
+ else if(get_tx_id() == 0x002b) this->get_tx_subtree()->create<std::string>("name").set("RFX2400 TX");
+ else this->get_tx_subtree()->create<std::string>("name").set("RFX TX");
+
+ this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX));
+ this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists
+ this->get_tx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set((_freq_range.start() + _freq_range.stop())/2.0);
+ this->get_tx_subtree()->create<meta_range_t>("freq/range").set(_freq_range);
+ this->get_tx_subtree()->create<std::string>("antenna/value")
+ .subscribe(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0));
+ this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(rfx_tx_antennas);
+ this->get_tx_subtree()->create<std::string>("connection").set("IQ");
+ this->get_tx_subtree()->create<bool>("enabled").set(true); //always enabled
+ this->get_tx_subtree()->create<bool>("use_lo_offset").set(true);
+ this->get_tx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided
+ this->get_tx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(2*20.0e6, 2*20.0e6));
+
+ //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);
+}
+
+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
+ if (ant == "CAL") {
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX );
+ }
+ else {
+ 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);
+ 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");
+
+ //set the tx atr regs that change with antenna setting
+ if (ant == "CAL") {
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB);
+ }
+ else {
+ 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);
+ }
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+static double rx_pga0_gain_to_dac_volts(double &gain, double range){
+ //voltage level constants (negative slope)
+ static const double max_volts = .2, min_volts = 1.2;
+ static const double slope = (max_volts-min_volts)/(range);
+
+ //calculate the voltage for the aux dac
+ double dac_volts = uhd::clip<double>(gain*slope + min_volts, max_volts, min_volts);
+
+ //the actual gain setting
+ gain = (dac_volts - min_volts)/slope;
+
+ return dac_volts;
+}
+
+double rfx_xcvr::set_rx_gain(double gain, const std::string &name){
+ assert_has(_rx_gain_ranges.keys(), name, "rfx rx gain name");
+ if(name == "PGA0"){
+ double dac_volts = rx_pga0_gain_to_dac_volts(gain,
+ (_rx_gain_ranges["PGA0"].stop() - _rx_gain_ranges["PGA0"].start()));
+
+ //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);
+
+ return gain;
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+}
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double rfx_xcvr::set_lo_freq(
+ dboard_iface::unit_t unit,
+ double target_freq
+){
+ UHD_LOGV(often) << boost::format(
+ "RFX tune: target frequency %f Mhz"
+ ) % (target_freq/1e6) << std::endl;
+
+ //clip the input
+ target_freq = _freq_range.clip(target_freq);
+ if (_div2[unit]) target_freq *= 2;
+
+ //rfx400 rx is a special case with div2 in mixer, so adf4360 must output fundamental
+ bool is_rx_rfx400 = ((get_rx_id() == 0x0024) && unit != dboard_iface::UNIT_TX);
+
+ //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 exits 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:
+
+ UHD_LOGV(often) << boost::format(
+ "RFX tune: R=%d, BS=%d, P=%d, B=%d, A=%d, DIV2=%d"
+ ) % R % BS % P % B % A % int(_div2[unit] && (!is_rx_rfx400)) << 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] && (!is_rx_rfx400)) ? // Special case RFX400 RX Mixer divides by two
+ 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;
+ UHD_LOGV(often) << boost::format(
+ "RFX tune: actual frequency %f Mhz"
+ ) % (actual_freq/1e6) << std::endl;
+ return actual_freq;
+}
diff --git a/host/lib/usrp/dboard/db_sbx_common.cpp b/host/lib/usrp/dboard/db_sbx_common.cpp
new file mode 100644
index 000000000..728cb17e9
--- /dev/null
+++ b/host/lib/usrp/dboard/db_sbx_common.cpp
@@ -0,0 +1,356 @@
+//
+// Copyright 2011-2012 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 "db_sbx_common.hpp"
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+
+/***********************************************************************
+ * Register the SBX dboard (min freq, max freq, rx div2, tx div2)
+ **********************************************************************/
+static dboard_base::sptr make_sbx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new sbx_xcvr(args));
+}
+
+UHD_STATIC_BLOCK(reg_sbx_dboards){
+ dboard_manager::register_dboard(0x0054, 0x0055, &make_sbx, "SBX");
+ dboard_manager::register_dboard(0x0065, 0x0064, &make_sbx, "SBX v4");
+}
+
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+static int rx_pga0_gain_to_iobits(double &gain){
+ //clip the input
+ gain = sbx_rx_gain_ranges["PGA0"].clip(gain);
+
+ //convert to attenuation and update iobits for atr
+ double attn = sbx_rx_gain_ranges["PGA0"].stop() - gain;
+
+ //calculate the RX attenuation
+ int attn_code = int(floor(attn*2));
+ int iobits = ((~attn_code) << RX_ATTN_SHIFT) & RX_ATTN_MASK;
+
+ UHD_LOGV(often) << boost::format(
+ "SBX TX 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 = sbx_rx_gain_ranges["PGA0"].stop() - double(attn_code)/2;
+
+ return iobits;
+}
+
+static int tx_pga0_gain_to_iobits(double &gain){
+ //clip the input
+ gain = sbx_tx_gain_ranges["PGA0"].clip(gain);
+
+ //convert to attenuation and update iobits for atr
+ double attn = sbx_tx_gain_ranges["PGA0"].stop() - gain;
+
+ //calculate the TX attenuation
+ int attn_code = int(floor(attn*2));
+ int iobits = ((~attn_code) << TX_ATTN_SHIFT) & TX_ATTN_MASK;
+
+ UHD_LOGV(often) << boost::format(
+ "SBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x"
+ ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl;
+
+ //the actual gain setting
+ gain = sbx_tx_gain_ranges["PGA0"].stop() - double(attn_code)/2;
+
+ return iobits;
+}
+
+double sbx_xcvr::set_tx_gain(double gain, const std::string &name){
+ assert_has(sbx_tx_gain_ranges.keys(), name, "sbx tx gain name");
+ if(name == "PGA0"){
+ tx_pga0_gain_to_iobits(gain);
+ _tx_gains[name] = gain;
+
+ //write the new gain to atr regs
+ update_atr();
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ return _tx_gains[name];
+}
+
+double sbx_xcvr::set_rx_gain(double gain, const std::string &name){
+ assert_has(sbx_rx_gain_ranges.keys(), name, "sbx 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();
+ return _rx_gains[name];
+}
+
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){
+ switch(get_rx_id().to_uint16()) {
+ case 0x054:
+ db_actual = sbx_versionx_sptr(new sbx_version3(this));
+ break;
+ case 0x065:
+ db_actual = sbx_versionx_sptr(new sbx_version4(this));
+ break;
+ default:
+ /* We didn't recognize the version of the board... */
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ // Register RX properties
+ ////////////////////////////////////////////////////////////////////
+ if(get_rx_id() == 0x054) this->get_rx_subtree()->create<std::string>("name").set("SBXv3 RX");
+ else if(get_rx_id() == 0x065) this->get_rx_subtree()->create<std::string>("name").set("SBXv4 RX");
+ else this->get_rx_subtree()->create<std::string>("name").set("SBX RX");
+
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX));
+ BOOST_FOREACH(const std::string &name, sbx_rx_gain_ranges.keys()){
+ this->get_rx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name))
+ .set(sbx_rx_gain_ranges[name].start());
+ this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(sbx_rx_gain_ranges[name]);
+ }
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set((sbx_freq_range.start() + sbx_freq_range.stop())/2.0);
+ this->get_rx_subtree()->create<meta_range_t>("freq/range").set(sbx_freq_range);
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .subscribe(boost::bind(&sbx_xcvr::set_rx_ant, this, _1))
+ .set("RX2");
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(sbx_rx_antennas);
+ this->get_rx_subtree()->create<std::string>("connection").set("IQ");
+ this->get_rx_subtree()->create<bool>("enabled").set(true); //always enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset").set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(2*20.0e6, 2*20.0e6));
+
+ ////////////////////////////////////////////////////////////////////
+ // Register TX properties
+ ////////////////////////////////////////////////////////////////////
+ if(get_tx_id() == 0x055) this->get_tx_subtree()->create<std::string>("name").set("SBXv3 TX");
+ else if(get_tx_id() == 0x067) this->get_tx_subtree()->create<std::string>("name").set("SBXv4 TX");
+ else this->get_tx_subtree()->create<std::string>("name").set("SBX TX");
+
+ this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX));
+ BOOST_FOREACH(const std::string &name, sbx_tx_gain_ranges.keys()){
+ this->get_tx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name))
+ .set(sbx_tx_gain_ranges[name].start());
+ this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(sbx_tx_gain_ranges[name]);
+ }
+ this->get_tx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set((sbx_freq_range.start() + sbx_freq_range.stop())/2.0);
+ this->get_tx_subtree()->create<meta_range_t>("freq/range").set(sbx_freq_range);
+ this->get_tx_subtree()->create<std::string>("antenna/value")
+ .subscribe(boost::bind(&sbx_xcvr::set_tx_ant, this, _1))
+ .set(sbx_tx_antennas.at(0));
+ this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(sbx_tx_antennas);
+ this->get_tx_subtree()->create<std::string>("connection").set("QI");
+ this->get_tx_subtree()->create<bool>("enabled").set(true); //always enabled
+ this->get_tx_subtree()->create<bool>("use_lo_offset").set(false);
+ this->get_tx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided
+ this->get_tx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(2*20.0e6, 2*20.0e6));
+
+ //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|TX_LED_IO));
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO));
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
+
+ //flash LEDs
+ flash_leds();
+
+ UHD_LOGV(often) << boost::format(
+ "SBX GPIO Direction: RX: 0x%08x, TX: 0x%08x"
+ ) % RXIO_MASK % TXIO_MASK << std::endl;
+}
+
+sbx_xcvr::~sbx_xcvr(void){
+ /* NOP */
+}
+
+/***********************************************************************
+ * Antenna Handling
+ **********************************************************************/
+void sbx_xcvr::update_atr(void){
+ //calculate atr pins
+ int rx_pga0_iobits = rx_pga0_gain_to_iobits(_rx_gains["PGA0"]);
+ int tx_pga0_iobits = tx_pga0_gain_to_iobits(_tx_gains["PGA0"]);
+ int rx_lo_lpf_en = (_rx_lo_freq == sbx_enable_rx_lo_filter.clip(_rx_lo_freq)) ? LO_LPF_EN : 0;
+ int tx_lo_lpf_en = (_tx_lo_freq == sbx_enable_tx_lo_filter.clip(_tx_lo_freq)) ? LO_LPF_EN : 0;
+ int rx_ld_led = _rx_lo_lock_cache ? 0 : RX_LED_LD;
+ int tx_ld_led = _tx_lo_lock_cache ? 0 : TX_LED_LD;
+ int rx_ant_led = _rx_ant == "TX/RX" ? RX_LED_RX1RX2 : 0;
+ int tx_ant_led = _tx_ant == "TX/RX" ? 0 : TX_LED_TXRX;
+
+ //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, 0 | tx_lo_lpf_en \
+ | tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_XX | TX_MIXER_DIS);
+
+ //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, rx_pga0_iobits | rx_lo_lpf_en \
+ | rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_XX | RX_MIXER_DIS);
+
+ //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, rx_pga0_iobits | rx_lo_lpf_en \
+ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \
+ | ((_rx_ant != "RX2")? ANT_TXRX : ANT_RX2));
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
+ dboard_iface::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \
+ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_DIS \
+ | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2));
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
+ dboard_iface::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \
+ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \
+ | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2));
+
+ //set the TX atr regs that change with antenna setting
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
+ dboard_iface::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \
+ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_DIS \
+ | ((_rx_ant != "RX2")? ANT_RX : ANT_TX));
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
+ dboard_iface::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \
+ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \
+ | ((_tx_ant == "CAL")? ANT_RX : ANT_TX));
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
+ dboard_iface::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \
+ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \
+ | ((_tx_ant == "CAL")? ANT_RX : ANT_TX));
+}
+
+void sbx_xcvr::set_rx_ant(const std::string &ant){
+ //validate input
+ assert_has(sbx_rx_antennas, ant, "sbx rx antenna name");
+
+ //shadow the setting
+ _rx_ant = ant;
+
+ //write the new antenna setting to atr regs
+ update_atr();
+}
+
+void sbx_xcvr::set_tx_ant(const std::string &ant){
+ assert_has(sbx_tx_antennas, ant, "sbx tx antenna name");
+
+ //shadow the setting
+ _tx_ant = ant;
+
+ //write the new antenna setting to atr regs
+ update_atr();
+}
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double sbx_xcvr::set_lo_freq(dboard_iface::unit_t unit, double target_freq) {
+ const double actual = db_actual->set_lo_freq(unit, target_freq);
+ if (unit == dboard_iface::UNIT_RX){
+ _rx_lo_lock_cache = false;
+ _rx_lo_freq = actual;
+ }
+ if (unit == dboard_iface::UNIT_TX){
+ _tx_lo_lock_cache = false;
+ _tx_lo_freq = actual;
+ }
+ update_atr();
+ return actual;
+}
+
+
+sensor_value_t sbx_xcvr::get_locked(dboard_iface::unit_t unit) {
+ const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0;
+
+ if (unit == dboard_iface::UNIT_RX) _rx_lo_lock_cache = locked;
+ if (unit == dboard_iface::UNIT_TX) _tx_lo_lock_cache = locked;
+
+ //write the new lock cache setting to atr regs
+ update_atr();
+
+ return sensor_value_t("LO", locked, "locked", "unlocked");
+}
+
+
+void sbx_xcvr::flash_leds(void) {
+ //Remove LED gpios from ATR control temporarily and set to outputs
+ 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|RX_LED_IO));
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
+
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, \
+ TX_LED_TXRX|TX_LED_LD, TX_LED_IO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, \
+ RX_LED_RX1RX2|RX_LED_LD, RX_LED_IO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(100));
+
+ //Put LED gpios back in ATR control and update atr
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO));
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO));
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
+}
+
diff --git a/host/lib/usrp/dboard/db_sbx_common.hpp b/host/lib/usrp/dboard/db_sbx_common.hpp
new file mode 100644
index 000000000..2a0e83115
--- /dev/null
+++ b/host/lib/usrp/dboard/db_sbx_common.hpp
@@ -0,0 +1,226 @@
+//
+// Copyright 2011-2012 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 LO_LPF_EN (1 << 15)
+#define SYNTH_CE (1 << 3)
+#define SYNTH_PDBRF (1 << 2)
+#define SYNTH_MUXOUT (1 << 1) // INPUT!!!
+#define LOCKDET_MASK (1 << 0) // INPUT!!!
+
+// TX IO Pins
+#define TRSW (1 << 14) // 0 = TX, 1 = RX
+#define TX_LED_TXRX (1 << 7) // LED for TX Antenna Selection TX/RX
+#define TX_LED_LD (1 << 6) // LED for TX Lock Detect
+#define DIS_POWER_TX (1 << 5) // on UNIT_TX, 0 powers up TX
+#define TX_ENABLE (1 << 4) // on UNIT_TX, 0 disables TX Mixer
+
+// RX IO Pins
+#define LNASW (1 << 14) // 0 = TX/RX, 1 = RX2
+#define RX_LED_RX1RX2 (1 << 7) // LED for RX Antenna Selection RX1/RX2
+#define RX_LED_LD (1 << 6) // LED for RX Lock Detect
+#define DIS_POWER_RX (1 << 5) // on UNIT_RX, 0 powers up RX
+#define RX_DISABLE (1 << 4) // on UNIT_RX, 1 disables RX Mixer and 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
+
+// TX Attenuator Pins
+#define TX_ATTN_SHIFT 8 // lsb of TX Attenuator Control
+#define TX_ATTN_MASK (63 << TX_ATTN_SHIFT) // valid bits of TX Attenuator Control
+
+// Mixer functions
+#define TX_MIXER_ENB (SYNTH_PDBRF|TX_ENABLE)
+#define TX_MIXER_DIS 0
+
+#define RX_MIXER_ENB (SYNTH_PDBRF)
+#define RX_MIXER_DIS 0
+
+// Pin functions
+#define TX_LED_IO (TX_LED_TXRX|TX_LED_LD) // LED gpio lines, pull down for LED
+#define TXIO_MASK (LO_LPF_EN|TRSW|SYNTH_CE|SYNTH_PDBRF|TX_ATTN_MASK|DIS_POWER_TX|TX_ENABLE)
+
+#define RX_LED_IO (RX_LED_RX1RX2|RX_LED_LD) // LED gpio lines, pull down for LED
+#define RXIO_MASK (LO_LPF_EN|LNASW|SYNTH_CE|SYNTH_PDBRF|RX_ATTN_MASK|DIS_POWER_RX|RX_DISABLE)
+
+// Power functions
+#define TX_POWER_UP (SYNTH_CE)
+#define TX_POWER_DOWN (DIS_POWER_TX)
+
+#define RX_POWER_UP (SYNTH_CE)
+#define RX_POWER_DOWN (DIS_POWER_RX)
+
+// Antenna constants
+#define ANT_TX TRSW //the tx line is transmitting
+#define ANT_RX 0 //the tx line is receiving
+#define ANT_TXRX 0 //the rx line is on txrx
+#define ANT_RX2 LNASW //the rx line in on rx2
+#define ANT_XX LNASW //dont care how the antenna is set
+
+
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/msg.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>
+#include <boost/thread.hpp>
+
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+
+/***********************************************************************
+ * The SBX dboard constants
+ **********************************************************************/
+static const freq_range_t sbx_freq_range(400e6, 4.4e9);
+
+static const freq_range_t sbx_tx_lo_2dbm = list_of
+ (range_t(0.35e9, 0.37e9))
+;
+
+static const freq_range_t sbx_enable_tx_lo_filter = list_of
+ (range_t(0.4e9, 1.5e9))
+;
+
+static const freq_range_t sbx_enable_rx_lo_filter = list_of
+ (range_t(0.4e9, 1.5e9))
+;
+
+static const std::vector<std::string> sbx_tx_antennas = list_of("TX/RX")("CAL");
+
+static const std::vector<std::string> sbx_rx_antennas = list_of("TX/RX")("RX2")("CAL");
+
+static const uhd::dict<std::string, gain_range_t> sbx_tx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 31.5, double(0.5)))
+;
+
+static const uhd::dict<std::string, gain_range_t> sbx_rx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 31.5, double(0.5)))
+;
+
+/***********************************************************************
+ * The SBX dboard
+ **********************************************************************/
+class sbx_xcvr : public xcvr_dboard_base{
+public:
+ sbx_xcvr(ctor_args_t args);
+ ~sbx_xcvr(void);
+
+protected:
+
+ uhd::dict<std::string, double> _tx_gains, _rx_gains;
+ double _rx_lo_freq, _tx_lo_freq;
+ std::string _tx_ant, _rx_ant;
+ bool _rx_lo_lock_cache, _tx_lo_lock_cache;
+
+ void set_rx_ant(const std::string &ant);
+ void set_tx_ant(const std::string &ant);
+ double set_rx_gain(double gain, const std::string &name);
+ double set_tx_gain(double 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
+ */
+ virtual 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
+ */
+ sensor_value_t get_locked(dboard_iface::unit_t unit);
+
+ /*!
+ * Flash the LEDs
+ */
+ void flash_leds(void);
+
+ /*!
+ * Version-agnostic ABC that wraps version-specific implementations of the
+ * WBX base daughterboard.
+ *
+ * This class is an abstract base class, and thus is impossible to
+ * instantiate.
+ */
+ class sbx_versionx {
+ public:
+ sbx_versionx() {}
+ ~sbx_versionx(void) {}
+
+ virtual double set_lo_freq(dboard_iface::unit_t unit, double target_freq) = 0;
+ };
+
+ /*!
+ * Version 3 of the SBX Daughterboard
+ */
+ class sbx_version3 : public sbx_versionx {
+ public:
+ sbx_version3(sbx_xcvr *_self_sbx_xcvr);
+ ~sbx_version3(void);
+
+ double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
+
+ /*! This is the registered instance of the wrapper class, sbx_base. */
+ sbx_xcvr *self_base;
+ };
+
+ /*!
+ * Version 4 of the SBX Daughterboard
+ *
+ * The only difference in the fourth revision is the ADF4351 vs the ADF4350.
+ */
+ class sbx_version4 : public sbx_versionx {
+ public:
+ sbx_version4(sbx_xcvr *_self_sbx_xcvr);
+ ~sbx_version4(void);
+
+ double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
+
+ /*! This is the registered instance of the wrapper class, sbx_base. */
+ sbx_xcvr *self_base;
+ };
+
+ /*!
+ * Handle to the version-specific implementation of the SBX.
+ *
+ * Since many of this class's functions are dependent on the version of the
+ * SBX board, this class will instantiate an object of the appropriate
+ * sbx_version* subclass, and invoke any relevant functions through that
+ * object. This pointer is set to the proper object at construction time.
+ */
+ typedef boost::shared_ptr<sbx_versionx> sbx_versionx_sptr;
+ sbx_versionx_sptr db_actual;
+};
+
diff --git a/host/lib/usrp/dboard/db_sbx_version3.cpp b/host/lib/usrp/dboard/db_sbx_version3.cpp
new file mode 100644
index 000000000..2765d530c
--- /dev/null
+++ b/host/lib/usrp/dboard/db_sbx_version3.cpp
@@ -0,0 +1,193 @@
+//
+// Copyright 2011-2012 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 "adf4350_regs.hpp"
+#include "db_sbx_common.hpp"
+
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+sbx_xcvr::sbx_version3::sbx_version3(sbx_xcvr *_self_sbx_xcvr) {
+ //register the handle to our base SBX class
+ self_base = _self_sbx_xcvr;
+}
+
+sbx_xcvr::sbx_version3::~sbx_version3(void){
+ /* NOP */
+}
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double target_freq) {
+ UHD_LOGV(often) << boost::format(
+ "SBX tune: target frequency %f Mhz"
+ ) % (target_freq/1e6) << std::endl;
+
+ //clip the input
+ target_freq = sbx_freq_range.clip(target_freq);
+
+ //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 = self_base->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
+ double vco_freq = target_freq;
+ 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 = target_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 exits 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(target_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((target_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))));
+
+ UHD_LOGV(often)
+ << boost::format("SBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl
+ << boost::format("SBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d"
+ ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl
+ << boost::format("SBX 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;
+
+ if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq)))
+ regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM;
+ else
+ regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM;
+
+ regs.frac_12_bit = FRAC;
+ regs.int_16_bit = N;
+ regs.mod_12_bit = MOD;
+ regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD)));
+ regs.feedback_select = adf4350_regs_t::FEEDBACK_SELECT_DIVIDED;
+ regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE;
+ 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];
+
+ //reset the N and R counter
+ regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED;
+ self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
+ regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED;
+
+ //write the registers
+ //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
+ int addr;
+
+ for(addr=5; addr>=0; addr--){
+ UHD_LOGV(often) << boost::format(
+ "SBX SPI Reg (0x%02x): 0x%08x"
+ ) % addr % regs.get_reg(addr) << std::endl;
+ self_base->get_iface()->write_spi(
+ unit, spi_config_t::EDGE_RISE,
+ regs.get_reg(addr), 32
+ );
+ }
+
+ //return the actual frequency
+ UHD_LOGV(often) << boost::format(
+ "SBX tune: actual frequency %f Mhz"
+ ) % (actual_freq/1e6) << std::endl;
+ return actual_freq;
+}
+
diff --git a/host/lib/usrp/dboard/db_sbx_version4.cpp b/host/lib/usrp/dboard/db_sbx_version4.cpp
new file mode 100644
index 000000000..27fd68b05
--- /dev/null
+++ b/host/lib/usrp/dboard/db_sbx_version4.cpp
@@ -0,0 +1,196 @@
+//
+// Copyright 2011-2012 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 "adf4351_regs.hpp"
+#include "db_sbx_common.hpp"
+
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+sbx_xcvr::sbx_version4::sbx_version4(sbx_xcvr *_self_sbx_xcvr) {
+ //register the handle to our base SBX class
+ self_base = _self_sbx_xcvr;
+}
+
+
+sbx_xcvr::sbx_version4::~sbx_version4(void){
+ /* NOP */
+}
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double target_freq) {
+ UHD_LOGV(often) << boost::format(
+ "SBX tune: target frequency %f Mhz"
+ ) % (target_freq/1e6) << std::endl;
+
+ //clip the input
+ target_freq = sbx_freq_range.clip(target_freq);
+
+ //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) //adf4351_regs_t::PRESCALER_4_5
+ (1,75) //adf4351_regs_t::PRESCALER_8_9
+ ;
+
+ //map rf divider select output dividers to enums
+ static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
+ (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1)
+ (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2)
+ (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4)
+ (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8)
+ (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16)
+ (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32)
+ (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64)
+ ;
+
+ double actual_freq, pfd_freq;
+ double ref_freq = self_base->get_iface()->get_clock_rate(unit);
+ int R=0, BS=0, N=0, FRAC=0, MOD=0;
+ int RFdiv = 1;
+ adf4351_regs_t::reference_divide_by_2_t T = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
+ adf4351_regs_t::reference_doubler_t D = adf4351_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 = adf4351_regs_t::REFERENCE_DOUBLER_ENABLED;
+
+ //increase RF divider until acceptable VCO frequency
+ double vco_freq = target_freq;
+ while (vco_freq < 2.2e9) {
+ vco_freq *= 2;
+ RFdiv *= 2;
+ }
+
+ //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
+ adf4351_regs_t::prescaler_t prescaler = target_freq > 3e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_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 exits 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((target_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 = adf4351_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))));
+
+ UHD_LOGV(often)
+ << boost::format("SBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl
+ << boost::format("SBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d"
+ ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl
+ << boost::format("SBX 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
+ adf4351_regs_t regs;
+
+ if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq)))
+ regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM;
+ else
+ regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM;
+
+ regs.frac_12_bit = FRAC;
+ regs.int_16_bit = N;
+ regs.mod_12_bit = MOD;
+ regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD)));
+ regs.feedback_select = adf4351_regs_t::FEEDBACK_SELECT_DIVIDED;
+ regs.clock_div_mode = adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE;
+ 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];
+
+ //reset the N and R counter
+ regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED;
+ self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
+ regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED;
+
+ //write the registers
+ //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
+ int addr;
+
+ for(addr=5; addr>=0; addr--){
+ UHD_LOGV(often) << boost::format(
+ "SBX SPI Reg (0x%02x): 0x%08x"
+ ) % addr % regs.get_reg(addr) << std::endl;
+ self_base->get_iface()->write_spi(
+ unit, spi_config_t::EDGE_RISE,
+ regs.get_reg(addr), 32
+ );
+ }
+
+ //return the actual frequency
+ UHD_LOGV(often) << boost::format(
+ "SBX tune: actual frequency %f Mhz"
+ ) % (actual_freq/1e6) << std::endl;
+ return actual_freq;
+}
+
diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp
new file mode 100644
index 000000000..edee46cd5
--- /dev/null
+++ b/host/lib/usrp/dboard/db_tvrx.cpp
@@ -0,0 +1,411 @@
+//
+// Copyright 2010-2012 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
+
+//ADC/DAC functions:
+//DAC 1: RF AGC
+//DAC 2: IF AGC
+
+//min freq: 50e6
+//max freq: 860e6
+//gain range: [0:1dB:115dB]
+
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/types/dict.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/array.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <utility>
+#include <cmath>
+#include <cfloat>
+#include <limits>
+#include <tuner_4937di5_regs.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * The tvrx constants
+ **********************************************************************/
+static const freq_range_t tvrx_freq_range(50e6, 860e6);
+
+static const std::vector<std::string> tvrx_antennas = list_of("RX");
+
+static const uhd::dict<std::string, freq_range_t> tvrx_freq_ranges = map_list_of
+ ("VHFLO", freq_range_t(50e6, 158e6))
+ ("VHFHI", freq_range_t(158e6, 454e6))
+ ("UHF" , freq_range_t(454e6, 860e6))
+;
+
+static const boost::array<double, 17> vhflo_gains_db =
+ {{-6.00000, -6.00000, -6.00000, -4.00000, 0.00000,
+ 5.00000, 10.00000, 17.40000, 26.30000, 36.00000,
+ 43.00000, 48.00000, 49.50000, 50.10000, 50.30000,
+ 50.30000, 50.30000}};
+
+static const boost::array<double, 17> vhfhi_gains_db =
+ {{-13.3000, -13.3000, -13.3000, -1.0000, 7.7000,
+ 11.0000, 14.7000, 19.3000, 26.1000, 36.0000,
+ 42.7000, 46.0000, 47.0000, 47.8000, 48.2000,
+ 48.2000, 48.2000}};
+
+static const boost::array<double, 17> uhf_gains_db =
+ {{-8.0000, -8.0000, -7.0000, 4.0000, 10.2000,
+ 14.5000, 17.5000, 20.0000, 24.5000, 30.8000,
+ 37.0000, 39.8000, 40.7000, 41.6000, 42.6000,
+ 43.2000, 43.8000}};
+
+static const boost::array<double, 17> tvrx_if_gains_db =
+ {{-1.50000, -1.50000, -1.50000, -1.00000, 0.20000,
+ 2.10000, 4.30000, 6.40000, 9.00000, 12.00000,
+ 14.80000, 18.20000, 26.10000, 32.50000, 32.50000,
+ 32.50000, 32.50000}};
+
+//gain linearization data
+//this is from the datasheet and is dB vs. volts (below)
+//i tried to curve fit this, but it's really just so nonlinear that you'd
+//need dang near as many coefficients as to just map it like this and interp.
+//these numbers are culled from the 4937DI5 datasheet and are probably totally inaccurate
+//but if it's better than the old linear fit i'm happy
+static const uhd::dict<std::string, boost::array<double, 17> > tvrx_rf_gains_db = map_list_of
+ ("VHFLO", vhflo_gains_db)
+ ("VHFHI", vhfhi_gains_db)
+ ("UHF" , uhf_gains_db)
+;
+
+//sample voltages for the above points
+static const boost::array<double, 17> tvrx_gains_volts =
+ {{0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6, 2.8, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0}};
+
+static uhd::dict<std::string, gain_range_t> get_tvrx_gain_ranges(void) {
+ double rfmax = 0.0, rfmin = FLT_MAX;
+ BOOST_FOREACH(const std::string range, tvrx_rf_gains_db.keys()) {
+ double my_max = tvrx_rf_gains_db[range].back(); //we're assuming it's monotonic
+ double my_min = tvrx_rf_gains_db[range].front(); //if it's not this is wrong wrong wrong
+ if(my_max > rfmax) rfmax = my_max;
+ if(my_min < rfmin) rfmin = my_min;
+ }
+
+ double ifmin = tvrx_if_gains_db.front();
+ double ifmax = tvrx_if_gains_db.back();
+
+ return map_list_of
+ ("RF", gain_range_t(rfmin, rfmax, (rfmax-rfmin)/4096.0))
+ ("IF", gain_range_t(ifmin, ifmax, (ifmax-ifmin)/4096.0))
+ ;
+}
+
+static const double opamp_gain = 1.22; //onboard DAC opamp gain
+static const double tvrx_if_freq = 43.75e6; //IF freq of TVRX module
+static const boost::uint16_t reference_divider = 640; //clock reference divider to use
+static const double reference_freq = 4.0e6;
+
+/***********************************************************************
+ * The tvrx dboard class
+ **********************************************************************/
+class tvrx : public rx_dboard_base{
+public:
+ tvrx(ctor_args_t args);
+ ~tvrx(void);
+
+private:
+ uhd::dict<std::string, double> _gains;
+ double _lo_freq;
+ tuner_4937di5_regs_t _tuner_4937di5_regs;
+ boost::uint8_t _tuner_4937di5_addr(void){
+ return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x61 : 0x60; //ok really? we could rename that call
+ };
+
+ double set_gain(double gain, const std::string &name);
+ double set_freq(double freq);
+
+ void update_regs(void){
+ byte_vector_t regs_vector(4);
+
+ //get the register data
+ for(int i=0; i<4; i++){
+ regs_vector[i] = _tuner_4937di5_regs.get_reg(i);
+ UHD_LOGV(often) << boost::format(
+ "tvrx: send reg 0x%02x, value 0x%04x"
+ ) % int(i) % int(regs_vector[i]) << std::endl;
+ }
+
+ //send the data
+ this->get_iface()->write_i2c(
+ _tuner_4937di5_addr(), regs_vector
+ );
+ }
+
+};
+
+/***********************************************************************
+ * Register the tvrx dboard
+ **********************************************************************/
+static dboard_base::sptr make_tvrx(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new tvrx(args));
+}
+
+UHD_STATIC_BLOCK(reg_tvrx_dboard){
+ //register the factory function for the rx dbid
+ dboard_manager::register_dboard(0x0040, &make_tvrx, "TVRX");
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){
+ ////////////////////////////////////////////////////////////////////
+ // Register properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name")
+ .set("TVRX");
+ this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists
+ BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){
+ this->get_rx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&tvrx::set_gain, this, _1, name));
+ this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(get_tvrx_gain_ranges()[name]);
+ }
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&tvrx::set_freq, this, _1));
+ this->get_rx_subtree()->create<meta_range_t>("freq/range")
+ .set(tvrx_freq_range);
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .set(tvrx_antennas.at(0));
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(tvrx_antennas);
+ this->get_rx_subtree()->create<std::string>("connection")
+ .set("I");
+ this->get_rx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value")
+ .set(6.0e6);
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(6.0e6, 6.0e6));
+
+ //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
+ if (this->get_iface()->get_special_props().soft_clock_divider){
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x1); // GPIO0 is clock
+ }
+ else{
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs
+ }
+
+ //send initial register settings if necessary
+
+ //set default freq
+ _lo_freq = tvrx_freq_range.start() + tvrx_if_freq; //init _lo_freq to a sane default
+ this->get_rx_subtree()->access<double>("freq/value").set(tvrx_freq_range.start());
+
+ //set default gains
+ BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){
+ this->get_rx_subtree()->access<double>("gains/"+name+"/value")
+ .set(get_tvrx_gain_ranges()[name].start());
+ }
+}
+
+tvrx::~tvrx(void){
+}
+
+/*! Return a string corresponding to the relevant band
+ * \param freq the frequency of interest
+ * \return a string corresponding to the band
+ */
+
+static std::string get_band(double freq) {
+ BOOST_FOREACH(const std::string &band, tvrx_freq_ranges.keys()) {
+ if(freq >= tvrx_freq_ranges[band].start() && freq <= tvrx_freq_ranges[band].stop()){
+ UHD_LOGV(often) << "Band: " << band << std::endl;
+ return band;
+ }
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+/*!
+ * Execute a linear interpolation to find the voltage corresponding to a desired gain
+ * \param gain the desired gain in dB
+ * \param db_vector the vector of dB readings
+ * \param volts_vector the corresponding vector of voltages db_vector was sampled at
+ * \return a voltage to feed the TVRX analog gain
+ */
+
+static double gain_interp(double gain, boost::array<double, 17> db_vector, boost::array<double, 17> volts_vector) {
+ double volts;
+ gain = uhd::clip<double>(gain, db_vector.front(), db_vector.back()); //let's not get carried away here
+
+ boost::uint8_t gain_step = 0;
+ //find which bin we're in
+ for(size_t i = 0; i < db_vector.size()-1; i++) {
+ if(gain >= db_vector[i] && gain <= db_vector[i+1]) gain_step = i;
+ }
+
+ //find the current slope for linear interpolation
+ double slope = (volts_vector[gain_step + 1] - volts_vector[gain_step])
+ / (db_vector[gain_step + 1] - db_vector[gain_step]);
+
+ //the problem here is that for gains approaching the maximum, the voltage slope becomes infinite
+ //i.e., a small change in gain requires an infinite change in voltage
+ //to cope, we limit the slope
+
+ if(slope == std::numeric_limits<double>::infinity())
+ return volts_vector[gain_step];
+
+ //use the volts per dB slope to find the final interpolated voltage
+ volts = volts_vector[gain_step] + (slope * (gain - db_vector[gain_step]));
+
+ UHD_LOGV(often) << "Gain interp: gain: " << gain << ", gain_step: " << int(gain_step) << ", slope: " << slope << ", volts: " << volts << std::endl;
+
+ return volts;
+}
+
+/*!
+ * Convert a requested gain for the RF gain into a DAC voltage.
+ * 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 double rf_gain_to_voltage(double gain, double lo_freq){
+ //clip the input
+ gain = get_tvrx_gain_ranges()["RF"].clip(gain);
+
+ //first we need to find out what band we're in, because gains are different across different bands
+ std::string band = get_band(lo_freq - tvrx_if_freq);
+
+ //this is the voltage at the TVRX gain input
+ double gain_volts = gain_interp(gain, tvrx_rf_gains_db[band], tvrx_gains_volts);
+ //this is the voltage at the USRP DAC output
+ double dac_volts = gain_volts / opamp_gain;
+
+ dac_volts = uhd::clip<double>(dac_volts, 0.0, 3.3);
+
+ UHD_LOGV(often) << boost::format(
+ "tvrx RF AGC gain: %f dB, dac_volts: %f V"
+ ) % gain % dac_volts << std::endl;
+
+ return dac_volts;
+}
+
+/*!
+ * Convert a requested gain for the IF gain into a DAC voltage.
+ * 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 double if_gain_to_voltage(double gain){
+ //clip the input
+ gain = get_tvrx_gain_ranges()["IF"].clip(gain);
+
+ double gain_volts = gain_interp(gain, tvrx_if_gains_db, tvrx_gains_volts);
+ double dac_volts = gain_volts / opamp_gain;
+
+ dac_volts = uhd::clip<double>(dac_volts, 0.0, 3.3);
+
+ UHD_LOGV(often) << boost::format(
+ "tvrx IF AGC gain: %f dB, dac_volts: %f V"
+ ) % gain % dac_volts << std::endl;
+
+ return dac_volts;
+}
+
+double tvrx::set_gain(double gain, const std::string &name){
+ assert_has(get_tvrx_gain_ranges().keys(), name, "tvrx gain name");
+ if (name == "RF"){
+ this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_B, rf_gain_to_voltage(gain, _lo_freq));
+ }
+ else if(name == "IF"){
+ this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, if_gain_to_voltage(gain));
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ _gains[name] = gain;
+
+ return gain;
+}
+
+/*!
+ * Set the tuner to center the desired frequency at 43.75MHz
+ * \param freq the requested frequency
+ */
+
+double tvrx::set_freq(double freq) {
+ freq = tvrx_freq_range.clip(freq);
+ std::string prev_band = get_band(_lo_freq - tvrx_if_freq);
+ std::string new_band = get_band(freq);
+
+ double target_lo_freq = freq + tvrx_if_freq; //the desired LO freq for high-side mixing
+ double f_ref = reference_freq / double(reference_divider); //your tuning step size
+
+ int divisor = int((target_lo_freq + (f_ref * 4.0)) / (f_ref * 8)); //the divisor we'll use
+ double actual_lo_freq = (f_ref * 8 * divisor); //the LO freq we'll actually get
+
+ if((divisor & ~0x7fff)) UHD_THROW_INVALID_CODE_PATH();
+
+ //now we update the registers
+ _tuner_4937di5_regs.db1 = (divisor >> 8) & 0xff;
+ _tuner_4937di5_regs.db2 = divisor & 0xff;
+
+ if(new_band == "VHFLO") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFLO;
+ else if(new_band == "VHFHI") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFHI;
+ else if(new_band == "UHF") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_UHF;
+ else UHD_THROW_INVALID_CODE_PATH();
+
+ _tuner_4937di5_regs.power = tuner_4937di5_regs_t::POWER_OFF;
+ update_regs();
+
+ //ok don't forget to reset RF gain here if the new band != the old band
+ //we do this because the gains are different for different band settings
+ //not FAR off, but we do this to be consistent
+ if(prev_band != new_band) set_gain(_gains["RF"], "RF");
+
+ UHD_LOGV(often) << boost::format("set_freq: target LO: %f f_ref: %f divisor: %i actual LO: %f") % target_lo_freq % f_ref % divisor % actual_lo_freq << std::endl;
+
+ _lo_freq = actual_lo_freq; //for rx props
+
+ //Check the the IF if larger than the dsp rate and apply a corrective adjustment
+ //so that the cordic will be tuned to a possible rate within its range.
+ const double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX);
+ if (tvrx_if_freq >= codec_rate/2){
+ return _lo_freq - codec_rate;
+ }
+
+ return _lo_freq;
+}
diff --git a/host/lib/usrp/dboard/db_tvrx2.cpp b/host/lib/usrp/dboard/db_tvrx2.cpp
new file mode 100644
index 000000000..0bfa5229a
--- /dev/null
+++ b/host/lib/usrp/dboard/db_tvrx2.cpp
@@ -0,0 +1,1844 @@
+//
+// Copyright 2010,2012 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 REFCLOCK_DIV_MASK ((1 << 8)|(1 << 9)|(1 << 10)) // Three GPIO lines to CPLD for Clock Divisor Selection
+#define REFCLOCK_DIV8 ((1 << 8)|(1 << 9)|(1 << 10)) // GPIO to set clock div8 mode
+#define REFCLOCK_DIV7 ((0 << 8)|(1 << 9)|(1 << 10)) // GPIO to set clock div7 mode
+#define REFCLOCK_DIV6 ((1 << 8)|(0 << 9)|(1 << 10)) // GPIO to set clock div6 mode
+#define REFCLOCK_DIV5 ((0 << 8)|(0 << 9)|(1 << 10)) // GPIO to set clock div5 mode
+#define REFCLOCK_DIV4 ((1 << 8)|(1 <<9)) // GPIO to set clock div4 mode
+#define REFCLOCK_DIV3 (1 <<9) // GPIO to set clock div3 mode
+#define REFCLOCK_DIV2 (1 <<8) // GPIO to set clock div2 mode
+#define REFCLOCK_DIV1 ((0 << 8)|(0 << 9)|(0 << 10)) // GPIO to set clock div1 mode
+
+// RX1 IO Pins
+#define RX1_MASTERSYNC (1 << 3) // MASTERSYNC Signal for Slave Tuner Coordination
+#define RX1_FREEZE (1 << 2) // FREEZE Signal for Slave Tuner Coordination
+#define RX1_IRQ (1 << 1) // IRQ Signals TDA18272HNM State Machine Completion
+#define RX1_VSYNC (1 << 0) // VSYNC Pulse for AGC Holdoff
+
+// RX2 IO Pins
+#define RX2_MASTERSYNC (1 << 7) // MASTERSYNC Signal for Slave Tuner Coordination
+#define RX2_FREEZE (1 << 6) // FREEZE Signal for Slave Tuner Coordination
+#define RX2_IRQ (1 << 5) // IRQ Signals TDA18272HNM State Machine Completion
+#define RX2_VSYNC (1 << 4) // VSYNC Pulse for AGC Holdoff
+
+// Pin functions
+#define RX1_OUTPUT_MASK (0)
+#define RX1_INPUT_MASK (RX1_VSYNC|RX1_MASTERSYNC|RX1_FREEZE|RX1_IRQ)
+
+#define RX2_OUTPUT_MASK (0)
+#define RX2_INPUT_MASK (RX2_VSYNC|RX2_MASTERSYNC|RX2_FREEZE|RX2_IRQ)
+
+#define OUTPUT_MASK (RX1_OUTPUT_MASK|RX2_OUTPUT_MASK|REFCLOCK_DIV_MASK)
+#define INPUT_MASK (RX1_INPUT_MASK|RX2_INPUT_MASK)
+
+
+#include "tda18272hnm_regs.hpp"
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/types/dict.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/array.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <utility>
+#include <cmath>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * The TVRX2 types
+ **********************************************************************/
+struct tvrx2_tda18272_rfcal_result_t {
+ boost::int8_t delta_c;
+ boost::int8_t c_offset;
+ tvrx2_tda18272_rfcal_result_t(void): delta_c(0), c_offset(0){}
+};
+
+struct tvrx2_tda18272_rfcal_coeffs_t {
+ boost::uint8_t cal_number;
+ boost::int32_t RF_A1;
+ boost::int32_t RF_B1;
+ tvrx2_tda18272_rfcal_coeffs_t(void): cal_number(0), RF_A1(0), RF_B1(0) {}
+ tvrx2_tda18272_rfcal_coeffs_t(boost::uint32_t num): RF_A1(0), RF_B1(0) { cal_number = num; }
+};
+
+struct tvrx2_tda18272_cal_map_t {
+ boost::array<boost::uint32_t, 4> cal_freq;
+ boost::array<boost::uint8_t, 4> c_offset;
+ tvrx2_tda18272_cal_map_t(boost::array<boost::uint32_t, 4> freqs, boost::array<boost::uint8_t, 4> offsets)
+ { cal_freq = freqs; c_offset = offsets; }
+};
+
+struct tvrx2_tda18272_freq_map_t {
+ boost::uint32_t rf_max;
+ boost::uint8_t c_prog;
+ boost::uint8_t gain_taper;
+ boost::uint8_t rf_band;
+ tvrx2_tda18272_freq_map_t( boost::uint32_t max, boost::uint8_t c, boost::uint8_t taper, boost::uint8_t band)
+ { rf_max = max; c_prog = c; gain_taper = taper; rf_band = band; }
+};
+
+/***********************************************************************
+ * The TVRX2 constants
+ **********************************************************************/
+
+static const boost::array<freq_range_t, 4> tvrx2_tda18272_rf_bands = list_of
+ ( freq_range_t( 44.056e6, 144.408e6) )
+ ( freq_range_t( 145.432e6, 361.496e6) )
+ ( freq_range_t( 365.592e6, 618.520e6) )
+ ( freq_range_t( 619.544e6, 865.304e6) )
+;
+
+#define TVRX2_TDA18272_FREQ_MAP_ENTRIES (565)
+
+static const uhd::dict<boost::uint32_t, tvrx2_tda18272_cal_map_t> tvrx2_tda18272_cal_map = map_list_of
+ ( 0, tvrx2_tda18272_cal_map_t( list_of( 44032000)( 48128000)( 52224000)( 56320000), list_of(15)( 0)(10)(17) ) )
+ ( 1, tvrx2_tda18272_cal_map_t( list_of( 84992000)( 89088000)( 93184000)( 97280000), list_of( 1)( 0)(-2)( 3) ) )
+ ( 2, tvrx2_tda18272_cal_map_t( list_of(106496000)(111616000)(115712000)(123904000), list_of( 0)(-1)( 1)( 2) ) )
+ ( 3, tvrx2_tda18272_cal_map_t( list_of(161792000)(165888000)(169984000)(174080000), list_of( 3)( 0)( 1)( 2) ) )
+ ( 4, tvrx2_tda18272_cal_map_t( list_of(224256000)(228352000)(232448000)(235520000), list_of( 3)( 0)( 1)( 2) ) )
+ ( 5, tvrx2_tda18272_cal_map_t( list_of(301056000)(312320000)(322560000)(335872000), list_of( 0)(-1)( 1)( 2) ) )
+ ( 6, tvrx2_tda18272_cal_map_t( list_of(389120000)(393216000)(397312000)(401408000), list_of(-2)( 0)(-1)( 1) ) )
+ ( 7, tvrx2_tda18272_cal_map_t( list_of(455680000)(460800000)(465920000)(471040000), list_of( 0)(-2)(-3)( 1) ) )
+ ( 8, tvrx2_tda18272_cal_map_t( list_of(555008000)(563200000)(570368000)(577536000), list_of(-1)( 0)(-3)(-2) ) )
+ ( 9, tvrx2_tda18272_cal_map_t( list_of(647168000)(652288000)(658432000)(662528000), list_of(-6)(-3)( 0)(-5) ) )
+ ( 10, tvrx2_tda18272_cal_map_t( list_of(748544000)(755712000)(762880000)(770048000), list_of(-6)(-3)( 0)(-5) ) )
+ ( 11, tvrx2_tda18272_cal_map_t( list_of(792576000)(801792000)(809984000)(818176000), list_of(-5)(-2)( 0)(-4) ) )
+;
+
+static const std::vector<tvrx2_tda18272_freq_map_t> tvrx2_tda18272_freq_map = list_of
+ ( tvrx2_tda18272_freq_map_t( 39936000, 0xFF, 0x17, 0) )
+ ( tvrx2_tda18272_freq_map_t( 40960000, 0xFD, 0x17, 0) )
+ ( tvrx2_tda18272_freq_map_t( 41984000, 0xF1, 0x15, 0) )
+ ( tvrx2_tda18272_freq_map_t( 43008000, 0xE5, 0x13, 0) )
+ ( tvrx2_tda18272_freq_map_t( 44032000, 0xDB, 0x13, 0) )
+ ( tvrx2_tda18272_freq_map_t( 45056000, 0xD1, 0x12, 0) )
+ ( tvrx2_tda18272_freq_map_t( 46080000, 0xC7, 0x10, 0) )
+ ( tvrx2_tda18272_freq_map_t( 47104000, 0xBE, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 48128000, 0xB5, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 49152000, 0xAD, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 50176000, 0xA6, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 51200000, 0x9F, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 52224000, 0x98, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 53248000, 0x91, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 54272000, 0x8B, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 55296000, 0x86, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 56320000, 0x80, 0x0F, 0) )
+ ( tvrx2_tda18272_freq_map_t( 57344000, 0x7B, 0x0E, 0) )
+ ( tvrx2_tda18272_freq_map_t( 58368000, 0x76, 0x0E, 0) )
+ ( tvrx2_tda18272_freq_map_t( 59392000, 0x72, 0x0D, 0) )
+ ( tvrx2_tda18272_freq_map_t( 60416000, 0x6D, 0x0D, 0) )
+ ( tvrx2_tda18272_freq_map_t( 61440000, 0x69, 0x0C, 0) )
+ ( tvrx2_tda18272_freq_map_t( 62464000, 0x65, 0x0C, 0) )
+ ( tvrx2_tda18272_freq_map_t( 63488000, 0x61, 0x0B, 0) )
+ ( tvrx2_tda18272_freq_map_t( 64512000, 0x5E, 0x0B, 0) )
+ ( tvrx2_tda18272_freq_map_t( 64512000, 0x5A, 0x0B, 0) )
+ ( tvrx2_tda18272_freq_map_t( 65536000, 0x57, 0x0A, 0) )
+ ( tvrx2_tda18272_freq_map_t( 66560000, 0x54, 0x0A, 0) )
+ ( tvrx2_tda18272_freq_map_t( 67584000, 0x51, 0x09, 0) )
+ ( tvrx2_tda18272_freq_map_t( 68608000, 0x4E, 0x09, 0) )
+ ( tvrx2_tda18272_freq_map_t( 69632000, 0x4B, 0x09, 0) )
+ ( tvrx2_tda18272_freq_map_t( 70656000, 0x49, 0x08, 0) )
+ ( tvrx2_tda18272_freq_map_t( 71680000, 0x46, 0x08, 0) )
+ ( tvrx2_tda18272_freq_map_t( 72704000, 0x44, 0x08, 0) )
+ ( tvrx2_tda18272_freq_map_t( 73728000, 0x41, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 74752000, 0x3F, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 75776000, 0x3D, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 76800000, 0x3B, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 77824000, 0x39, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 78848000, 0x37, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 79872000, 0x35, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 80896000, 0x33, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 81920000, 0x32, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 82944000, 0x30, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 83968000, 0x2F, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 84992000, 0x2D, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 86016000, 0x2C, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 87040000, 0x2A, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t( 88064000, 0x29, 0x06, 0) )
+ ( tvrx2_tda18272_freq_map_t( 89088000, 0x27, 0x06, 0) )
+ ( tvrx2_tda18272_freq_map_t( 90112000, 0x26, 0x06, 0) )
+ ( tvrx2_tda18272_freq_map_t( 91136000, 0x25, 0x06, 0) )
+ ( tvrx2_tda18272_freq_map_t( 92160000, 0x24, 0x06, 0) )
+ ( tvrx2_tda18272_freq_map_t( 93184000, 0x22, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t( 94208000, 0x21, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t( 95232000, 0x20, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t( 96256000, 0x1F, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t( 97280000, 0x1E, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t( 98304000, 0x1D, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t( 99328000, 0x1C, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(100352000, 0x1B, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(101376000, 0x1A, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(103424000, 0x19, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(104448000, 0x18, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(105472000, 0x17, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(106496000, 0x16, 0x03, 0) )
+ ( tvrx2_tda18272_freq_map_t(106496000, 0x15, 0x03, 0) )
+ ( tvrx2_tda18272_freq_map_t(108544000, 0x14, 0x03, 0) )
+ ( tvrx2_tda18272_freq_map_t(109568000, 0x13, 0x03, 0) )
+ ( tvrx2_tda18272_freq_map_t(111616000, 0x12, 0x03, 0) )
+ ( tvrx2_tda18272_freq_map_t(112640000, 0x11, 0x03, 0) )
+ ( tvrx2_tda18272_freq_map_t(113664000, 0x11, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t(114688000, 0x10, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t(115712000, 0x0F, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t(117760000, 0x0E, 0x07, 0) )
+ ( tvrx2_tda18272_freq_map_t(119808000, 0x0D, 0x06, 0) )
+ ( tvrx2_tda18272_freq_map_t(121856000, 0x0C, 0x06, 0) )
+ ( tvrx2_tda18272_freq_map_t(123904000, 0x0B, 0x06, 0) )
+ ( tvrx2_tda18272_freq_map_t(125952000, 0x0A, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t(128000000, 0x09, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t(130048000, 0x08, 0x05, 0) )
+ ( tvrx2_tda18272_freq_map_t(133120000, 0x07, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(135168000, 0x06, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(138240000, 0x05, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(141312000, 0x04, 0x04, 0) )
+ ( tvrx2_tda18272_freq_map_t(144384000, 0x03, 0x03, 0) )
+ ( tvrx2_tda18272_freq_map_t(145408000, 0xE0, 0x3F, 1) )
+ ( tvrx2_tda18272_freq_map_t(147456000, 0xDC, 0x37, 1) )
+ ( tvrx2_tda18272_freq_map_t(148480000, 0xD9, 0x32, 1) )
+ ( tvrx2_tda18272_freq_map_t(149504000, 0xD6, 0x2F, 1) )
+ ( tvrx2_tda18272_freq_map_t(149504000, 0xD2, 0x2F, 1) )
+ ( tvrx2_tda18272_freq_map_t(150528000, 0xCF, 0x2F, 1) )
+ ( tvrx2_tda18272_freq_map_t(151552000, 0xCC, 0x2B, 1) )
+ ( tvrx2_tda18272_freq_map_t(152576000, 0xC9, 0x27, 1) )
+ ( tvrx2_tda18272_freq_map_t(153600000, 0xC5, 0x27, 1) )
+ ( tvrx2_tda18272_freq_map_t(154624000, 0xC2, 0x25, 1) )
+ ( tvrx2_tda18272_freq_map_t(155648000, 0xBF, 0x23, 1) )
+ ( tvrx2_tda18272_freq_map_t(156672000, 0xBD, 0x20, 1) )
+ ( tvrx2_tda18272_freq_map_t(157696000, 0xBA, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(158720000, 0xB7, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(159744000, 0xB4, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(160768000, 0xB1, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(161792000, 0xAF, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(162816000, 0xAC, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(163840000, 0xAA, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(164864000, 0xA7, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(165888000, 0xA5, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(166912000, 0xA2, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(167936000, 0xA0, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(168960000, 0x9D, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(169984000, 0x9B, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(171008000, 0x99, 0x1F, 1) )
+ ( tvrx2_tda18272_freq_map_t(172032000, 0x97, 0x1E, 1) )
+ ( tvrx2_tda18272_freq_map_t(173056000, 0x95, 0x1D, 1) )
+ ( tvrx2_tda18272_freq_map_t(174080000, 0x92, 0x1C, 1) )
+ ( tvrx2_tda18272_freq_map_t(175104000, 0x90, 0x1B, 1) )
+ ( tvrx2_tda18272_freq_map_t(176128000, 0x8E, 0x1A, 1) )
+ ( tvrx2_tda18272_freq_map_t(177152000, 0x8C, 0x19, 1) )
+ ( tvrx2_tda18272_freq_map_t(178176000, 0x8A, 0x18, 1) )
+ ( tvrx2_tda18272_freq_map_t(179200000, 0x88, 0x17, 1) )
+ ( tvrx2_tda18272_freq_map_t(180224000, 0x86, 0x17, 1) )
+ ( tvrx2_tda18272_freq_map_t(181248000, 0x84, 0x17, 1) )
+ ( tvrx2_tda18272_freq_map_t(182272000, 0x82, 0x17, 1) )
+ ( tvrx2_tda18272_freq_map_t(183296000, 0x81, 0x17, 1) )
+ ( tvrx2_tda18272_freq_map_t(184320000, 0x7F, 0x17, 1) )
+ ( tvrx2_tda18272_freq_map_t(185344000, 0x7D, 0x16, 1) )
+ ( tvrx2_tda18272_freq_map_t(186368000, 0x7B, 0x15, 1) )
+ ( tvrx2_tda18272_freq_map_t(187392000, 0x7A, 0x14, 1) )
+ ( tvrx2_tda18272_freq_map_t(188416000, 0x78, 0x14, 1) )
+ ( tvrx2_tda18272_freq_map_t(189440000, 0x76, 0x13, 1) )
+ ( tvrx2_tda18272_freq_map_t(190464000, 0x75, 0x13, 1) )
+ ( tvrx2_tda18272_freq_map_t(191488000, 0x73, 0x13, 1) )
+ ( tvrx2_tda18272_freq_map_t(192512000, 0x71, 0x12, 1) )
+ ( tvrx2_tda18272_freq_map_t(192512000, 0x70, 0x11, 1) )
+ ( tvrx2_tda18272_freq_map_t(193536000, 0x6E, 0x11, 1) )
+ ( tvrx2_tda18272_freq_map_t(194560000, 0x6D, 0x10, 1) )
+ ( tvrx2_tda18272_freq_map_t(195584000, 0x6B, 0x10, 1) )
+ ( tvrx2_tda18272_freq_map_t(196608000, 0x6A, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(197632000, 0x68, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(198656000, 0x67, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(199680000, 0x65, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(200704000, 0x64, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(201728000, 0x63, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(202752000, 0x61, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(203776000, 0x60, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(204800000, 0x5F, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(205824000, 0x5D, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(206848000, 0x5C, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(207872000, 0x5B, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(208896000, 0x5A, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(209920000, 0x58, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(210944000, 0x57, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(211968000, 0x56, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(212992000, 0x55, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(214016000, 0x54, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(215040000, 0x53, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(216064000, 0x52, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(217088000, 0x50, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(218112000, 0x4F, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(219136000, 0x4E, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(220160000, 0x4D, 0x0E, 1) )
+ ( tvrx2_tda18272_freq_map_t(221184000, 0x4C, 0x0E, 1) )
+ ( tvrx2_tda18272_freq_map_t(222208000, 0x4B, 0x0E, 1) )
+ ( tvrx2_tda18272_freq_map_t(223232000, 0x4A, 0x0E, 1) )
+ ( tvrx2_tda18272_freq_map_t(224256000, 0x49, 0x0D, 1) )
+ ( tvrx2_tda18272_freq_map_t(225280000, 0x48, 0x0D, 1) )
+ ( tvrx2_tda18272_freq_map_t(226304000, 0x47, 0x0D, 1) )
+ ( tvrx2_tda18272_freq_map_t(227328000, 0x46, 0x0D, 1) )
+ ( tvrx2_tda18272_freq_map_t(228352000, 0x45, 0x0C, 1) )
+ ( tvrx2_tda18272_freq_map_t(229376000, 0x44, 0x0C, 1) )
+ ( tvrx2_tda18272_freq_map_t(230400000, 0x43, 0x0C, 1) )
+ ( tvrx2_tda18272_freq_map_t(231424000, 0x42, 0x0C, 1) )
+ ( tvrx2_tda18272_freq_map_t(232448000, 0x42, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(233472000, 0x41, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(234496000, 0x40, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(234496000, 0x3F, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(235520000, 0x3E, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(236544000, 0x3D, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(237568000, 0x3C, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(239616000, 0x3B, 0x0A, 1) )
+ ( tvrx2_tda18272_freq_map_t(240640000, 0x3A, 0x0A, 1) )
+ ( tvrx2_tda18272_freq_map_t(241664000, 0x39, 0x0A, 1) )
+ ( tvrx2_tda18272_freq_map_t(242688000, 0x38, 0x0A, 1) )
+ ( tvrx2_tda18272_freq_map_t(244736000, 0x37, 0x09, 1) )
+ ( tvrx2_tda18272_freq_map_t(245760000, 0x36, 0x09, 1) )
+ ( tvrx2_tda18272_freq_map_t(246784000, 0x35, 0x09, 1) )
+ ( tvrx2_tda18272_freq_map_t(248832000, 0x34, 0x09, 1) )
+ ( tvrx2_tda18272_freq_map_t(249856000, 0x33, 0x09, 1) )
+ ( tvrx2_tda18272_freq_map_t(250880000, 0x32, 0x08, 1) )
+ ( tvrx2_tda18272_freq_map_t(252928000, 0x31, 0x08, 1) )
+ ( tvrx2_tda18272_freq_map_t(253952000, 0x30, 0x08, 1) )
+ ( tvrx2_tda18272_freq_map_t(256000000, 0x2F, 0x08, 1) )
+ ( tvrx2_tda18272_freq_map_t(257024000, 0x2E, 0x08, 1) )
+ ( tvrx2_tda18272_freq_map_t(259072000, 0x2D, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(260096000, 0x2C, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(262144000, 0x2B, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(264192000, 0x2A, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(265216000, 0x29, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(267264000, 0x28, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(269312000, 0x27, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(270336000, 0x26, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(272384000, 0x25, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(274432000, 0x24, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(276480000, 0x23, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(277504000, 0x22, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(279552000, 0x21, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(281600000, 0x20, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(283648000, 0x1F, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(285696000, 0x1E, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(287744000, 0x1D, 0x0F, 1) )
+ ( tvrx2_tda18272_freq_map_t(289792000, 0x1C, 0x0E, 1) )
+ ( tvrx2_tda18272_freq_map_t(291840000, 0x1B, 0x0E, 1) )
+ ( tvrx2_tda18272_freq_map_t(293888000, 0x1A, 0x0D, 1) )
+ ( tvrx2_tda18272_freq_map_t(296960000, 0x19, 0x0D, 1) )
+ ( tvrx2_tda18272_freq_map_t(299008000, 0x18, 0x0C, 1) )
+ ( tvrx2_tda18272_freq_map_t(301056000, 0x17, 0x0C, 1) )
+ ( tvrx2_tda18272_freq_map_t(304128000, 0x16, 0x0C, 1) )
+ ( tvrx2_tda18272_freq_map_t(306176000, 0x15, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(309248000, 0x14, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(312320000, 0x13, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(314368000, 0x12, 0x0B, 1) )
+ ( tvrx2_tda18272_freq_map_t(317440000, 0x11, 0x0A, 1) )
+ ( tvrx2_tda18272_freq_map_t(320512000, 0x10, 0x0A, 1) )
+ ( tvrx2_tda18272_freq_map_t(322560000, 0x0F, 0x0A, 1) )
+ ( tvrx2_tda18272_freq_map_t(325632000, 0x0E, 0x09, 1) )
+ ( tvrx2_tda18272_freq_map_t(328704000, 0x0D, 0x09, 1) )
+ ( tvrx2_tda18272_freq_map_t(331776000, 0x0C, 0x08, 1) )
+ ( tvrx2_tda18272_freq_map_t(335872000, 0x0B, 0x08, 1) )
+ ( tvrx2_tda18272_freq_map_t(338944000, 0x0A, 0x08, 1) )
+ ( tvrx2_tda18272_freq_map_t(343040000, 0x09, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(346112000, 0x08, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(350208000, 0x07, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(354304000, 0x06, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(358400000, 0x05, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(362496000, 0x04, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(365568000, 0x04, 0x07, 1) )
+ ( tvrx2_tda18272_freq_map_t(367616000, 0xDA, 0x2A, 2) )
+ ( tvrx2_tda18272_freq_map_t(367616000, 0xD9, 0x27, 2) )
+ ( tvrx2_tda18272_freq_map_t(368640000, 0xD8, 0x27, 2) )
+ ( tvrx2_tda18272_freq_map_t(369664000, 0xD6, 0x27, 2) )
+ ( tvrx2_tda18272_freq_map_t(370688000, 0xD5, 0x27, 2) )
+ ( tvrx2_tda18272_freq_map_t(371712000, 0xD3, 0x25, 2) )
+ ( tvrx2_tda18272_freq_map_t(372736000, 0xD2, 0x23, 2) )
+ ( tvrx2_tda18272_freq_map_t(373760000, 0xD0, 0x23, 2) )
+ ( tvrx2_tda18272_freq_map_t(374784000, 0xCF, 0x21, 2) )
+ ( tvrx2_tda18272_freq_map_t(375808000, 0xCD, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(376832000, 0xCC, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(377856000, 0xCA, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(378880000, 0xC9, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(379904000, 0xC7, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(380928000, 0xC6, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(381952000, 0xC4, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(382976000, 0xC3, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(384000000, 0xC1, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(385024000, 0xC0, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(386048000, 0xBF, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(387072000, 0xBD, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(388096000, 0xBC, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(389120000, 0xBB, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(390144000, 0xB9, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(391168000, 0xB8, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(392192000, 0xB7, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(393216000, 0xB5, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(394240000, 0xB4, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(395264000, 0xB3, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(396288000, 0xB1, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(397312000, 0xB0, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(398336000, 0xAF, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(399360000, 0xAD, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(400384000, 0xAC, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(401408000, 0xAB, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(402432000, 0xAA, 0x1F, 2) )
+ ( tvrx2_tda18272_freq_map_t(403456000, 0xA8, 0x1E, 2) )
+ ( tvrx2_tda18272_freq_map_t(404480000, 0xA7, 0x1D, 2) )
+ ( tvrx2_tda18272_freq_map_t(405504000, 0xA6, 0x1D, 2) )
+ ( tvrx2_tda18272_freq_map_t(405504000, 0xA5, 0x1C, 2) )
+ ( tvrx2_tda18272_freq_map_t(406528000, 0xA3, 0x1C, 2) )
+ ( tvrx2_tda18272_freq_map_t(407552000, 0xA2, 0x1B, 2) )
+ ( tvrx2_tda18272_freq_map_t(408576000, 0xA1, 0x1B, 2) )
+ ( tvrx2_tda18272_freq_map_t(409600000, 0xA0, 0x1B, 2) )
+ ( tvrx2_tda18272_freq_map_t(410624000, 0x9F, 0x1A, 2) )
+ ( tvrx2_tda18272_freq_map_t(411648000, 0x9D, 0x1A, 2) )
+ ( tvrx2_tda18272_freq_map_t(412672000, 0x9C, 0x19, 2) )
+ ( tvrx2_tda18272_freq_map_t(413696000, 0x9B, 0x18, 2) )
+ ( tvrx2_tda18272_freq_map_t(414720000, 0x9A, 0x18, 2) )
+ ( tvrx2_tda18272_freq_map_t(415744000, 0x99, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(416768000, 0x98, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(417792000, 0x97, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(418816000, 0x95, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(419840000, 0x94, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(420864000, 0x93, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(421888000, 0x92, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(422912000, 0x91, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(423936000, 0x90, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(424960000, 0x8F, 0x17, 2) )
+ ( tvrx2_tda18272_freq_map_t(425984000, 0x8E, 0x16, 2) )
+ ( tvrx2_tda18272_freq_map_t(427008000, 0x8D, 0x16, 2) )
+ ( tvrx2_tda18272_freq_map_t(428032000, 0x8C, 0x15, 2) )
+ ( tvrx2_tda18272_freq_map_t(429056000, 0x8B, 0x15, 2) )
+ ( tvrx2_tda18272_freq_map_t(430080000, 0x8A, 0x15, 2) )
+ ( tvrx2_tda18272_freq_map_t(431104000, 0x88, 0x14, 2) )
+ ( tvrx2_tda18272_freq_map_t(432128000, 0x87, 0x14, 2) )
+ ( tvrx2_tda18272_freq_map_t(433152000, 0x86, 0x14, 2) )
+ ( tvrx2_tda18272_freq_map_t(434176000, 0x85, 0x13, 2) )
+ ( tvrx2_tda18272_freq_map_t(435200000, 0x84, 0x13, 2) )
+ ( tvrx2_tda18272_freq_map_t(436224000, 0x83, 0x13, 2) )
+ ( tvrx2_tda18272_freq_map_t(437248000, 0x82, 0x13, 2) )
+ ( tvrx2_tda18272_freq_map_t(438272000, 0x81, 0x13, 2) )
+ ( tvrx2_tda18272_freq_map_t(439296000, 0x80, 0x12, 2) )
+ ( tvrx2_tda18272_freq_map_t(440320000, 0x7F, 0x12, 2) )
+ ( tvrx2_tda18272_freq_map_t(441344000, 0x7E, 0x12, 2) )
+ ( tvrx2_tda18272_freq_map_t(442368000, 0x7D, 0x11, 2) )
+ ( tvrx2_tda18272_freq_map_t(444416000, 0x7C, 0x11, 2) )
+ ( tvrx2_tda18272_freq_map_t(445440000, 0x7B, 0x10, 2) )
+ ( tvrx2_tda18272_freq_map_t(446464000, 0x7A, 0x10, 2) )
+ ( tvrx2_tda18272_freq_map_t(447488000, 0x79, 0x10, 2) )
+ ( tvrx2_tda18272_freq_map_t(448512000, 0x78, 0x10, 2) )
+ ( tvrx2_tda18272_freq_map_t(448512000, 0x77, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(449536000, 0x76, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(450560000, 0x75, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(451584000, 0x74, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(452608000, 0x73, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(453632000, 0x72, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(454656000, 0x71, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(455680000, 0x70, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(457728000, 0x6F, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(458752000, 0x6E, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(459776000, 0x6D, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(460800000, 0x6C, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(461824000, 0x6B, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(462848000, 0x6A, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(464896000, 0x69, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(465920000, 0x68, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(466944000, 0x67, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(467968000, 0x66, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(468992000, 0x65, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(471040000, 0x64, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(472064000, 0x63, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(473088000, 0x62, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(474112000, 0x61, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(476160000, 0x60, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(477184000, 0x5F, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(478208000, 0x5E, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(479232000, 0x5D, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(481280000, 0x5C, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(482304000, 0x5B, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(483328000, 0x5A, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(485376000, 0x59, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(486400000, 0x58, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(487424000, 0x57, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(489472000, 0x56, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(490496000, 0x55, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(490496000, 0x54, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(492544000, 0x53, 0x0E, 2) )
+ ( tvrx2_tda18272_freq_map_t(493568000, 0x52, 0x0E, 2) )
+ ( tvrx2_tda18272_freq_map_t(495616000, 0x51, 0x0E, 2) )
+ ( tvrx2_tda18272_freq_map_t(496640000, 0x50, 0x0E, 2) )
+ ( tvrx2_tda18272_freq_map_t(497664000, 0x4F, 0x0E, 2) )
+ ( tvrx2_tda18272_freq_map_t(499712000, 0x4E, 0x0D, 2) )
+ ( tvrx2_tda18272_freq_map_t(500736000, 0x4D, 0x0D, 2) )
+ ( tvrx2_tda18272_freq_map_t(502784000, 0x4C, 0x0D, 2) )
+ ( tvrx2_tda18272_freq_map_t(503808000, 0x4B, 0x0D, 2) )
+ ( tvrx2_tda18272_freq_map_t(505856000, 0x4A, 0x0C, 2) )
+ ( tvrx2_tda18272_freq_map_t(506880000, 0x49, 0x0C, 2) )
+ ( tvrx2_tda18272_freq_map_t(508928000, 0x48, 0x0C, 2) )
+ ( tvrx2_tda18272_freq_map_t(509952000, 0x47, 0x0C, 2) )
+ ( tvrx2_tda18272_freq_map_t(512000000, 0x46, 0x0C, 2) )
+ ( tvrx2_tda18272_freq_map_t(513024000, 0x45, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(515072000, 0x44, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(517120000, 0x43, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(518144000, 0x42, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(520192000, 0x41, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(521216000, 0x40, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(523264000, 0x3F, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(525312000, 0x3E, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(526336000, 0x3D, 0x0B, 2) )
+ ( tvrx2_tda18272_freq_map_t(528384000, 0x3C, 0x0A, 2) )
+ ( tvrx2_tda18272_freq_map_t(530432000, 0x3B, 0x0A, 2) )
+ ( tvrx2_tda18272_freq_map_t(531456000, 0x3A, 0x0A, 2) )
+ ( tvrx2_tda18272_freq_map_t(533504000, 0x39, 0x0A, 2) )
+ ( tvrx2_tda18272_freq_map_t(534528000, 0x38, 0x0A, 2) )
+ ( tvrx2_tda18272_freq_map_t(536576000, 0x37, 0x0A, 2) )
+ ( tvrx2_tda18272_freq_map_t(537600000, 0x36, 0x09, 2) )
+ ( tvrx2_tda18272_freq_map_t(539648000, 0x35, 0x09, 2) )
+ ( tvrx2_tda18272_freq_map_t(541696000, 0x34, 0x09, 2) )
+ ( tvrx2_tda18272_freq_map_t(543744000, 0x33, 0x09, 2) )
+ ( tvrx2_tda18272_freq_map_t(544768000, 0x32, 0x09, 2) )
+ ( tvrx2_tda18272_freq_map_t(546816000, 0x31, 0x09, 2) )
+ ( tvrx2_tda18272_freq_map_t(548864000, 0x30, 0x08, 2) )
+ ( tvrx2_tda18272_freq_map_t(550912000, 0x2F, 0x08, 2) )
+ ( tvrx2_tda18272_freq_map_t(552960000, 0x2E, 0x08, 2) )
+ ( tvrx2_tda18272_freq_map_t(555008000, 0x2D, 0x08, 2) )
+ ( tvrx2_tda18272_freq_map_t(557056000, 0x2C, 0x08, 2) )
+ ( tvrx2_tda18272_freq_map_t(559104000, 0x2B, 0x08, 2) )
+ ( tvrx2_tda18272_freq_map_t(561152000, 0x2A, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(563200000, 0x29, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(565248000, 0x28, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(567296000, 0x27, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(569344000, 0x26, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(570368000, 0x26, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(571392000, 0x25, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(573440000, 0x24, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(575488000, 0x23, 0x07, 2) )
+ ( tvrx2_tda18272_freq_map_t(577536000, 0x22, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(578560000, 0x21, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(580608000, 0x20, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(583680000, 0x1F, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(585728000, 0x1E, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(587776000, 0x1D, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(589824000, 0x1C, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(592896000, 0x1B, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(594944000, 0x1A, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(596992000, 0x19, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(600064000, 0x18, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(602112000, 0x17, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(604160000, 0x16, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(607232000, 0x15, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(609280000, 0x14, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(612352000, 0x13, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(615424000, 0x12, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(617472000, 0x11, 0x0F, 2) )
+ ( tvrx2_tda18272_freq_map_t(619520000, 0x10, 0x0E, 2) )
+ ( tvrx2_tda18272_freq_map_t(621568000, 0x0F, 0x0E, 2) )
+ ( tvrx2_tda18272_freq_map_t(623616000, 0x0F, 0x0E, 2) )
+ ( tvrx2_tda18272_freq_map_t(624640000, 0xA3, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(625664000, 0xA2, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(626688000, 0xA1, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(627712000, 0xA0, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(628736000, 0x9F, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(630784000, 0x9E, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(631808000, 0x9D, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(632832000, 0x9C, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(633856000, 0x9B, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(635904000, 0x9A, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(636928000, 0x99, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(637952000, 0x98, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(638976000, 0x97, 0x1F, 3) )
+ ( tvrx2_tda18272_freq_map_t(641024000, 0x96, 0x1E, 3) )
+ ( tvrx2_tda18272_freq_map_t(642048000, 0x95, 0x1E, 3) )
+ ( tvrx2_tda18272_freq_map_t(643072000, 0x94, 0x1E, 3) )
+ ( tvrx2_tda18272_freq_map_t(644096000, 0x93, 0x1D, 3) )
+ ( tvrx2_tda18272_freq_map_t(646144000, 0x92, 0x1D, 3) )
+ ( tvrx2_tda18272_freq_map_t(647168000, 0x91, 0x1C, 3) )
+ ( tvrx2_tda18272_freq_map_t(648192000, 0x90, 0x1C, 3) )
+ ( tvrx2_tda18272_freq_map_t(650240000, 0x8F, 0x1B, 3) )
+ ( tvrx2_tda18272_freq_map_t(651264000, 0x8E, 0x1B, 3) )
+ ( tvrx2_tda18272_freq_map_t(652288000, 0x8D, 0x1B, 3) )
+ ( tvrx2_tda18272_freq_map_t(654336000, 0x8C, 0x1B, 3) )
+ ( tvrx2_tda18272_freq_map_t(655360000, 0x8B, 0x1B, 3) )
+ ( tvrx2_tda18272_freq_map_t(656384000, 0x8A, 0x1B, 3) )
+ ( tvrx2_tda18272_freq_map_t(658432000, 0x89, 0x1A, 3) )
+ ( tvrx2_tda18272_freq_map_t(659456000, 0x88, 0x1A, 3) )
+ ( tvrx2_tda18272_freq_map_t(660480000, 0x87, 0x1A, 3) )
+ ( tvrx2_tda18272_freq_map_t(661504000, 0x86, 0x19, 3) )
+ ( tvrx2_tda18272_freq_map_t(662528000, 0x85, 0x19, 3) )
+ ( tvrx2_tda18272_freq_map_t(664576000, 0x84, 0x18, 3) )
+ ( tvrx2_tda18272_freq_map_t(665600000, 0x83, 0x18, 3) )
+ ( tvrx2_tda18272_freq_map_t(666624000, 0x82, 0x18, 3) )
+ ( tvrx2_tda18272_freq_map_t(668672000, 0x81, 0x18, 3) )
+ ( tvrx2_tda18272_freq_map_t(669696000, 0x80, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(671744000, 0x7F, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(672768000, 0x7E, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(674816000, 0x7D, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(675840000, 0x7C, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(676864000, 0x7B, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(678912000, 0x7A, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(679936000, 0x79, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(681984000, 0x78, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(683008000, 0x77, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(685056000, 0x76, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(686080000, 0x75, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(688128000, 0x74, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(689152000, 0x73, 0x17, 3) )
+ ( tvrx2_tda18272_freq_map_t(691200000, 0x72, 0x16, 3) )
+ ( tvrx2_tda18272_freq_map_t(693248000, 0x71, 0x16, 3) )
+ ( tvrx2_tda18272_freq_map_t(694272000, 0x70, 0x16, 3) )
+ ( tvrx2_tda18272_freq_map_t(696320000, 0x6F, 0x15, 3) )
+ ( tvrx2_tda18272_freq_map_t(697344000, 0x6E, 0x15, 3) )
+ ( tvrx2_tda18272_freq_map_t(699392000, 0x6D, 0x15, 3) )
+ ( tvrx2_tda18272_freq_map_t(700416000, 0x6C, 0x15, 3) )
+ ( tvrx2_tda18272_freq_map_t(702464000, 0x6B, 0x14, 3) )
+ ( tvrx2_tda18272_freq_map_t(704512000, 0x6A, 0x14, 3) )
+ ( tvrx2_tda18272_freq_map_t(704512000, 0x69, 0x14, 3) )
+ ( tvrx2_tda18272_freq_map_t(706560000, 0x68, 0x14, 3) )
+ ( tvrx2_tda18272_freq_map_t(707584000, 0x67, 0x13, 3) )
+ ( tvrx2_tda18272_freq_map_t(709632000, 0x66, 0x13, 3) )
+ ( tvrx2_tda18272_freq_map_t(711680000, 0x65, 0x13, 3) )
+ ( tvrx2_tda18272_freq_map_t(712704000, 0x64, 0x13, 3) )
+ ( tvrx2_tda18272_freq_map_t(714752000, 0x63, 0x13, 3) )
+ ( tvrx2_tda18272_freq_map_t(716800000, 0x62, 0x13, 3) )
+ ( tvrx2_tda18272_freq_map_t(717824000, 0x61, 0x13, 3) )
+ ( tvrx2_tda18272_freq_map_t(719872000, 0x60, 0x13, 3) )
+ ( tvrx2_tda18272_freq_map_t(721920000, 0x5F, 0x12, 3) )
+ ( tvrx2_tda18272_freq_map_t(723968000, 0x5E, 0x12, 3) )
+ ( tvrx2_tda18272_freq_map_t(724992000, 0x5D, 0x12, 3) )
+ ( tvrx2_tda18272_freq_map_t(727040000, 0x5C, 0x12, 3) )
+ ( tvrx2_tda18272_freq_map_t(729088000, 0x5B, 0x11, 3) )
+ ( tvrx2_tda18272_freq_map_t(731136000, 0x5A, 0x11, 3) )
+ ( tvrx2_tda18272_freq_map_t(732160000, 0x59, 0x11, 3) )
+ ( tvrx2_tda18272_freq_map_t(734208000, 0x58, 0x11, 3) )
+ ( tvrx2_tda18272_freq_map_t(736256000, 0x57, 0x10, 3) )
+ ( tvrx2_tda18272_freq_map_t(738304000, 0x56, 0x10, 3) )
+ ( tvrx2_tda18272_freq_map_t(740352000, 0x55, 0x10, 3) )
+ ( tvrx2_tda18272_freq_map_t(741376000, 0x54, 0x10, 3) )
+ ( tvrx2_tda18272_freq_map_t(743424000, 0x53, 0x10, 3) )
+ ( tvrx2_tda18272_freq_map_t(745472000, 0x52, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(746496000, 0x51, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(748544000, 0x50, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(750592000, 0x4F, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(752640000, 0x4E, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(753664000, 0x4D, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(755712000, 0x4C, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(757760000, 0x4B, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(759808000, 0x4A, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(761856000, 0x49, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(762880000, 0x49, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(763904000, 0x48, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(765952000, 0x47, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(768000000, 0x46, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(770048000, 0x45, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(772096000, 0x44, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(774144000, 0x43, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(776192000, 0x42, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(778240000, 0x41, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(780288000, 0x40, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(783360000, 0x3F, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(785408000, 0x3E, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(787456000, 0x3D, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(789504000, 0x3C, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(790528000, 0x3B, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(792576000, 0x3A, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(794624000, 0x39, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(797696000, 0x38, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(799744000, 0x37, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(801792000, 0x36, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(803840000, 0x35, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(806912000, 0x34, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(808960000, 0x33, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(809984000, 0x33, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(811008000, 0x32, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(813056000, 0x31, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(816128000, 0x30, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(818176000, 0x2F, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(820224000, 0x2E, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(823296000, 0x2D, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(825344000, 0x2C, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(828416000, 0x2B, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(830464000, 0x2A, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(832512000, 0x29, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(834560000, 0x28, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(836608000, 0x27, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(839680000, 0x26, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(841728000, 0x25, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(844800000, 0x24, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(847872000, 0x23, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(849920000, 0x22, 0x0F, 3) )
+ ( tvrx2_tda18272_freq_map_t(852992000, 0x21, 0x0E, 3) )
+ ( tvrx2_tda18272_freq_map_t(855040000, 0x20, 0x0E, 3) )
+ ( tvrx2_tda18272_freq_map_t(858112000, 0x1F, 0x0E, 3) )
+ ( tvrx2_tda18272_freq_map_t(861184000, 0x1E, 0x0E, 3) )
+ ( tvrx2_tda18272_freq_map_t(863232000, 0x1D, 0x0E, 3) )
+ ( tvrx2_tda18272_freq_map_t(866304000, 0x1C, 0x0E, 3) )
+ ( tvrx2_tda18272_freq_map_t(900096000, 0x10, 0x0C, 3) )
+ ( tvrx2_tda18272_freq_map_t(929792000, 0x07, 0x0B, 3) )
+ ( tvrx2_tda18272_freq_map_t(969728000, 0x00, 0x0A, 3) )
+;
+
+static const freq_range_t tvrx2_freq_range(42e6, 870e6);
+
+static const freq_range_t tvrx2_bandwidth_range = list_of
+ (range_t(1.7e6))
+ (range_t(6.0e6))
+ (range_t(7.0e6))
+ (range_t(8.0e6))
+ (range_t(10.0e6))
+;
+
+static const uhd::dict<std::string, std::string> tvrx2_sd_name_to_antennas = map_list_of
+ ("RX1", "J100")
+ ("RX2", "J140")
+;
+
+static const uhd::dict<std::string, std::string> tvrx2_sd_name_to_conn = map_list_of
+ ("RX1", "Q")
+ ("RX2", "I")
+;
+
+static const uhd::dict<std::string, boost::uint8_t> tvrx2_sd_name_to_i2c_addr = map_list_of
+ ("RX1", 0x63)
+ ("RX2", 0x60)
+;
+
+static const uhd::dict<std::string, boost::uint8_t> tvrx2_sd_name_to_irq_io = map_list_of
+ ("RX1", (RX1_IRQ))
+ ("RX2", (RX2_IRQ))
+;
+
+static const uhd::dict<std::string, dboard_iface::aux_dac_t> tvrx2_sd_name_to_dac = map_list_of
+ ("RX1", dboard_iface::AUX_DAC_A)
+ ("RX2", dboard_iface::AUX_DAC_B)
+;
+
+static const uhd::dict<std::string, gain_range_t> tvrx2_gain_ranges = map_list_of
+// ("LNA", gain_range_t(-12, 15, 3))
+// ("RF_FILTER", gain_range_t(-11, -2, 3))
+// ("IR_MIXER", gain_range_t(2, 14, 3))
+// ("LPF", gain_range_t(0, 9, 3))
+ ("IF", gain_range_t(0, 30, 0.5))
+;
+
+/***********************************************************************
+ * The TVRX2 dboard class
+ **********************************************************************/
+class tvrx2 : public rx_dboard_base{
+public:
+ tvrx2(ctor_args_t args);
+ ~tvrx2(void);
+
+private:
+ double _freq_scalar;
+ double _lo_freq;
+ double _if_freq;
+ double _bandwidth;
+ uhd::dict<std::string, double> _gains;
+ tda18272hnm_regs_t _tda18272hnm_regs;
+ uhd::dict<boost::uint32_t, tvrx2_tda18272_rfcal_result_t> _rfcal_results;
+ uhd::dict<boost::uint32_t, tvrx2_tda18272_rfcal_coeffs_t> _rfcal_coeffs;
+
+ bool _enabled;
+
+ bool set_enabled(bool);
+
+ double set_lo_freq(double target_freq);
+ double set_gain(double gain, const std::string &name);
+ double set_bandwidth(double bandwidth);
+
+ void set_scaled_rf_freq(double rf_freq);
+ double get_scaled_rf_freq(void);
+ void set_scaled_if_freq(double if_freq);
+ double get_scaled_if_freq(void);
+ void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg);
+ void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg);
+
+ freq_range_t get_tda18272_rfcal_result_freq_range(boost::uint32_t result);
+ void tvrx2_tda18272_init_rfcal(void);
+ void tvrx2_tda18272_tune_rf_filter(boost::uint32_t uRF);
+ void soft_calibration(void);
+ void transition_0(void);
+ void transition_1(void);
+ void transition_2(int rf_freq);
+ void transition_3(void);
+ void transition_4(int rf_freq);
+ void wait_irq(void);
+ void test_rf_filter_robustness(void);
+
+/***********************************************************************
+ * The TVRX2 class helper functions
+ **********************************************************************/
+ /*!
+ * Is the IRQ set or cleared?
+ * \return true for set
+ */
+ bool get_irq(void){
+ read_reg(0x8, 0x8);
+
+ //return irq status
+ bool irq = _tda18272hnm_regs.irq_status == tda18272hnm_regs_t::IRQ_STATUS_SET;
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s): IRQ %d"
+ ) % (get_subdev_name()) % irq << std::endl;
+
+ return irq;
+ }
+
+ /*!
+ * In Power-On Reset State?
+ * Check POR logic for reset state (causes POR to clear)
+ * \return true for reset
+ */
+ bool get_power_reset(void){
+ read_reg(0x5, 0x5);
+
+ //return POR state
+ bool por = _tda18272hnm_regs.por == tda18272hnm_regs_t::POR_RESET;
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s): POR %d"
+ ) % (get_subdev_name()) % int(_tda18272hnm_regs.por) << std::endl;
+
+ return por;
+ }
+
+ /*!
+ * Get the lock detect status of the LO.
+ * \return sensor for locked
+ */
+ sensor_value_t get_locked(void){
+ read_reg(0x5, 0x5);
+
+ //return lock detect
+ bool locked = _tda18272hnm_regs.lo_lock == tda18272hnm_regs_t::LO_LOCK_LOCKED;
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s): locked %d"
+ ) % (get_subdev_name()) % locked << std::endl;
+
+ return sensor_value_t("LO", locked, "locked", "unlocked");
+ }
+
+ /*!
+ * Read the RSSI from the registers
+ * Read the RSSI from the aux adc
+ * \return the rssi sensor in dB(m?) FIXME
+ */
+ sensor_value_t get_rssi(void){
+ //Launch RSSI calculation with MSM statemachine
+ _tda18272hnm_regs.set_reg(0x19, 0x80); //set MSM_byte_1 for rssi calculation
+ _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching rssi calculation
+
+ send_reg(0x19, 0x1A);
+
+ wait_irq();
+
+ //read rssi in dBuV
+ read_reg(0x7, 0x7);
+
+ //calculate the rssi from the voltage
+ double rssi_dBuV = 40.0 + double(((110.0 - 40.0)/128.0) * _tda18272hnm_regs.get_reg(0x7));
+ double rssi = rssi_dBuV - 107.0; //convert to dBm in 50ohm environment ( -108.8 if 75ohm ) FIXME
+
+ return sensor_value_t("RSSI", rssi, "dBm");
+ }
+
+ /*!
+ * Read the Temperature from the registers
+ * \return the temp in degC
+ */
+ sensor_value_t get_temp(void){
+ //Enable Temperature reading
+ _tda18272hnm_regs.tm_on = tda18272hnm_regs_t::TM_ON_SENSOR_ON;
+ send_reg(0x4, 0x4);
+
+ //read temp in degC
+ read_reg(0x3, 0x3);
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s): Temperature %f C"
+ ) % (get_subdev_name()) % (double(_tda18272hnm_regs.tm_d)) << std::endl;
+
+ //Disable Temperature reading
+ _tda18272hnm_regs.tm_on = tda18272hnm_regs_t::TM_ON_SENSOR_OFF;
+ send_reg(0x4, 0x4);
+
+ return sensor_value_t("TEMP", double(_tda18272hnm_regs.tm_d), "degC");
+ }
+};
+
+/***********************************************************************
+ * Register the TVRX2 dboard
+ **********************************************************************/
+static dboard_base::sptr make_tvrx2(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new tvrx2(args));
+}
+
+UHD_STATIC_BLOCK(reg_tvrx2_dboard){
+ //register the factory function for the rx dbid
+ dboard_manager::register_dboard(0x0046, &make_tvrx2, "TVRX2", tvrx2_sd_name_to_conn.keys());
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){
+ //FIXME for USRP1, we can only support one TVRX2 installed
+
+ _rfcal_results = map_list_of
+ ( 0, tvrx2_tda18272_rfcal_result_t() )
+ ( 1, tvrx2_tda18272_rfcal_result_t() )
+ ( 2, tvrx2_tda18272_rfcal_result_t() )
+ ( 3, tvrx2_tda18272_rfcal_result_t() )
+ ( 4, tvrx2_tda18272_rfcal_result_t() )
+ ( 5, tvrx2_tda18272_rfcal_result_t() )
+ ( 6, tvrx2_tda18272_rfcal_result_t() )
+ ( 7, tvrx2_tda18272_rfcal_result_t() )
+ ( 8, tvrx2_tda18272_rfcal_result_t() )
+ ( 9, tvrx2_tda18272_rfcal_result_t() )
+ ( 10, tvrx2_tda18272_rfcal_result_t() )
+ ( 11, tvrx2_tda18272_rfcal_result_t() )
+ ;
+
+ _rfcal_coeffs = map_list_of
+ ( 0, tvrx2_tda18272_rfcal_coeffs_t(0) )
+ ( 1, tvrx2_tda18272_rfcal_coeffs_t(1) )
+ ( 2, tvrx2_tda18272_rfcal_coeffs_t(3) )
+ ( 3, tvrx2_tda18272_rfcal_coeffs_t(4) )
+ ( 4, tvrx2_tda18272_rfcal_coeffs_t(6) )
+ ( 5, tvrx2_tda18272_rfcal_coeffs_t(7) )
+ ( 6, tvrx2_tda18272_rfcal_coeffs_t(9) )
+ ( 7, tvrx2_tda18272_rfcal_coeffs_t(10) )
+ ;
+
+ //set defaults for LO, gains, and filter bandwidth
+ _bandwidth = 10e6;
+
+ _if_freq = 12.5e6;
+
+ _enabled = false;
+
+ //send initial register settings
+ //this->read_reg(0x0, 0x43);
+ //this->send_reg(0x0, 0x43);
+
+ //send magic xtal_cal_dac setting
+ send_reg(0x65, 0x65);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name")
+ .set("TVRX2");
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&tvrx2::get_locked, this));
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi")
+ .publish(boost::bind(&tvrx2::get_rssi, this));
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/temperature")
+ .publish(boost::bind(&tvrx2::get_temp, this));
+ BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){
+ this->get_rx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&tvrx2::set_gain, this, _1, name));
+ this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(tvrx2_gain_ranges[name]);
+ }
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&tvrx2::set_lo_freq, this, _1));
+ this->get_rx_subtree()->create<meta_range_t>("freq/range")
+ .set(tvrx2_freq_range);
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .set(tvrx2_sd_name_to_antennas[get_subdev_name()]);
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(list_of(tvrx2_sd_name_to_antennas[get_subdev_name()]));
+ this->get_rx_subtree()->create<std::string>("connection")
+ .set(tvrx2_sd_name_to_conn[get_subdev_name()]);
+ this->get_rx_subtree()->create<bool>("enabled")
+ .coerce(boost::bind(&tvrx2::set_enabled, this, _1))
+ .set(_enabled);
+ this->get_rx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value")
+ .coerce(boost::bind(&tvrx2::set_bandwidth, this, _1))
+ .set(_bandwidth);
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(tvrx2_bandwidth_range);
+
+ //set the gpio directions and atr controls (identically)
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0); // All unused in atr
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, OUTPUT_MASK); // Set outputs
+
+ //configure ref_clock
+ double ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX);
+
+ if (ref_clock == 64.0e6) {
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV4);
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s): Dividing Refclock by 4"
+ ) % (get_subdev_name()) << std::endl;
+
+ _freq_scalar = (4*16.0e6)/(this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX));
+ } else if (ref_clock == 100e6) {
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV6);
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s): Dividing Refclock by 6"
+ ) % (get_subdev_name()) << std::endl;
+
+ _freq_scalar = (6*16.0e6)/this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX);
+ } else {
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV6);
+ UHD_MSG(warning) << boost::format("Unsupported ref_clock %0.2f, valid options 64e6 and 100e6") % ref_clock << std::endl;
+ _freq_scalar = 1.0;
+ }
+
+ //enable only the clocks we need
+ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true);
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s): Refclock %f Hz, scalar = %f"
+ ) % (get_subdev_name()) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % _freq_scalar << std::endl;
+
+ _tda18272hnm_regs.irq_polarity = tda18272hnm_regs_t::IRQ_POLARITY_RAISED_VCC;
+ _tda18272hnm_regs.irq_clear = tda18272hnm_regs_t::IRQ_CLEAR_TRUE;
+ send_reg(0x37, 0x37);
+ send_reg(0xA, 0xA);
+
+ send_reg(0x31, 0x31); //N_CP_Current
+ send_reg(0x36, 0x36); //RSSI_Clock
+ send_reg(0x24, 0x25); //AGC1_Do_step
+ send_reg(0x2C, 0x2C); //AGC1_Do_step
+ send_reg(0x2E, 0x2E); //AGC2_Do_step
+ send_reg(0x0E, 0x0E); //AGCs_Up_step_assym
+ send_reg(0x11, 0x11); //AGCs_Do_step_assym
+
+ //intialize i2c
+ //soft_calibration();
+ //tvrx2_tda18272_init_rfcal();
+ transition_0();
+}
+
+bool tvrx2::set_enabled(bool enable){
+ if (enable == _enabled) return _enabled;
+
+ if (enable and not _enabled){
+ //setup tuner parameters
+ transition_1();
+
+ transition_2(int(tvrx2_freq_range.start()));
+
+ test_rf_filter_robustness();
+
+ BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){
+ this->get_rx_subtree()->access<double>("gains/"+name+"/value")
+ .set(tvrx2_gain_ranges[name].start());
+ }
+
+ this->get_rx_subtree()->access<double>("bandwidth/value")
+ .set(_bandwidth); // default bandwidth from datasheet
+
+ //transition_2 equivalent
+ this->get_rx_subtree()->access<double>("freq/value")
+ .set(tvrx2_freq_range.start());
+
+ //enter standby mode
+ transition_3();
+ _enabled = true;
+
+ } else {
+ //enter standby mode
+ transition_3();
+ _enabled = false;
+ }
+
+ return _enabled;
+}
+
+tvrx2::~tvrx2(void){
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s): Called Destructor"
+ ) % (get_subdev_name()) << std::endl;
+ UHD_SAFE_CALL(if (_enabled) set_enabled(false);)
+}
+
+
+/***********************************************************************
+ * TDA18272 Register IO Functions
+ **********************************************************************/
+void tvrx2::set_scaled_rf_freq(double rf_freq){
+ _tda18272hnm_regs.set_rf_freq(_freq_scalar*rf_freq/1e3);
+}
+
+double tvrx2::get_scaled_rf_freq(void){
+ return _tda18272hnm_regs.get_rf_freq()*1e3/_freq_scalar;
+}
+
+void tvrx2::set_scaled_if_freq(double if_freq){
+ _tda18272hnm_regs.if_freq = int(_freq_scalar*if_freq/(50e3)); //max 12.8MHz??
+}
+
+double tvrx2::get_scaled_if_freq(void){
+ return _tda18272hnm_regs.if_freq*50e3/_freq_scalar;
+}
+
+void tvrx2::send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){
+ start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x43));
+ stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x43));
+
+ 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] = _tda18272hnm_regs.get_reg(start_addr+i);
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s, 0x%02x): send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d"
+ ) % (get_subdev_name()) % int(tvrx2_sd_name_to_i2c_addr[get_subdev_name()]) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl;
+ }
+
+ //send the data
+ this->get_iface()->write_i2c(
+ tvrx2_sd_name_to_i2c_addr[get_subdev_name()], regs_vector
+ );
+ }
+}
+
+void tvrx2::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(uhd::clip(int(start_reg), 0x0, 0x43));
+ stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x43));
+
+ 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 starting address
+ byte_vector_t start_address_vector(1);
+
+ //first byte is the address of first register
+ start_address_vector[0] = start_addr;
+
+ //send the start address
+ this->get_iface()->write_i2c(
+ tvrx2_sd_name_to_i2c_addr[get_subdev_name()], start_address_vector
+ );
+
+ //create buffer for register data
+ byte_vector_t regs_vector(num_bytes);
+
+ //read from i2c
+ regs_vector = this->get_iface()->read_i2c(
+ tvrx2_sd_name_to_i2c_addr[get_subdev_name()], num_bytes
+ );
+
+ for(boost::uint8_t i=0; i < num_bytes; i++){
+ if (i + start_addr >= status_addr){
+ _tda18272hnm_regs.set_reg(i + start_addr, regs_vector[i]);
+ }
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s, 0x%02x): read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d"
+ ) % (get_subdev_name()) % int(tvrx2_sd_name_to_i2c_addr[get_subdev_name()]) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl;
+ }
+ }
+}
+
+
+/***********************************************************************
+ * TDA18272 Calibration Functions
+ **********************************************************************/
+freq_range_t tvrx2::get_tda18272_rfcal_result_freq_range(boost::uint32_t result)
+{
+
+ uhd::dict<boost::uint32_t, freq_range_t> result_to_cal_freq_ranges_map = map_list_of
+ ( 0, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[0].cal_freq[_tda18272hnm_regs.rfcal_freq0] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[1].cal_freq[_tda18272hnm_regs.rfcal_freq1] * _freq_scalar
+ ) )
+ ( 1, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[1].cal_freq[_tda18272hnm_regs.rfcal_freq1] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[2].cal_freq[_tda18272hnm_regs.rfcal_freq2] * _freq_scalar
+ ) )
+ ( 2, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[2].cal_freq[_tda18272hnm_regs.rfcal_freq2] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[3].cal_freq[_tda18272hnm_regs.rfcal_freq3] * _freq_scalar
+ ) )
+ ( 3, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[3].cal_freq[_tda18272hnm_regs.rfcal_freq3] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[4].cal_freq[_tda18272hnm_regs.rfcal_freq4] * _freq_scalar
+ ) )
+ ( 4, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[4].cal_freq[_tda18272hnm_regs.rfcal_freq4] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[5].cal_freq[_tda18272hnm_regs.rfcal_freq5] * _freq_scalar
+ ) )
+ ( 5, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[5].cal_freq[_tda18272hnm_regs.rfcal_freq5] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[6].cal_freq[_tda18272hnm_regs.rfcal_freq6] * _freq_scalar
+ ) )
+ ( 6, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[6].cal_freq[_tda18272hnm_regs.rfcal_freq6] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[7].cal_freq[_tda18272hnm_regs.rfcal_freq7] * _freq_scalar
+ ) )
+ ( 7, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[7].cal_freq[_tda18272hnm_regs.rfcal_freq7] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[8].cal_freq[_tda18272hnm_regs.rfcal_freq8] * _freq_scalar
+ ) )
+ ( 8, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[8].cal_freq[_tda18272hnm_regs.rfcal_freq8] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[9].cal_freq[_tda18272hnm_regs.rfcal_freq9] * _freq_scalar
+ ) )
+ ( 9, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[9].cal_freq[_tda18272hnm_regs.rfcal_freq9] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[10].cal_freq[_tda18272hnm_regs.rfcal_freq10] * _freq_scalar
+ ) )
+ (10, freq_range_t(
+ (double) tvrx2_tda18272_cal_map[10].cal_freq[_tda18272hnm_regs.rfcal_freq10] * _freq_scalar,
+ (double) tvrx2_tda18272_cal_map[11].cal_freq[_tda18272hnm_regs.rfcal_freq11] * _freq_scalar
+ ) )
+ ;
+
+ if (result < 11)
+ return result_to_cal_freq_ranges_map[result];
+ else
+ return freq_range_t(0.0, 0.0);
+}
+
+
+/*
+ * Initialize the RF Filter calibration maps after hardware init
+ */
+void tvrx2::tvrx2_tda18272_init_rfcal(void)
+{
+
+ /* read byte 0x38-0x43 */
+ read_reg(0x38, 0x43);
+
+ uhd::dict<boost::uint32_t, boost::uint8_t> result_to_cal_regs = map_list_of
+ ( 0, _tda18272hnm_regs.rfcal_log_1)
+ ( 1, _tda18272hnm_regs.rfcal_log_2)
+ ( 2, _tda18272hnm_regs.rfcal_log_3)
+ ( 3, _tda18272hnm_regs.rfcal_log_4)
+ ( 4, _tda18272hnm_regs.rfcal_log_5)
+ ( 5, _tda18272hnm_regs.rfcal_log_6)
+ ( 6, _tda18272hnm_regs.rfcal_log_7)
+ ( 7, _tda18272hnm_regs.rfcal_log_8)
+ ( 8, _tda18272hnm_regs.rfcal_log_9)
+ ( 9, _tda18272hnm_regs.rfcal_log_10)
+ (10, _tda18272hnm_regs.rfcal_log_11)
+ (11, _tda18272hnm_regs.rfcal_log_12)
+ ;
+
+
+ // Loop through rfcal_log_* registers, initialize _rfcal_results
+ BOOST_FOREACH(const boost::uint32_t &result, result_to_cal_regs.keys())
+ _rfcal_results[result].delta_c = result_to_cal_regs[result] > 63 ? result_to_cal_regs[result] - 128 : result_to_cal_regs[result];
+
+ /* read byte 0x26-0x2B */
+ read_reg(0x26, 0x2B);
+
+ // Loop through rfcal_byte_* registers, initialize _rfcal_coeffs
+ BOOST_FOREACH(const boost::uint32_t &subband, _rfcal_coeffs.keys())
+ {
+ freq_range_t subband_freqs;
+
+ boost::uint32_t result = _rfcal_coeffs[subband].cal_number;
+
+ subband_freqs = get_tda18272_rfcal_result_freq_range(result);
+
+ _rfcal_coeffs[subband].RF_B1 = _rfcal_results[result].delta_c + tvrx2_tda18272_cal_map[result].c_offset[_rfcal_results[result].c_offset];
+
+ boost::uint32_t quotient = (((_rfcal_results[result+1].delta_c + tvrx2_tda18272_cal_map[result+1].c_offset[_rfcal_results[result].c_offset])
+ - (_rfcal_results[result].delta_c + tvrx2_tda18272_cal_map[result].c_offset[_rfcal_results[result].c_offset])) * 1000000);
+
+ boost::uint32_t divisor = ((boost::int32_t)(subband_freqs.stop() - subband_freqs.start())/1000);
+
+ _rfcal_coeffs[subband].RF_A1 = quotient / divisor;
+
+ }
+
+}
+
+/*
+ * Apply calibration coefficients to RF Filter tuning
+ */
+void tvrx2::tvrx2_tda18272_tune_rf_filter(boost::uint32_t uRF)
+{
+ boost::uint32_t uCounter = 0;
+ boost::uint8_t cal_result = 0;
+ boost::uint32_t uRFCal0 = 0;
+ boost::uint32_t uRFCal1 = 0;
+ boost::uint8_t subband = 0;
+ boost::int32_t cProg = 0;
+ boost::uint8_t gain_taper = 0;
+ boost::uint8_t RFBand = 0;
+ boost::int32_t RF_A1 = 0;
+ boost::int32_t RF_B1 = 0;
+ freq_range_t subband_freqs;
+
+ /* read byte 0x26-0x2B */
+ read_reg(0x26, 0x2B);
+
+ subband_freqs = get_tda18272_rfcal_result_freq_range(1);
+ uRFCal0 = subband_freqs.start();
+ subband_freqs = get_tda18272_rfcal_result_freq_range(4);
+ uRFCal1 = subband_freqs.start();
+
+ if(uRF < uRFCal0)
+ subband = 0;
+ else if(uRF < 145700000)
+ subband = 1;
+ else if(uRF < uRFCal1)
+ subband = 2;
+ else if(uRF < 367400000)
+ subband = 3;
+ else
+ {
+ subband_freqs = get_tda18272_rfcal_result_freq_range(7);
+ uRFCal0 = subband_freqs.start();
+ subband_freqs = get_tda18272_rfcal_result_freq_range(10);
+ uRFCal1 = subband_freqs.start();
+
+ if(uRF < uRFCal0)
+ subband = 4;
+ else if(uRF < 625000000)
+ subband = 5;
+ else if(uRF < uRFCal1)
+ subband = 6;
+ else
+ subband = 7;
+ }
+
+ cal_result = _rfcal_coeffs[subband].cal_number;
+ subband_freqs = get_tda18272_rfcal_result_freq_range(cal_result);
+ uRFCal0 = subband_freqs.start();
+
+ RF_A1 = _rfcal_coeffs[subband].RF_A1;
+ RF_B1 = _rfcal_coeffs[subband].RF_B1;
+
+ uCounter = 0;
+ do uCounter ++;
+ while (uRF >= tvrx2_tda18272_freq_map[uCounter].rf_max && uCounter < TVRX2_TDA18272_FREQ_MAP_ENTRIES);
+
+ cProg = tvrx2_tda18272_freq_map[uCounter - 1].c_prog;
+ gain_taper = tvrx2_tda18272_freq_map[uCounter - 1].gain_taper;
+ RFBand = tvrx2_tda18272_freq_map[uCounter - 1].rf_band;
+
+ cProg = (boost::int32_t)(cProg + RF_B1 + (RF_A1*((boost::int32_t)(uRF - uRFCal0)/1000))/1000000);
+
+ if(cProg>255) cProg = 255;
+ if(cProg<0) cProg = 0;
+
+ _tda18272hnm_regs.rf_filter_bypass = 1;
+ _tda18272hnm_regs.rf_filter_cap = (boost::uint8_t) cProg;
+ _tda18272hnm_regs.gain_taper = gain_taper;
+ _tda18272hnm_regs.rf_filter_band = RFBand;
+
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Software Calibration:\n"
+ "\tRF Filter Bypass = %d\n"
+ "\tRF Filter Cap = %d\n"
+ "\tRF Filter Band = %d\n"
+ "\tGain Taper = %d\n")
+ % (get_subdev_name())
+ % int(_tda18272hnm_regs.rf_filter_bypass)
+ % int(_tda18272hnm_regs.rf_filter_cap)
+ % int(_tda18272hnm_regs.rf_filter_band)
+ % int(_tda18272hnm_regs.gain_taper)
+ << std::endl;
+
+ send_reg(0x2c, 0x2f);
+}
+
+void tvrx2::soft_calibration(void){
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Software Calibration: Initialize Tuner, Calibrate and Standby\n") % (get_subdev_name()) << std::endl;
+
+ _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL;
+ _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON;
+ _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON;
+
+ send_reg(0x6, 0x6);
+ read_reg(0x6, 0x6);
+
+ read_reg(0x19, 0x1A);
+ read_reg(0x26, 0x2B);
+
+ _tda18272hnm_regs.rfcal_freq0 = 0x2;
+ _tda18272hnm_regs.rfcal_freq1 = 0x2;
+ _tda18272hnm_regs.rfcal_freq2 = 0x2;
+ _tda18272hnm_regs.rfcal_freq3 = 0x2;
+ _tda18272hnm_regs.rfcal_freq4 = 0x2;
+ _tda18272hnm_regs.rfcal_freq5 = 0x2;
+ _tda18272hnm_regs.rfcal_freq6 = 0x2;
+ _tda18272hnm_regs.rfcal_freq7 = 0x2;
+ _tda18272hnm_regs.rfcal_freq8 = 0x2;
+ _tda18272hnm_regs.rfcal_freq9 = 0x2;
+ _tda18272hnm_regs.rfcal_freq10 = 0x2;
+ _tda18272hnm_regs.rfcal_freq11 = 0x2;
+
+ send_reg(0x26, 0x2B);
+
+ _tda18272hnm_regs.set_reg(0x19, 0x3B); //set MSM_byte_1 for calibration per datasheet
+ _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration
+
+ send_reg(0x19, 0x1A);
+
+ wait_irq();
+
+ send_reg(0x1D, 0x1D); //Fmax_LO
+ send_reg(0x0C, 0x0C); //LT_Enable
+ send_reg(0x1B, 0x1B); //PSM_AGC1
+ send_reg(0x0C, 0x0C); //AGC1_6_15dB
+
+ //set spread spectrum for clock
+ //FIXME: NXP turns clock spread on and off
+ // based on where clock spurs would be relative to RF frequency
+ // we should do this also
+ _tda18272hnm_regs.digital_clock = tda18272hnm_regs_t::DIGITAL_CLOCK_SPREAD_OFF;
+ if (get_subdev_name() == "RX1")
+ //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO;
+ _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ;
+ else
+ //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO;
+ _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ;
+
+ send_reg(0x14, 0x14);
+
+ _tda18272hnm_regs.set_reg(0x36, 0x0E); //sets clock mode
+ send_reg(0x36, 0x36);
+
+ //go to standby mode
+ _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY;
+ send_reg(0x6, 0x6);
+}
+
+void tvrx2::test_rf_filter_robustness(void){
+ typedef uhd::dict<std::string, std::string> tvrx2_filter_ratings_t;
+ typedef uhd::dict<std::string, double> tvrx2_filter_margins_t;
+
+ tvrx2_filter_margins_t _filter_margins;
+ tvrx2_filter_ratings_t _filter_ratings;
+
+ read_reg(0x38, 0x43);
+
+ uhd::dict<std::string, boost::uint8_t> filter_cal_regs = map_list_of
+ ("VHFLow_0", 0x38)
+ ("VHFLow_1", 0x3a)
+ ("VHFHigh_0", 0x3b)
+ ("VHFHigh_1", 0x3d)
+ ("UHFLow_0", 0x3e)
+ ("UHFLow_1", 0x40)
+ ("UHFHigh_0", 0x41)
+ ("UHFHigh_1", 0x43)
+ ;
+
+ BOOST_FOREACH(const std::string &name, filter_cal_regs.keys()){
+ boost::uint8_t cal_result = _tda18272hnm_regs.get_reg(filter_cal_regs[name]);
+ if (cal_result & 0x80) {
+ _filter_ratings.set(name, "E");
+ _filter_margins.set(name, 0.0);
+ }
+ else {
+ double partial;
+
+ if (name == "VHFLow_0")
+ partial = 100 * (45 - 39.8225 * (1 + (0.31 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / 45.0;
+
+ else if (name == "VHFLow_1")
+ partial = 100 * (152.1828 * (1 + (1.53 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (144.896 - 6)) / (144.896 - 6);
+
+ else if (name == "VHFHigh_0")
+ partial = 100 * ((144.896 + 6) - 135.4063 * (1 + (0.27 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (144.896 + 6);
+
+ else if (name == "VHFHigh_1")
+ partial = 100 * (383.1455 * (1 + (0.91 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (367.104 - 8)) / (367.104 - 8);
+
+ else if (name == "UHFLow_0")
+ partial = 100 * ((367.104 + 8) - 342.6224 * (1 + (0.21 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (367.104 + 8);
+
+ else if (name == "UHFLow_1")
+ partial = 100 * (662.5595 * (1 + (0.33 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (624.128 - 2)) / (624.128 - 2);
+
+ else if (name == "UHFHigh_0")
+ partial = 100 * ((624.128 + 2) - 508.2747 * (1 + (0.23 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (624.128 + 2);
+
+ else if (name == "UHFHigh_1")
+ partial = 100 * (947.8913 * (1 + (0.3 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (866 - 14)) / (866 - 14);
+
+ else
+ UHD_THROW_INVALID_CODE_PATH();
+
+ _filter_margins.set(name, 0.0024 * partial * partial * partial - 0.101 * partial * partial + 1.629 * partial + 1.8266);
+ _filter_ratings.set(name, _filter_margins[name] >= 0.0 ? "H" : "L");
+ }
+ }
+
+ std::stringstream robustness_message;
+ robustness_message << boost::format("TVRX2 (%s): RF Filter Robustness Results:") % (get_subdev_name()) << std::endl;
+ BOOST_FOREACH(const std::string &name, uhd::sorted(_filter_ratings.keys())){
+ robustness_message << boost::format("\t%s:\tMargin = %0.2f,\tRobustness = %c") % name % (_filter_margins[name]) % (_filter_ratings[name]) << std::endl;
+ }
+
+ UHD_LOGV(often) << robustness_message.str();
+}
+
+/***********************************************************************
+ * TDA18272 State Functions
+ **********************************************************************/
+void tvrx2::transition_0(void){
+ //Transition 0: Initialize Tuner and place in standby
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Transistion 0: Initialize Tuner, Calibrate and Standby\n") % (get_subdev_name()) << std::endl;
+
+ //Check for Power-On Reset, if reset, initialze tuner
+ if (get_power_reset()) {
+ _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL;
+ _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON;
+ _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON;
+
+ send_reg(0x6, 0x6);
+ read_reg(0x6, 0x6);
+
+ read_reg(0x19, 0x1A);
+
+ _tda18272hnm_regs.set_reg(0x19, 0x3B); //set MSM_byte_1 for calibration per datasheet
+ _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration
+
+ send_reg(0x19, 0x1A);
+
+ wait_irq();
+ }
+
+ //send magic xtal_cal_dac setting
+ send_reg(0x65, 0x65);
+
+ send_reg(0x1D, 0x1D); //Fmax_LO
+ send_reg(0x0C, 0x0C); //LT_Enable
+ send_reg(0x1B, 0x1B); //PSM_AGC1
+ send_reg(0x0C, 0x0C); //AGC1_6_15dB
+
+ //set spread spectrum for clock
+ //FIXME: NXP turns clock spread on and off
+ // based on where clock spurs would be relative to RF frequency
+ // we should do this also
+ _tda18272hnm_regs.digital_clock = tda18272hnm_regs_t::DIGITAL_CLOCK_SPREAD_OFF;
+ if (get_subdev_name() == "RX1")
+ //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO;
+ _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ;
+ else
+ //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO;
+ _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ;
+
+ send_reg(0x14, 0x14);
+
+ _tda18272hnm_regs.set_reg(0x36, 0x0E); //sets clock mode
+ send_reg(0x36, 0x36);
+
+ //go to standby mode
+ _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY;
+ send_reg(0x6, 0x6);
+}
+
+void tvrx2::transition_1(void){
+ //Transition 1: Select TV Standard
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Transistion 1: Select TV Standard\n") % (get_subdev_name()) << std::endl;
+
+ //send magic xtal_cal_dac setting
+ send_reg(0x65, 0x65);
+
+ //Choose IF Byte 1 Setting
+ //_tda18272hnm_regs.if_hp_fc = tda18272hnm_regs_t::IF_HP_FC_0_4MHZ;
+ //_tda18272hnm_regs.if_notch = tda18272hnm_regs_t::IF_NOTCH_OFF;
+ //_tda18272hnm_regs.lp_fc_offset = tda18272hnm_regs_t::LP_FC_OFFSET_0_PERCENT;
+ //_tda18272hnm_regs.lp_fc = tda18272hnm_regs_t::LP_FC_10_0MHZ;
+ //send_reg(0x13, 0x13);
+
+ //Choose IR Mixer Byte 2 Setting
+ //_tda18272hnm_regs.hi_pass = tda18272hnm_regs_t::HI_PASS_DISABLE;
+ //_tda18272hnm_regs.dc_notch = tda18272hnm_regs_t::DC_NOTCH_OFF;
+ send_reg(0x23, 0x23);
+
+ //Set AGC TOP Bytes
+ send_reg(0x0C, 0x13);
+
+ //Set PSM Byt1
+ send_reg(0x1B, 0x1B);
+
+ //Choose IF Frequency, setting is 50KHz steps
+ set_scaled_if_freq(_if_freq);
+ send_reg(0x15, 0x15);
+}
+
+void tvrx2::transition_2(int rf_freq){
+ //Transition 2: Select RF Frequency after changing TV Standard
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Transistion 2: Select RF Frequency after changing TV Standard\n") % (get_subdev_name()) << std::endl;
+
+ //send magic xtal_cal_dac setting
+ send_reg(0x65, 0x65);
+
+ //Wake up from Standby
+ _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL;
+ _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON;
+ _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON;
+
+ send_reg(0x6, 0x6);
+
+ //Set Clock Mode
+ _tda18272hnm_regs.set_reg(0x36, 0x00);
+ send_reg(0x36, 0x36);
+
+ //Set desired RF Frequency
+ set_scaled_rf_freq(rf_freq);
+ send_reg(0x16, 0x18);
+
+ //Lock PLL and tune RF Filters
+ _tda18272hnm_regs.set_reg(0x19, 0x41); //set MSM_byte_1 for RF Filters Tuning, PLL Locking
+ _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration
+
+ send_reg(0x19, 0x1A);
+
+ wait_irq();
+
+ tvrx2_tda18272_tune_rf_filter(rf_freq);
+
+ ////LO Lock state in Reg 0x5 LSB
+ //read_reg(0x6, 0x6);
+
+}
+
+void tvrx2::transition_3(void){
+ //Transition 3: Standby Mode
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Transistion 3: Standby Mode\n") % (get_subdev_name()) << std::endl;
+
+ //send magic xtal_cal_dac setting
+ send_reg(0x65, 0x65);
+
+ //Set clock mode
+ _tda18272hnm_regs.set_reg(0x36, 0x0E);
+ send_reg(0x36, 0x36);
+
+ //go to standby mode
+ _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY;
+ _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_OFF;
+ _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_OFF;
+ send_reg(0x6, 0x6);
+}
+
+void tvrx2::transition_4(int rf_freq){
+ //Transition 4: Change RF Frequency without changing TV Standard
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Transistion 4: Change RF Frequency without changing TV Standard\n") % (get_subdev_name()) << std::endl;
+
+ //send magic xtal_cal_dac setting
+ send_reg(0x65, 0x65);
+
+ //Set desired RF Frequency
+ set_scaled_rf_freq(rf_freq);
+ send_reg(0x16, 0x18);
+
+ //Lock PLL and tune RF Filters
+ _tda18272hnm_regs.set_reg(0x19, 0x41); //set MSM_byte_1 for RF Filters Tuning, PLL Locking
+ _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration
+
+ send_reg(0x19, 0x1A);
+
+ wait_irq();
+
+ tvrx2_tda18272_tune_rf_filter(rf_freq);
+
+ ////LO Lock state in Reg 0x5 LSB
+ //read_reg(0x5, 0x6);
+
+}
+
+void tvrx2::wait_irq(void){
+ int timeout = 20; //irq waiting timeout in milliseconds
+ //int irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & int(tvrx2_sd_name_to_irq_io[get_subdev_name()]));
+ bool irq = get_irq();
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Waiting on IRQ, subdev = %d, mask = 0x%x, Status: 0x%x\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq << std::endl;
+
+ while (not irq and timeout > 0) {
+ //irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & tvrx2_sd_name_to_irq_io[get_subdev_name()]);
+ irq = get_irq();
+ boost::this_thread::sleep(boost::posix_time::milliseconds(10));
+ timeout -= 1;
+ }
+
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): IRQ Raised, subdev = %d, mask = 0x%x, Status: 0x%x, Timeout: %d\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq % timeout << std::endl;
+
+ read_reg(0xA, 0xB);
+ //UHD_ASSERT_THROW(timeout > 0);
+ if(timeout <= 0) UHD_MSG(warning) << boost::format(
+ "\nTVRX2 (%s): Timeout waiting on IRQ\n") % (get_subdev_name()) << std::endl;
+
+ _tda18272hnm_regs.irq_clear = tda18272hnm_regs_t::IRQ_CLEAR_TRUE;
+ send_reg(0xA, 0xA);
+ read_reg(0xA, 0xB);
+
+ irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & tvrx2_sd_name_to_irq_io[get_subdev_name()]);
+
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): Cleared IRQ, subdev = %d, mask = 0x%x, Status: 0x%x\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq << std::endl;
+}
+
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double tvrx2::set_lo_freq(double target_freq){
+ //target_freq = std::clip(target_freq, tvrx2_freq_range.min, tvrx2_freq_range.max);
+
+ read_reg(0x6, 0x6);
+
+ if (_tda18272hnm_regs.sm == tda18272hnm_regs_t::SM_STANDBY) {
+ transition_2(int(target_freq + _bandwidth/2 - get_scaled_if_freq()));
+ } else {
+ transition_4(int(target_freq + _bandwidth/2 - get_scaled_if_freq()));
+ }
+ read_reg(0x16, 0x18);
+
+ //compute actual tuned frequency
+ _lo_freq = get_scaled_rf_freq() + get_scaled_if_freq(); // - _bandwidth/2;
+
+ //debug output of calculated variables
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): LO Frequency\n"
+ "\tRequested: \t%f\n"
+ "\tComputed: \t%f\n"
+ "\tReadback: \t%f\n"
+ "\tIF Frequency: \t%f\n") % (get_subdev_name()) % target_freq % double(int(target_freq/1e3)*1e3) % get_scaled_rf_freq() % get_scaled_if_freq() << std::endl;
+
+ get_locked();
+
+ test_rf_filter_robustness();
+
+ UHD_LOGV(often) << boost::format(
+ "\nTVRX2 (%s): RSSI = %f dBm\n"
+ ) % (get_subdev_name()) % (get_rssi().to_real()) << std::endl;
+
+ return _lo_freq;
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+/*
+ * Convert the requested gain into a dac voltage
+ */
+static double gain_to_if_gain_dac(double &gain){
+ //clip the input
+ gain = tvrx2_gain_ranges["IF"].clip(gain);
+
+ //voltage level constants
+ static const double max_volts = double(1.7), min_volts = double(0.5);
+ static const double slope = (max_volts-min_volts)/tvrx2_gain_ranges["IF"].stop();
+
+ //calculate the voltage for the aux dac
+ double dac_volts = gain*slope + min_volts;
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 IF 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;
+}
+
+double tvrx2::set_gain(double gain, const std::string &name){
+ assert_has(tvrx2_gain_ranges.keys(), name, "tvrx2 gain name");
+
+ if (name == "IF"){
+ //write voltage to aux_dac
+ this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, tvrx2_sd_name_to_dac[get_subdev_name()], gain_to_if_gain_dac(gain));
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+
+ //shadow gain setting
+ _gains[name] = gain;
+
+ return gain;
+}
+
+/***********************************************************************
+ * Bandwidth Handling
+ **********************************************************************/
+static tda18272hnm_regs_t::lp_fc_t bandwidth_to_lp_fc_reg(double &bandwidth){
+ int reg = uhd::clip(boost::math::iround((bandwidth-5.0e6)/1.0e6), 0, 4);
+
+ switch(reg){
+ case 0:
+ bandwidth = 1.7e6;
+ return tda18272hnm_regs_t::LP_FC_1_7MHZ;
+ case 1:
+ bandwidth = 6e6;
+ return tda18272hnm_regs_t::LP_FC_6_0MHZ;
+ case 2:
+ bandwidth = 7e6;
+ return tda18272hnm_regs_t::LP_FC_7_0MHZ;
+ case 3:
+ bandwidth = 8e6;
+ return tda18272hnm_regs_t::LP_FC_8_0MHZ;
+ case 4:
+ bandwidth = 10e6;
+ return tda18272hnm_regs_t::LP_FC_10_0MHZ;
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+double tvrx2::set_bandwidth(double bandwidth){
+ //clip the input
+ bandwidth = tvrx2_bandwidth_range.clip(bandwidth);
+
+ //compute low pass cutoff frequency setting
+ _tda18272hnm_regs.lp_fc = bandwidth_to_lp_fc_reg(bandwidth);
+
+ //shadow bandwidth setting
+ _bandwidth = bandwidth;
+
+ //update register
+ send_reg(0x13, 0x13);
+
+ UHD_LOGV(often) << boost::format(
+ "TVRX2 (%s) Bandwidth (lp_fc): %f Hz, reg: %d"
+ ) % (get_subdev_name()) % _bandwidth % (int(_tda18272hnm_regs.lp_fc)) << std::endl;
+
+ return _bandwidth;
+}
diff --git a/host/lib/usrp/dboard/db_unknown.cpp b/host/lib/usrp/dboard/db_unknown.cpp
new file mode 100644
index 000000000..2ed50cd91
--- /dev/null
+++ b/host/lib/usrp/dboard/db_unknown.cpp
@@ -0,0 +1,160 @@
+//
+// Copyright 2010-2011 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/types/ranges.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/msg.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/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <vector>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+/***********************************************************************
+ * Utility functions
+ **********************************************************************/
+static void warn_if_old_rfx(const dboard_id_t &dboard_id, const std::string &xx){
+ typedef boost::tuple<std::string, dboard_id_t, dboard_id_t> old_ids_t; //name, rx_id, tx_id
+ static const std::vector<old_ids_t> old_rfx_ids = list_of
+ (old_ids_t("Flex 400 Classic", 0x0004, 0x0008))
+ (old_ids_t("Flex 900 Classic", 0x0005, 0x0009))
+ (old_ids_t("Flex 1200 Classic", 0x0006, 0x000a))
+ (old_ids_t("Flex 1800 Classic", 0x0030, 0x0031))
+ (old_ids_t("Flex 2400 Classic", 0x0007, 0x000b))
+ ;
+ BOOST_FOREACH(const old_ids_t &old_id, old_rfx_ids){
+ std::string name; dboard_id_t rx_id, tx_id;
+ boost::tie(name, rx_id, tx_id) = old_id;
+ if (
+ (xx == "RX" and rx_id == dboard_id) or
+ (xx == "TX" and tx_id == dboard_id)
+ ) UHD_MSG(warning) << boost::format(
+ "Detected %s daughterboard %s\n"
+ "This board requires modification to use.\n"
+ "See the daughterboard application notes.\n"
+ ) % xx % name;
+ }
+}
+
+/***********************************************************************
+ * 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);
+};
+
+class unknown_tx : public tx_dboard_base{
+public:
+ unknown_tx(ctor_args_t args);
+};
+
+/***********************************************************************
+ * 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){
+ warn_if_old_rfx(this->get_rx_id(), "RX");
+
+ ////////////////////////////////////////////////////////////////////
+ // Register properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name").set(
+ std::string(str(boost::format("%s - %s")
+ % get_rx_id().to_pp_string()
+ % get_subdev_name()
+ )));
+ this->get_rx_subtree()->create<int>("gains"); //phony property so this dir exists
+ this->get_rx_subtree()->create<double>("freq/value")
+ .set(double(0.0));
+ this->get_rx_subtree()->create<meta_range_t>("freq/range")
+ .set(freq_range_t(double(0.0), double(0.0)));
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .set("");
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(list_of(""));
+ this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists
+ this->get_rx_subtree()->create<std::string>("connection")
+ .set("IQ");
+ this->get_rx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value")
+ .set(double(0.0));
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(0.0, 0.0));
+}
+
+/***********************************************************************
+ * Unknown TX dboard
+ **********************************************************************/
+unknown_tx::unknown_tx(ctor_args_t args) : tx_dboard_base(args){
+ warn_if_old_rfx(this->get_tx_id(), "TX");
+
+ ////////////////////////////////////////////////////////////////////
+ // Register properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_tx_subtree()->create<std::string>("name").set(
+ std::string(str(boost::format("%s - %s")
+ % get_tx_id().to_pp_string()
+ % get_subdev_name()
+ )));
+ this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists
+ this->get_tx_subtree()->create<double>("freq/value")
+ .set(double(0.0));
+ this->get_tx_subtree()->create<meta_range_t>("freq/range")
+ .set(freq_range_t(double(0.0), double(0.0)));
+ this->get_tx_subtree()->create<std::string>("antenna/value")
+ .set("");
+ this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(list_of(""));
+ this->get_tx_subtree()->create<int>("sensors"); //phony property so this dir exists
+ this->get_tx_subtree()->create<std::string>("connection")
+ .set("IQ");
+ this->get_tx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_tx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_tx_subtree()->create<double>("bandwidth/value")
+ .set(double(0.0));
+ this->get_tx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(0.0, 0.0));
+}
diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp
new file mode 100644
index 000000000..503e5aabf
--- /dev/null
+++ b/host/lib/usrp/dboard/db_wbx_common.cpp
@@ -0,0 +1,151 @@
+//
+// Copyright 2011-2012 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 "db_wbx_common.hpp"
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/msg.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+
+/***********************************************************************
+ * Gain-related functions
+ **********************************************************************/
+static int rx_pga0_gain_to_iobits(double &gain){
+ //clip the input
+ gain = wbx_rx_gain_ranges["PGA0"].clip(gain);
+
+ //convert to attenuation
+ double attn = wbx_rx_gain_ranges["PGA0"].stop() - gain;
+
+ //calculate the attenuation
+ int attn_code = boost::math::iround(attn*2);
+ int iobits = ((~attn_code) << RX_ATTN_SHIFT) & RX_ATTN_MASK;
+
+ UHD_LOGV(often) << boost::format(
+ "WBX RX 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"].stop() - double(attn_code)/2;
+
+ return iobits;
+}
+
+
+/***********************************************************************
+ * WBX Common Implementation
+ **********************************************************************/
+wbx_base::wbx_base(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);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register RX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX));
+ BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){
+ this->get_rx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&wbx_base::set_rx_gain, this, _1, name))
+ .set(wbx_rx_gain_ranges[name].start());
+ this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(wbx_rx_gain_ranges[name]);
+ }
+ this->get_rx_subtree()->create<std::string>("connection").set("IQ");
+ this->get_rx_subtree()->create<bool>("enabled")
+ .subscribe(boost::bind(&wbx_base::set_rx_enabled, this, _1))
+ .set(true); //start enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset").set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(2*20.0e6, 2*20.0e6));
+
+ ////////////////////////////////////////////////////////////////////
+ // Register TX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX));
+ this->get_tx_subtree()->create<std::string>("connection").set("IQ");
+ this->get_tx_subtree()->create<bool>("use_lo_offset").set(false);
+ this->get_tx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided
+ this->get_tx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(2*20.0e6, 2*20.0e6));
+
+ // instantiate subclass foo
+ switch(get_rx_id().to_uint16()) {
+ case 0x053:
+ db_actual = wbx_versionx_sptr(new wbx_version2(this));
+ return;
+ case 0x057:
+ db_actual = wbx_versionx_sptr(new wbx_version3(this));
+ return;
+ case 0x063:
+ db_actual = wbx_versionx_sptr(new wbx_version4(this));
+ return;
+ default:
+ /* We didn't recognize the version of the board... */
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+}
+
+
+wbx_base::~wbx_base(void){
+ /* NOP */
+}
+
+/***********************************************************************
+ * Enables
+ **********************************************************************/
+void wbx_base::set_rx_enabled(bool enb){
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX,
+ (enb)? RX_POWER_UP : RX_POWER_DOWN, RX_POWER_UP | RX_POWER_DOWN
+ );
+}
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+double wbx_base::set_rx_gain(double gain, const std::string &name){
+ assert_has(wbx_rx_gain_ranges.keys(), name, "wbx rx gain name");
+ if(name == "PGA0"){
+ boost::uint16_t io_bits = rx_pga0_gain_to_iobits(gain);
+ _rx_gains[name] = gain;
+
+ //write the new gain to rx gpio outputs
+ this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, io_bits, RX_ATTN_MASK);
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ return _rx_gains[name]; //returned shadowed
+}
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+sensor_value_t wbx_base::get_locked(dboard_iface::unit_t unit){
+ const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0;
+ return sensor_value_t("LO", locked, "locked", "unlocked");
+}
diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp
new file mode 100644
index 000000000..9e984dce7
--- /dev/null
+++ b/host/lib/usrp/dboard/db_wbx_common.hpp
@@ -0,0 +1,204 @@
+//
+// Copyright 2011 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP
+#define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP
+
+// Common IO Pins
+#define ADF4350_CE (1 << 3)
+#define ADF4350_PDBRF (1 << 2)
+#define ADF4350_MUXOUT (1 << 1) // INPUT!!!
+#define ADF4351_CE (1 << 3)
+#define ADF4351_PDBRF (1 << 2)
+#define ADF4351_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
+
+// TX Attenuator Pins (v3 only)
+#define TX_ATTN_16 (1 << 14)
+#define TX_ATTN_8 (1 << 5)
+#define TX_ATTN_4 (1 << 4)
+#define TX_ATTN_2 (1 << 3)
+#define TX_ATTN_1 (1 << 1)
+#define TX_ATTN_MASK (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1) // valid bits of TX Attenuator Control
+
+// Mixer functions
+#define TX_MIXER_ENB (TXMOD_EN|ADF4350_PDBRF) // for v3, TXMOD_EN tied to ADF4350_PDBRF rather than separate
+#define TX_MIXER_DIS 0
+
+#define RX_MIXER_ENB (RXBB_PDB|ADF4350_PDBRF)
+#define RX_MIXER_DIS 0
+
+// Power functions
+#define TX_POWER_UP (TX_PUP_5V|TX_PUP_3V) // high enables power supply
+#define TX_POWER_DOWN 0
+
+#define RX_POWER_UP (RX_PUP_5V|RX_PUP_3V|ADF4350_CE) // high enables power supply
+#define RX_POWER_DOWN 0
+
+
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/format.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <boost/bind.hpp>
+
+namespace uhd{ namespace usrp{
+
+
+/***********************************************************************
+ * The WBX Common dboard constants
+ **********************************************************************/
+static const uhd::dict<std::string, gain_range_t> wbx_rx_gain_ranges = boost::assign::map_list_of
+ ("PGA0", gain_range_t(0, 31.5, 0.5));
+
+
+/***********************************************************************
+ * The WBX dboard base class
+ **********************************************************************/
+class wbx_base : public xcvr_dboard_base{
+public:
+ wbx_base(ctor_args_t args);
+ virtual ~wbx_base(void);
+
+protected:
+ virtual double set_rx_gain(double gain, const std::string &name);
+
+ virtual void set_rx_enabled(bool enb);
+
+ /*!
+ * Get the lock detect status of the LO.
+ *
+ * This operation is identical for all versions of the WBX board.
+ * \param unit which unit rx or tx
+ * \return true for locked
+ */
+ virtual sensor_value_t get_locked(dboard_iface::unit_t unit);
+
+ /*!
+ * Version-agnostic ABC that wraps version-specific implementations of the
+ * WBX base daughterboard.
+ *
+ * This class is an abstract base class, and thus is impossible to
+ * instantiate.
+ */
+ class wbx_versionx {
+ public:
+ wbx_versionx() {}
+ ~wbx_versionx(void) {}
+
+ virtual double set_tx_gain(double gain, const std::string &name) = 0;
+ virtual void set_tx_enabled(bool enb) = 0;
+ virtual double set_lo_freq(dboard_iface::unit_t unit, double target_freq) = 0;
+
+ /*! This is the registered instance of the wrapper class, wbx_base. */
+ wbx_base *self_base;
+
+ property_tree::sptr get_rx_subtree(void){
+ return self_base->get_rx_subtree();
+ }
+
+ property_tree::sptr get_tx_subtree(void){
+ return self_base->get_tx_subtree();
+ }
+ };
+
+
+ /*!
+ * Version 2 of the WBX Daughterboard
+ *
+ * Basically the original release of the DB.
+ */
+ class wbx_version2 : public wbx_versionx {
+ public:
+ wbx_version2(wbx_base *_self_wbx_base);
+ ~wbx_version2(void);
+
+ double set_tx_gain(double gain, const std::string &name);
+ void set_tx_enabled(bool enb);
+ double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
+ };
+
+ /*!
+ * Version 3 of the WBX Daughterboard
+ *
+ * Fixed a problem with the AGC from Version 2.
+ */
+ class wbx_version3 : public wbx_versionx {
+ public:
+ wbx_version3(wbx_base *_self_wbx_base);
+ ~wbx_version3(void);
+
+ double set_tx_gain(double gain, const std::string &name);
+ void set_tx_enabled(bool enb);
+ double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
+ };
+
+ /*!
+ * Version 4 of the WBX Daughterboard
+ *
+ * Upgrades the Frequnecy Synthensizer from ADF4350 to ADF4351.
+ */
+ class wbx_version4 : public wbx_versionx {
+ public:
+ wbx_version4(wbx_base *_self_wbx_base);
+ ~wbx_version4(void);
+
+ double set_tx_gain(double gain, const std::string &name);
+ void set_tx_enabled(bool enb);
+ double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
+ };
+
+ /*!
+ * Handle to the version-specific implementation of the WBX.
+ *
+ * Since many of this class's functions are dependent on the version of the
+ * WBX board, this class will instantiate an object of the appropriate
+ * wbx_version_* subclass, and invoke any relevant functions through that
+ * object. This pointer is set to the proper object at construction time.
+ */
+ typedef boost::shared_ptr<wbx_versionx> wbx_versionx_sptr;
+ wbx_versionx_sptr db_actual;
+
+ uhd::dict<std::string, double> _tx_gains, _rx_gains;
+ bool _rx_enabled, _tx_enabled;
+};
+
+
+}} //namespace uhd::usrp
+
+#endif /* INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP */
diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp
new file mode 100644
index 000000000..4ba30255d
--- /dev/null
+++ b/host/lib/usrp/dboard/db_wbx_simple.cpp
@@ -0,0 +1,162 @@
+//
+// Copyright 2011-2012 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/>.
+//
+
+// Antenna constants
+#define ANTSW_IO ((1 << 15)) // on UNIT_TX, 0 = TX, 1 = RX, on UNIT_RX 0 = main ant, 1 = RX2
+#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
+
+#include "db_wbx_common.hpp"
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <boost/assign/list_of.hpp>
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace boost::assign;
+
+
+/***********************************************************************
+ * The WBX Simple dboard constants
+ **********************************************************************/
+static const std::vector<std::string> wbx_tx_antennas = list_of("TX/RX")("CAL");
+
+static const std::vector<std::string> wbx_rx_antennas = list_of("TX/RX")("RX2")("CAL");
+
+/***********************************************************************
+ * The WBX simple implementation
+ **********************************************************************/
+class wbx_simple : public wbx_base{
+public:
+ wbx_simple(ctor_args_t args);
+ ~wbx_simple(void);
+
+private:
+ void set_rx_ant(const std::string &ant);
+ void set_tx_ant(const std::string &ant);
+ std::string _rx_ant;
+};
+
+/***********************************************************************
+ * Register the WBX simple implementation
+ **********************************************************************/
+static dboard_base::sptr make_wbx_simple(dboard_base::ctor_args_t args){
+ return dboard_base::sptr(new wbx_simple(args));
+}
+
+/***********************************************************************
+ * ID Numbers for WBX daughterboard combinations.
+ **********************************************************************/
+UHD_STATIC_BLOCK(reg_wbx_simple_dboards){
+ dboard_manager::register_dboard(0x0053, 0x0052, &make_wbx_simple, "WBX");
+ dboard_manager::register_dboard(0x0053, 0x004f, &make_wbx_simple, "WBX + Simple GDB");
+ dboard_manager::register_dboard(0x0057, 0x0056, &make_wbx_simple, "WBX v3");
+ dboard_manager::register_dboard(0x0057, 0x004f, &make_wbx_simple, "WBX v3 + Simple GDB");
+ dboard_manager::register_dboard(0x0063, 0x0062, &make_wbx_simple, "WBX v4");
+ dboard_manager::register_dboard(0x0063, 0x004f, &make_wbx_simple, "WBX v4 + Simple GDB");
+}
+
+/***********************************************************************
+ * Structors
+ **********************************************************************/
+wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){
+
+ ////////////////////////////////////////////////////////////////////
+ // Register RX properties
+ ////////////////////////////////////////////////////////////////////
+
+ this->get_rx_subtree()->access<std::string>("name").set(
+ std::string(str(boost::format("%s+GDB") % this->get_rx_subtree()->access<std::string>("name").get()
+ )));
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .subscribe(boost::bind(&wbx_simple::set_rx_ant, this, _1))
+ .set("RX2");
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(wbx_rx_antennas);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register TX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_tx_subtree()->access<std::string>("name").set(
+ std::string(str(boost::format("%s+GDB") % this->get_tx_subtree()->access<std::string>("name").get()
+ )));
+ this->get_tx_subtree()->create<std::string>("antenna/value")
+ .subscribe(boost::bind(&wbx_simple::set_tx_ant, this, _1))
+ .set(wbx_tx_antennas.at(0));
+ this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(wbx_tx_antennas);
+
+ //set the gpio directions and atr controls (antenna switches all under ATR)
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, ANTSW_IO, ANTSW_IO);
+ this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, ANTSW_IO, ANTSW_IO);
+ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO);
+
+ //setup ATR for the antenna switches (constant)
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, ANT_RX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO);
+
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO);
+}
+
+wbx_simple::~wbx_simple(void){
+ /* NOP */
+}
+
+/***********************************************************************
+ * Antennas
+ **********************************************************************/
+void wbx_simple::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
+ if (_rx_ant == "CAL") {
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO);
+ }
+ else {
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO);
+ }
+}
+
+void wbx_simple::set_tx_ant(const std::string &ant){
+ assert_has(wbx_tx_antennas, ant, "wbx tx antenna name");
+
+ //write the new antenna setting to atr regs
+ if (ant == "CAL") {
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO);
+ }
+ else {
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO);
+ }
+}
diff --git a/host/lib/usrp/dboard/db_wbx_version2.cpp b/host/lib/usrp/dboard/db_wbx_version2.cpp
new file mode 100644
index 000000000..5f6118a91
--- /dev/null
+++ b/host/lib/usrp/dboard/db_wbx_version2.cpp
@@ -0,0 +1,343 @@
+//
+// Copyright 2011-2012 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 "db_wbx_common.hpp"
+#include "adf4350_regs.hpp"
+#include <uhd/utils/log.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/usrp/dboard_base.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;
+
+
+/***********************************************************************
+ * WBX Version 2 Constants
+ **********************************************************************/
+static const uhd::dict<std::string, gain_range_t> wbx_v2_tx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 25, 0.05))
+;
+
+static const freq_range_t wbx_v2_freq_range(68.75e6, 2.2e9);
+
+/***********************************************************************
+ * Gain-related functions
+ **********************************************************************/
+static double tx_pga0_gain_to_dac_volts(double &gain){
+ //clip the input
+ gain = wbx_v2_tx_gain_ranges["PGA0"].clip(gain);
+
+ //voltage level constants
+ static const double max_volts = 0.5, min_volts = 1.4;
+ static const double slope = (max_volts-min_volts)/wbx_v2_tx_gain_ranges["PGA0"].stop();
+
+ //calculate the voltage for the aux dac
+ double dac_volts = gain*slope + min_volts;
+
+ UHD_LOGV(often) << 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;
+}
+
+
+/***********************************************************************
+ * WBX Version 2 Implementation
+ **********************************************************************/
+wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) {
+ //register our handle on the primary wbx_base instance
+ self_base = _self_wbx_base;
+
+ ////////////////////////////////////////////////////////////////////
+ // Register RX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name").set("WBXv2 RX");
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0);
+ this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register TX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_tx_subtree()->create<std::string>("name").set("WBXv2 TX");
+ BOOST_FOREACH(const std::string &name, wbx_v2_tx_gain_ranges.keys()){
+ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name))
+ .set(wbx_v2_tx_gain_ranges[name].start());
+ self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(wbx_v2_tx_gain_ranges[name]);
+ }
+ this->get_tx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0);
+ this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range);
+ this->get_tx_subtree()->create<bool>("enabled")
+ .subscribe(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1))
+ .set(true); //start enabled
+
+ //set attenuator control bits
+ int v2_iobits = ADF4350_CE;
+ int v2_tx_mod = TXMOD_EN|ADF4350_PDBRF;
+
+ //set the gpio directions and atr controls
+ self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v2_tx_mod);
+ self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4350_PDBRF);
+ self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v2_tx_mod|v2_iobits);
+ self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK);
+
+ //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts)
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
+
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+}
+
+wbx_base::wbx_version2::~wbx_version2(void){
+ /* NOP */
+}
+
+/***********************************************************************
+ * Enables
+ **********************************************************************/
+void wbx_base::wbx_version2::set_tx_enabled(bool enb){
+ self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX,
+ (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | ADF4350_CE);
+}
+
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+double wbx_base::wbx_version2::set_tx_gain(double gain, const std::string &name){
+ assert_has(wbx_v2_tx_gain_ranges.keys(), name, "wbx tx gain name");
+ if(name == "PGA0"){
+ double dac_volts = tx_pga0_gain_to_dac_volts(gain);
+ self_base->_tx_gains[name] = gain;
+
+ //write the new voltage to the aux dac
+ self_base->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts);
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ return self_base->_tx_gains[name]; //shadowed
+}
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double target_freq) {
+ //clip to tuning range
+ target_freq = wbx_v2_freq_range.clip(target_freq);
+
+ UHD_LOGV(often) << boost::format(
+ "WBX tune: target frequency %f Mhz"
+ ) % (target_freq/1e6) << std::endl;
+
+ //start with target_freq*2 because mixer has divide by 2
+ target_freq *= 2;
+
+ //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 = self_base->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
+ const bool do_sync = (target_freq/2 > ref_freq);
+ double vco_freq = target_freq;
+ while (vco_freq < 2.2e9) {
+ vco_freq *= 2;
+ RFdiv *= 2;
+ }
+ if (do_sync) vco_freq = target_freq;
+
+ //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 exits 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)))/2/(vco_freq/target_freq));
+
+ UHD_LOGV(often)
+ << 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
+
+ << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d"
+ ) % R % BS % N % FRAC % MOD % T % D % RFdiv << 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;
+ if (do_sync)
+ {
+ regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD)));
+ regs.feedback_select = adf4350_regs_t::FEEDBACK_SELECT_DIVIDED;
+ regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE;
+ }
+ 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];
+
+ if (unit == dboard_iface::UNIT_RX) {
+ freq_range_t rx_lo_5dbm = list_of
+ (range_t(0.05e9, 1.4e9))
+ ;
+
+ freq_range_t rx_lo_2dbm = list_of
+ (range_t(1.4e9, 2.2e9))
+ ;
+
+ if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM;
+
+ if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM;
+
+ } else if (unit == dboard_iface::UNIT_TX) {
+ freq_range_t tx_lo_5dbm = list_of
+ (range_t(0.05e9, 1.7e9))
+ (range_t(1.9e9, 2.2e9))
+ ;
+
+ freq_range_t tx_lo_m1dbm = list_of
+ (range_t(1.7e9, 1.9e9))
+ ;
+
+ if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM;
+
+ if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM;
+
+ }
+
+ //reset the N and R counter
+ regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED;
+ self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
+ regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED;
+
+ //write the registers
+ //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
+ int addr;
+
+ for(addr=5; addr>=0; addr--){
+ UHD_LOGV(often) << boost::format(
+ "WBX SPI Reg (0x%02x): 0x%08x"
+ ) % addr % regs.get_reg(addr) << std::endl;
+ self_base->get_iface()->write_spi(
+ unit, spi_config_t::EDGE_RISE,
+ regs.get_reg(addr), 32
+ );
+ }
+
+ //return the actual frequency
+ UHD_LOGV(often) << boost::format(
+ "WBX tune: actual frequency %f Mhz"
+ ) % (actual_freq/1e6) << std::endl;
+ return actual_freq;
+}
diff --git a/host/lib/usrp/dboard/db_wbx_version3.cpp b/host/lib/usrp/dboard/db_wbx_version3.cpp
new file mode 100644
index 000000000..3e8fc8095
--- /dev/null
+++ b/host/lib/usrp/dboard/db_wbx_version3.cpp
@@ -0,0 +1,375 @@
+//
+// Copyright 2011-2012 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 "db_wbx_common.hpp"
+#include "adf4350_regs.hpp"
+#include <uhd/utils/log.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/usrp/dboard_base.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;
+
+
+/***********************************************************************
+ * WBX Version 3 Constants
+ **********************************************************************/
+static const uhd::dict<std::string, gain_range_t> wbx_v3_tx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 31, 1.0))
+;
+
+static const freq_range_t wbx_v3_freq_range(68.75e6, 2.2e9);
+
+/***********************************************************************
+ * Gain-related functions
+ **********************************************************************/
+static int tx_pga0_gain_to_iobits(double &gain){
+ //clip the input
+ gain = wbx_v3_tx_gain_ranges["PGA0"].clip(gain);
+
+ //convert to attenuation
+ double attn = wbx_v3_tx_gain_ranges["PGA0"].stop() - gain;
+
+ //calculate the attenuation
+ int attn_code = boost::math::iround(attn);
+ int iobits = (
+ (attn_code & 16 ? 0 : TX_ATTN_16) |
+ (attn_code & 8 ? 0 : TX_ATTN_8) |
+ (attn_code & 4 ? 0 : TX_ATTN_4) |
+ (attn_code & 2 ? 0 : TX_ATTN_2) |
+ (attn_code & 1 ? 0 : TX_ATTN_1)
+ ) & TX_ATTN_MASK;
+
+ UHD_LOGV(often) << boost::format(
+ "WBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x"
+ ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl;
+
+ //the actual gain setting
+ gain = wbx_v3_tx_gain_ranges["PGA0"].stop() - double(attn_code);
+
+ return iobits;
+}
+
+
+/***********************************************************************
+ * WBX Common Implementation
+ **********************************************************************/
+wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) {
+ //register our handle on the primary wbx_base instance
+ self_base = _self_wbx_base;
+
+ ////////////////////////////////////////////////////////////////////
+ // Register RX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name").set("WBXv3 RX");
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0);
+ this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register TX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_tx_subtree()->create<std::string>("name").set("WBXv3 TX");
+ BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){
+ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name))
+ .set(wbx_v3_tx_gain_ranges[name].start());
+ self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(wbx_v3_tx_gain_ranges[name]);
+ }
+ this->get_tx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0);
+ this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range);
+ this->get_tx_subtree()->create<bool>("enabled")
+ .subscribe(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1))
+ .set(true); //start enabled
+
+ //set attenuator control bits
+ int v3_iobits = TX_ATTN_MASK;
+ int v3_tx_mod = ADF4350_PDBRF;
+
+ //set the gpio directions and atr controls
+ self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, \
+ v3_tx_mod|v3_iobits);
+ self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, \
+ RXBB_PDB|ADF4350_PDBRF);
+ self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, \
+ TX_PUP_5V|TX_PUP_3V|v3_tx_mod|v3_iobits);
+ self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, \
+ RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK);
+
+ //setup ATR for the mixer enables (always enabled to prevent phase
+ //slip between bursts). set TX gain iobits to min gain (max attenuation)
+ //when RX_ONLY or IDLE to suppress LO leakage
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
+ dboard_iface::ATR_REG_IDLE, v3_tx_mod, \
+ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
+ dboard_iface::ATR_REG_RX_ONLY, v3_tx_mod, \
+ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
+ dboard_iface::ATR_REG_TX_ONLY, v3_tx_mod, \
+ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
+ dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, \
+ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod);
+
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
+ dboard_iface::ATR_REG_IDLE, \
+ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
+ dboard_iface::ATR_REG_TX_ONLY, \
+ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
+ dboard_iface::ATR_REG_RX_ONLY, \
+ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
+ dboard_iface::ATR_REG_FULL_DUPLEX, \
+ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+}
+
+wbx_base::wbx_version3::~wbx_version3(void){
+ /* NOP */
+}
+
+
+/***********************************************************************
+ * Enables
+ **********************************************************************/
+void wbx_base::wbx_version3::set_tx_enabled(bool enb){
+ self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX,
+ (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0);
+}
+
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+double wbx_base::wbx_version3::set_tx_gain(double gain, const std::string &name){
+ assert_has(wbx_v3_tx_gain_ranges.keys(), name, "wbx tx gain name");
+ if(name == "PGA0"){
+ boost::uint16_t io_bits = tx_pga0_gain_to_iobits(gain);
+
+ self_base->_tx_gains[name] = gain;
+
+ //write the new gain to tx gpio outputs
+ //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK);
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ return self_base->_tx_gains[name]; //shadow
+}
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double target_freq) {
+ //clip to tuning range
+ target_freq = wbx_v3_freq_range.clip(target_freq);
+
+ UHD_LOGV(often) << boost::format(
+ "WBX tune: target frequency %f Mhz"
+ ) % (target_freq/1e6) << std::endl;
+
+ //start with target_freq*2 because mixer has divide by 2
+ target_freq *= 2;
+
+ //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 = self_base->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
+ const bool do_sync = (target_freq/2 > ref_freq);
+ double vco_freq = target_freq;
+ while (vco_freq < 2.2e9) {
+ vco_freq *= 2;
+ RFdiv *= 2;
+ }
+ if (do_sync) vco_freq = target_freq;
+
+ //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 exits 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)))/2/(vco_freq/target_freq));
+
+ UHD_LOGV(often)
+ << 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
+
+ << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d"
+ ) % R % BS % N % FRAC % MOD % T % D % RFdiv << 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;
+ if (do_sync)
+ {
+ regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD)));
+ regs.feedback_select = adf4350_regs_t::FEEDBACK_SELECT_DIVIDED;
+ regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE;
+ }
+ 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];
+
+ if (unit == dboard_iface::UNIT_RX) {
+ freq_range_t rx_lo_5dbm = list_of
+ (range_t(0.05e9, 1.4e9))
+ ;
+
+ freq_range_t rx_lo_2dbm = list_of
+ (range_t(1.4e9, 2.2e9))
+ ;
+
+ if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM;
+
+ if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM;
+
+ } else if (unit == dboard_iface::UNIT_TX) {
+ freq_range_t tx_lo_5dbm = list_of
+ (range_t(0.05e9, 1.7e9))
+ (range_t(1.9e9, 2.2e9))
+ ;
+
+ freq_range_t tx_lo_m1dbm = list_of
+ (range_t(1.7e9, 1.9e9))
+ ;
+
+ if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM;
+
+ if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM;
+
+ }
+
+ //reset the N and R counter
+ regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED;
+ self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
+ regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED;
+
+ //write the registers
+ //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
+ int addr;
+
+ for(addr=5; addr>=0; addr--){
+ UHD_LOGV(often) << boost::format(
+ "WBX SPI Reg (0x%02x): 0x%08x"
+ ) % addr % regs.get_reg(addr) << std::endl;
+ self_base->get_iface()->write_spi(
+ unit, spi_config_t::EDGE_RISE,
+ regs.get_reg(addr), 32
+ );
+ }
+
+ //return the actual frequency
+ UHD_LOGV(often) << boost::format(
+ "WBX tune: actual frequency %f Mhz"
+ ) % (actual_freq/1e6) << std::endl;
+ return actual_freq;
+}
diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp
new file mode 100644
index 000000000..58a5e5f3e
--- /dev/null
+++ b/host/lib/usrp/dboard/db_wbx_version4.cpp
@@ -0,0 +1,358 @@
+//
+// Copyright 2011-2012 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 "db_wbx_common.hpp"
+#include "adf4351_regs.hpp"
+#include <uhd/utils/log.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/usrp/dboard_base.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;
+
+
+/***********************************************************************
+ * WBX Version 3 Constants
+ **********************************************************************/
+static const uhd::dict<std::string, gain_range_t> wbx_v4_tx_gain_ranges = map_list_of
+ ("PGA0", gain_range_t(0, 31, 1.0))
+;
+
+static const freq_range_t wbx_v4_freq_range(50.0e6, 2.2e9);
+
+
+/***********************************************************************
+ * Gain-related functions
+ **********************************************************************/
+static int tx_pga0_gain_to_iobits(double &gain){
+ //clip the input
+ gain = wbx_v4_tx_gain_ranges["PGA0"].clip(gain);
+
+ //convert to attenuation
+ double attn = wbx_v4_tx_gain_ranges["PGA0"].stop() - gain;
+
+ //calculate the attenuation
+ int attn_code = boost::math::iround(attn);
+ int iobits = (
+ (attn_code & 16 ? 0 : TX_ATTN_16) |
+ (attn_code & 8 ? 0 : TX_ATTN_8) |
+ (attn_code & 4 ? 0 : TX_ATTN_4) |
+ (attn_code & 2 ? 0 : TX_ATTN_2) |
+ (attn_code & 1 ? 0 : TX_ATTN_1)
+ ) & TX_ATTN_MASK;
+
+ UHD_LOGV(often) << boost::format(
+ "WBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x"
+ ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl;
+
+ //the actual gain setting
+ gain = wbx_v4_tx_gain_ranges["PGA0"].stop() - double(attn_code);
+
+ return iobits;
+}
+
+
+/***********************************************************************
+ * WBX Common Implementation
+ **********************************************************************/
+wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) {
+ //register our handle on the primary wbx_base instance
+ self_base = _self_wbx_base;
+
+ ////////////////////////////////////////////////////////////////////
+ // Register RX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name").set("WBXv4 RX");
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0);
+ this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register TX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_tx_subtree()->create<std::string>("name").set("WBXv4 TX");
+ BOOST_FOREACH(const std::string &name, wbx_v4_tx_gain_ranges.keys()){
+ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name))
+ .set(wbx_v4_tx_gain_ranges[name].start());
+ self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(wbx_v4_tx_gain_ranges[name]);
+ }
+ this->get_tx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0);
+ this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range);
+ this->get_tx_subtree()->create<bool>("enabled")
+ .subscribe(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1))
+ .set(true); //start enabled
+
+ //set attenuator control bits
+ int v4_iobits = TX_ATTN_MASK;
+ int v4_tx_mod = ADF4351_PDBRF;
+
+ //set the gpio directions and atr controls
+ self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v4_tx_mod|v4_iobits);
+ self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4351_PDBRF);
+ self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v4_tx_mod|v4_iobits);
+ self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4351_CE|RXBB_PDB|ADF4351_PDBRF|RX_ATTN_MASK);
+
+ //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts)
+ //set TX gain iobits to min gain (max attenuation) when RX_ONLY or IDLE to suppress LO leakage
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod);
+
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+}
+
+wbx_base::wbx_version4::~wbx_version4(void){
+ /* NOP */
+}
+
+
+/***********************************************************************
+ * Enables
+ **********************************************************************/
+void wbx_base::wbx_version4::set_tx_enabled(bool enb) {
+ self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX,
+ (enb)? TX_POWER_UP | ADF4351_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0);
+}
+
+
+/***********************************************************************
+ * Gain Handling
+ **********************************************************************/
+double wbx_base::wbx_version4::set_tx_gain(double gain, const std::string &name) {
+ assert_has(wbx_v4_tx_gain_ranges.keys(), name, "wbx tx gain name");
+ if(name == "PGA0"){
+ boost::uint16_t io_bits = tx_pga0_gain_to_iobits(gain);
+
+ self_base->_tx_gains[name] = gain;
+
+ //write the new gain to tx gpio outputs
+ //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK);
+
+ }
+ else UHD_THROW_INVALID_CODE_PATH();
+ return self_base->_tx_gains[name];
+}
+
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double target_freq) {
+ //clip to tuning range
+ target_freq = wbx_v4_freq_range.clip(target_freq);
+
+ UHD_LOGV(often) << boost::format(
+ "WBX tune: target frequency %f Mhz"
+ ) % (target_freq/1e6) << std::endl;
+
+ //start with target_freq*2 because mixer has divide by 2
+ target_freq *= 2;
+
+ //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) //adf4351_regs_t::PRESCALER_4_5
+ (1,75) //adf4351_regs_t::PRESCALER_8_9
+ ;
+
+ //map rf divider select output dividers to enums
+ static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
+ (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1)
+ (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2)
+ (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4)
+ (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8)
+ (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16)
+ (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32)
+ (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64)
+ ;
+
+ double actual_freq, pfd_freq;
+ double ref_freq = self_base->get_iface()->get_clock_rate(unit);
+ int R=0, BS=0, N=0, FRAC=0, MOD=0;
+ int RFdiv = 1;
+ adf4351_regs_t::reference_divide_by_2_t T = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
+ adf4351_regs_t::reference_doubler_t D = adf4351_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 = adf4351_regs_t::REFERENCE_DOUBLER_ENABLED;
+
+ //increase RF divider until acceptable VCO frequency
+ const bool do_sync = (target_freq/2 > ref_freq);
+ double vco_freq = target_freq;
+ while (vco_freq < 2.2e9) {
+ vco_freq *= 2;
+ RFdiv *= 2;
+ }
+ if (do_sync) vco_freq = target_freq;
+
+ //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
+ adf4351_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_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 exits 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 = adf4351_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)))/2/(vco_freq/target_freq));
+
+ UHD_LOGV(often)
+ << 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
+
+ << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d"
+ ) % R % BS % N % FRAC % MOD % T % D % RFdiv << 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
+ adf4351_regs_t regs;
+
+ regs.frac_12_bit = FRAC;
+ regs.int_16_bit = N;
+ regs.mod_12_bit = MOD;
+ if (do_sync)
+ {
+ regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD)));
+ regs.feedback_select = adf4351_regs_t::FEEDBACK_SELECT_DIVIDED;
+ regs.clock_div_mode = adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE;
+ }
+ 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];
+
+ if (unit == dboard_iface::UNIT_RX) {
+ freq_range_t rx_lo_5dbm = list_of
+ (range_t(0.05e9, 1.4e9))
+ ;
+
+ freq_range_t rx_lo_2dbm = list_of
+ (range_t(1.4e9, 2.2e9))
+ ;
+
+ if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM;
+
+ if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM;
+
+ } else if (unit == dboard_iface::UNIT_TX) {
+ freq_range_t tx_lo_5dbm = list_of
+ (range_t(0.05e9, 1.7e9))
+ (range_t(1.9e9, 2.2e9))
+ ;
+
+ freq_range_t tx_lo_m1dbm = list_of
+ (range_t(1.7e9, 1.9e9))
+ ;
+
+ if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM;
+
+ if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_M1DBM;
+
+ }
+
+ //reset the N and R counter
+ regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED;
+ self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
+ regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED;
+
+ //write the registers
+ //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
+ int addr;
+
+ for(addr=5; addr>=0; addr--){
+ UHD_LOGV(often) << boost::format(
+ "WBX SPI Reg (0x%02x): 0x%08x"
+ ) % addr % regs.get_reg(addr) << std::endl;
+ self_base->get_iface()->write_spi(
+ unit, spi_config_t::EDGE_RISE,
+ regs.get_reg(addr), 32
+ );
+ }
+
+ //return the actual frequency
+ UHD_LOGV(often) << boost::format(
+ "WBX tune: actual frequency %f Mhz"
+ ) % (actual_freq/1e6) << std::endl;
+ return actual_freq;
+}
diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp
new file mode 100644
index 000000000..348195a6e
--- /dev/null
+++ b/host/lib/usrp/dboard/db_xcvr2450.cpp
@@ -0,0 +1,673 @@
+//
+// Copyright 2010-2012 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 (HB_PA_OFF_TXIO | LB_PA_OFF_TXIO)
+#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/log.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include <uhd/utils/assert_has.hpp>
+#include <uhd/utils/algorithm.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/types/dict.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 freq_range_t xcvr_freq_range = list_of
+ (range_t(2.4e9, 2.5e9))
+ (range_t(4.9e9, 6.0e9))
+;
+
+//Multiplied by 2.0 for conversion to complex bandpass from lowpass
+static const freq_range_t xcvr_tx_bandwidth_range = list_of
+ (range_t(2.0*12e6))
+ (range_t(2.0*18e6))
+ (range_t(2.0*24e6))
+;
+
+//Multiplied by 2.0 for conversion to complex bandpass from lowpass
+static const freq_range_t xcvr_rx_bandwidth_range = list_of
+ (range_t(2.0*0.9*7.5e6, 2.0*1.1*7.5e6))
+ (range_t(2.0*0.9*9.5e6, 2.0*1.1*9.5e6))
+ (range_t(2.0*0.9*14e6, 2.0*1.1*14e6))
+ (range_t(2.0*0.9*18e6, 2.0*1.1*18e6))
+;
+
+static const std::vector<std::string> 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(list_of
+ (range_t(0))
+ (range_t(15))
+ (range_t(30.5))
+ ))
+ ("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);
+
+private:
+ double _lo_freq;
+ double _rx_bandwidth, _tx_bandwidth;
+ uhd::dict<std::string, double> _tx_gains, _rx_gains;
+ std::string _tx_ant, _rx_ant;
+ int _ad9515div;
+ max2829_regs_t _max2829_regs;
+
+ double set_lo_freq(double target_freq);
+ double set_lo_freq_core(double target_freq);
+ void set_tx_ant(const std::string &ant);
+ void set_rx_ant(const std::string &ant);
+ double set_tx_gain(double gain, const std::string &name);
+ double set_rx_gain(double gain, const std::string &name);
+ double set_rx_bandwidth(double bandwidth);
+ double set_tx_bandwidth(double bandwidth);
+
+ void update_atr(void);
+ void spi_reset(void);
+ void send_reg(boost::uint8_t addr){
+ boost::uint32_t value = _max2829_regs.get_reg(addr);
+ UHD_LOGV(often) << 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;}
+
+ /*!
+ * Get the lock detect status of the LO.
+ * \return sensor for locked
+ */
+ sensor_value_t get_locked(void){
+ const bool locked = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & LOCKDET_RXIO) != 0;
+ return sensor_value_t("LO", locked, "locked", "unlocked");
+ }
+
+ /*!
+ * Read the RSSI from the aux adc
+ * \return the rssi sensor in dBm
+ */
+ sensor_value_t get_rssi(void){
+ //*FIXME* RSSI depends on LNA Gain Setting (datasheet pg 16 top middle chart)
+ double max_power = 0.0;
+ switch(_max2829_regs.rx_lna_gain){
+ case 0:
+ case 1: max_power = 0; break;
+ case 2: max_power = -15; break;
+ case 3: max_power = -30.5; break;
+ }
+
+ //constants for the rssi calculation
+ static const double min_v = 2.5, max_v = 0.5;
+ static const double rssi_dyn_range = 60.0;
+ //calculate the rssi from the voltage
+ double voltage = this->get_iface()->read_aux_adc(dboard_iface::UNIT_RX, dboard_iface::AUX_ADC_B);
+ double rssi = max_power - rssi_dyn_range*(voltage - min_v)/(max_v - min_v);
+ return sensor_value_t("RSSI", rssi, "dBm");
+ }
+};
+
+/***********************************************************************
+ * 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){
+ spi_reset(); //prepare the spi
+
+ _rx_bandwidth = 9.5e6;
+ _tx_bandwidth = 12.0e6;
+
+ //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_100;
+ _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);
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ // Register RX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_rx_subtree()->create<std::string>("name")
+ .set("XCVR2450 RX");
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&xcvr2450::get_locked, this));
+ this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi")
+ .publish(boost::bind(&xcvr2450::get_rssi, this));
+ BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){
+ this->get_rx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&xcvr2450::set_rx_gain, this, _1, name))
+ .set(xcvr_rx_gain_ranges[name].start());
+ this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(xcvr_rx_gain_ranges[name]);
+ }
+ this->get_rx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1))
+ .set(double(2.45e9));
+ this->get_rx_subtree()->create<meta_range_t>("freq/range")
+ .set(xcvr_freq_range);
+ this->get_rx_subtree()->create<std::string>("antenna/value")
+ .subscribe(boost::bind(&xcvr2450::set_rx_ant, this, _1))
+ .set(xcvr_antennas.at(0));
+ this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(xcvr_antennas);
+ this->get_rx_subtree()->create<std::string>("connection")
+ .set("IQ");
+ this->get_rx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_rx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_rx_subtree()->create<double>("bandwidth/value")
+ .coerce(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth
+ .set(2.0*_rx_bandwidth); //_rx_bandwidth in lowpass, convert to complex bandpass
+ this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(xcvr_rx_bandwidth_range);
+
+ ////////////////////////////////////////////////////////////////////
+ // Register TX properties
+ ////////////////////////////////////////////////////////////////////
+ this->get_tx_subtree()->create<std::string>("name")
+ .set("XCVR2450 TX");
+ this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .publish(boost::bind(&xcvr2450::get_locked, this));
+ BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){
+ this->get_tx_subtree()->create<double>("gains/"+name+"/value")
+ .coerce(boost::bind(&xcvr2450::set_tx_gain, this, _1, name))
+ .set(xcvr_tx_gain_ranges[name].start());
+ this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
+ .set(xcvr_tx_gain_ranges[name]);
+ }
+ this->get_tx_subtree()->create<double>("freq/value")
+ .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1))
+ .set(double(2.45e9));
+ this->get_tx_subtree()->create<meta_range_t>("freq/range")
+ .set(xcvr_freq_range);
+ this->get_tx_subtree()->create<std::string>("antenna/value")
+ .subscribe(boost::bind(&xcvr2450::set_tx_ant, this, _1))
+ .set(xcvr_antennas.at(1));
+ this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(xcvr_antennas);
+ this->get_tx_subtree()->create<std::string>("connection")
+ .set("QI");
+ this->get_tx_subtree()->create<bool>("enabled")
+ .set(true); //always enabled
+ this->get_tx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ this->get_tx_subtree()->create<double>("bandwidth/value")
+ .coerce(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth
+ .set(2.0*_tx_bandwidth); //_tx_bandwidth in lowpass, convert to complex bandpass
+ this->get_tx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(xcvr_tx_bandwidth_range);
+
+ //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);
+}
+
+xcvr2450::~xcvr2450(void){
+ UHD_SAFE_CALL(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));
+}
+
+/***********************************************************************
+ * Update ATR regs which change with Antenna or Freq
+ **********************************************************************/
+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,
+ //due to the issue that USRP1 will take the value of full duplex for its TXATR.
+ 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_DIS_RXIO);
+}
+
+/***********************************************************************
+ * Tuning
+ **********************************************************************/
+double xcvr2450::set_lo_freq(double target_freq){
+ //tune the LO and sleep a bit for lock
+ //if not locked, try some carrier offsets
+ double actual = 0.0;
+ for (double offset = 0.0; offset <= 3e6; offset+=1e6){
+ actual = this->set_lo_freq_core(target_freq + offset);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(50));
+ if (this->get_locked().to_bool()) break;
+ }
+ return actual;
+}
+
+double xcvr2450::set_lo_freq_core(double target_freq){
+
+ //clip the input to the range
+ target_freq = xcvr_freq_range.clip(target_freq);
+
+ //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);
+
+ UHD_LOGV(often)
+ << 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){
+ UHD_LOGV(often) << "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{
+ UHD_LOGV(often) << "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);
+
+ return _lo_freq;
+}
+
+/***********************************************************************
+ * 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(double &gain){
+ //calculate the register value
+ int reg = uhd::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 = double(reg/2 - 1);
+ else gain = double(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(double &gain){
+ int reg = uhd::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(double &gain){
+ int reg = uhd::clip(boost::math::iround(gain/2.0), 0, 31);
+ gain = double(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(double &gain){
+ int reg = uhd::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;
+}
+
+double xcvr2450::set_tx_gain(double 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;
+
+ return gain;
+}
+
+double xcvr2450::set_rx_gain(double 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;
+
+ return gain;
+}
+
+
+/***********************************************************************
+ * Bandwidth Handling
+ **********************************************************************/
+static max2829_regs_t::tx_lpf_coarse_adj_t bandwidth_to_tx_lpf_coarse_reg(double &bandwidth){
+ int reg = uhd::clip(boost::math::iround((bandwidth-6.0e6)/6.0e6), 1, 3);
+
+ switch(reg){
+ case 1: // bandwidth < 15MHz
+ bandwidth = 12e6;
+ return max2829_regs_t::TX_LPF_COARSE_ADJ_12MHZ;
+ case 2: // 15MHz < bandwidth < 21MHz
+ bandwidth = 18e6;
+ return max2829_regs_t::TX_LPF_COARSE_ADJ_18MHZ;
+ case 3: // bandwidth > 21MHz
+ bandwidth = 24e6;
+ return max2829_regs_t::TX_LPF_COARSE_ADJ_24MHZ;
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+static max2829_regs_t::rx_lpf_fine_adj_t bandwidth_to_rx_lpf_fine_reg(double &bandwidth, double requested_bandwidth){
+ int reg = uhd::clip(boost::math::iround((requested_bandwidth/bandwidth)/0.05), 18, 22);
+
+ switch(reg){
+ case 18: // requested_bandwidth < 92.5%
+ bandwidth = 0.9 * bandwidth;
+ return max2829_regs_t::RX_LPF_FINE_ADJ_90;
+ case 19: // 92.5% < requested_bandwidth < 97.5%
+ bandwidth = 0.95 * bandwidth;
+ return max2829_regs_t::RX_LPF_FINE_ADJ_95;
+ case 20: // 97.5% < requested_bandwidth < 102.5%
+ bandwidth = 1.0 * bandwidth;
+ return max2829_regs_t::RX_LPF_FINE_ADJ_100;
+ case 21: // 102.5% < requested_bandwidth < 107.5%
+ bandwidth = 1.05 * bandwidth;
+ return max2829_regs_t::RX_LPF_FINE_ADJ_105;
+ case 22: // 107.5% < requested_bandwidth
+ bandwidth = 1.1 * bandwidth;
+ return max2829_regs_t::RX_LPF_FINE_ADJ_110;
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+static max2829_regs_t::rx_lpf_coarse_adj_t bandwidth_to_rx_lpf_coarse_reg(double &bandwidth){
+ int reg = uhd::clip(boost::math::iround((bandwidth-7.0e6)/1.0e6), 0, 11);
+
+ switch(reg){
+ case 0: // bandwidth < 7.5MHz
+ case 1: // 7.5MHz < bandwidth < 8.5MHz
+ bandwidth = 7.5e6;
+ return max2829_regs_t::RX_LPF_COARSE_ADJ_7_5MHZ;
+ case 2: // 8.5MHz < bandwidth < 9.5MHz
+ case 3: // 9.5MHz < bandwidth < 10.5MHz
+ case 4: // 10.5MHz < bandwidth < 11.5MHz
+ bandwidth = 9.5e6;
+ return max2829_regs_t::RX_LPF_COARSE_ADJ_9_5MHZ;
+ case 5: // 11.5MHz < bandwidth < 12.5MHz
+ case 6: // 12.5MHz < bandwidth < 13.5MHz
+ case 7: // 13.5MHz < bandwidth < 14.5MHz
+ case 8: // 14.5MHz < bandwidth < 15.5MHz
+ bandwidth = 14e6;
+ return max2829_regs_t::RX_LPF_COARSE_ADJ_14MHZ;
+ case 9: // 15.5MHz < bandwidth < 16.5MHz
+ case 10: // 16.5MHz < bandwidth < 17.5MHz
+ case 11: // 17.5MHz < bandwidth
+ bandwidth = 18e6;
+ return max2829_regs_t::RX_LPF_COARSE_ADJ_18MHZ;
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+double xcvr2450::set_rx_bandwidth(double bandwidth){
+ double requested_bandwidth = bandwidth;
+
+ //convert complex bandpass to lowpass bandwidth
+ bandwidth = bandwidth/2.0;
+
+ //compute coarse low pass cutoff frequency setting
+ _max2829_regs.rx_lpf_coarse_adj = bandwidth_to_rx_lpf_coarse_reg(bandwidth);
+
+ //compute fine low pass cutoff frequency setting
+ _max2829_regs.rx_lpf_fine_adj = bandwidth_to_rx_lpf_fine_reg(bandwidth, requested_bandwidth);
+
+ //shadow bandwidth setting
+ _rx_bandwidth = bandwidth;
+
+ //update register
+ send_reg(0x7);
+
+ UHD_LOGV(often) << boost::format(
+ "XCVR2450 RX Bandwidth (lp_fc): %f Hz, coarse reg: %d, fine reg: %d"
+ ) % _rx_bandwidth % (int(_max2829_regs.rx_lpf_coarse_adj)) % (int(_max2829_regs.rx_lpf_fine_adj)) << std::endl;
+
+ return 2.0*_rx_bandwidth;
+}
+
+double xcvr2450::set_tx_bandwidth(double bandwidth){
+ //convert complex bandpass to lowpass bandwidth
+ bandwidth = bandwidth/2.0;
+
+ //compute coarse low pass cutoff frequency setting
+ _max2829_regs.tx_lpf_coarse_adj = bandwidth_to_tx_lpf_coarse_reg(bandwidth);
+
+ //shadow bandwidth setting
+ _tx_bandwidth = bandwidth;
+
+ //update register
+ send_reg(0x7);
+
+ UHD_LOGV(often) << boost::format(
+ "XCVR2450 TX Bandwidth (lp_fc): %f Hz, coarse reg: %d"
+ ) % _tx_bandwidth % (int(_max2829_regs.tx_lpf_coarse_adj)) << std::endl;
+
+ //convert lowpass back to complex bandpass bandwidth
+ return 2.0*_tx_bandwidth;
+}