diff options
Diffstat (limited to 'host/lib/usrp/dboard')
-rw-r--r-- | host/lib/usrp/dboard/CMakeLists.txt | 40 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_basic_and_lf.cpp | 209 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_dbsrx.cpp | 544 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_dbsrx2.cpp | 379 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_rfx.cpp | 449 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_sbx_common.cpp | 356 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_sbx_common.hpp | 226 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_sbx_version3.cpp | 193 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_sbx_version4.cpp | 196 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_tvrx.cpp | 411 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_tvrx2.cpp | 1844 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_unknown.cpp | 160 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_wbx_common.cpp | 151 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_wbx_common.hpp | 204 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_wbx_simple.cpp | 162 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_wbx_version2.cpp | 343 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_wbx_version3.cpp | 375 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_wbx_version4.cpp | 358 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_xcvr2450.cpp | 673 |
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; +} |