diff options
Diffstat (limited to 'host/lib/usrp')
84 files changed, 18426 insertions, 0 deletions
diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt new file mode 100644 index 000000000..bd26d29a1 --- /dev/null +++ b/host/lib/usrp/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +#This file will be included by cmake, use absolute paths! + +LIBUHD_APPEND_SOURCES( +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard_base.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard_eeprom.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard_id.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard_iface.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard_manager.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dsp_utils.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/mboard_eeprom.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/misc_utils.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/multi_usrp.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/single_usrp.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/subdev_spec.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/tune_helper.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/wrapper_utils.hpp +) + +INCLUDE(${CMAKE_SOURCE_DIR}/lib/usrp/dboard/CMakeLists.txt) +INCLUDE(${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/CMakeLists.txt) +INCLUDE(${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/CMakeLists.txt) +INCLUDE(${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/CMakeLists.txt) diff --git a/host/lib/usrp/README b/host/lib/usrp/README new file mode 100644 index 000000000..344209179 --- /dev/null +++ b/host/lib/usrp/README @@ -0,0 +1,15 @@ +######################################################################## +# lib USRP directories: +######################################################################## + +dboard: +    Daughterboard implementation code for all USRP daughterboards + +usrp1: +    Implementation code for the USB-based USRP Classic motherboard. + +usrp2: +    Implementation code for USRP2, USRP-N200, and USRP-N210. + +usrp_e100: +    Implementation code for USRP-E100. diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt new file mode 100644 index 000000000..79cd42d18 --- /dev/null +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +#This file will be included by cmake, use absolute paths! + +LIBUHD_APPEND_SOURCES( +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_basic_and_lf.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_rfx.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_xcvr2450.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_wbx.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_dbsrx.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_unknown.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_tvrx.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_dbsrx2.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..f771595b6 --- /dev/null +++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp @@ -0,0 +1,329 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * 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); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _max_freq; +}; + +class basic_tx : public tx_dboard_base{ +public: +    basic_tx(ctor_args_t args, double max_freq); +    ~basic_tx(void); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _max_freq; +}; + +static const uhd::dict<std::string, subdev_conn_t> sd_name_to_conn = map_list_of +    ("AB", SUBDEV_CONN_COMPLEX_IQ) +    ("BA", SUBDEV_CONN_COMPLEX_QI) +    ("A",  SUBDEV_CONN_REAL_I) +    ("B",  SUBDEV_CONN_REAL_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; +     +    //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 */ +} + +void basic_rx::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = std::string(str(boost::format("%s - %s") +            % get_rx_id().to_pp_string() +            % get_subdev_name() +        )); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        val = float(0); +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        val = gain_range_t(0, 0, 0); +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_FREQ: +        val = double(0); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(-_max_freq, +_max_freq); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = sd_name_to_conn[get_subdev_name()]; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = true; //there is no LO, so it must be true! +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = subdev_bandwidth_scalar[get_subdev_name()]*_max_freq; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void basic_rx::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        UHD_ASSERT_THROW(val.as<float>() == float(0)); +        return; + +    case SUBDEV_PROP_ANTENNA: +        if (val.as<std::string>().empty()) return; +        throw std::runtime_error("no selectable antennas on this board"); + +    case SUBDEV_PROP_FREQ: +        return; // it wont do you much good, but you can set it + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("%s: No tunable bandwidth, fixed filtered to %0.2fMHz") +                % get_rx_id().to_pp_string() % _max_freq +            ) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * Basic and LF TX dboard + **********************************************************************/ +basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){ +    _max_freq = max_freq; +} + +basic_tx::~basic_tx(void){ +    /* NOP */ +} + +void basic_tx::tx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = std::string(str(boost::format("%s - %s") +            % get_tx_id().to_pp_string() +            % get_subdev_name() +        )); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        val = float(0); +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        val = gain_range_t(0, 0, 0); +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_FREQ: +        val = double(0); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(-_max_freq, +_max_freq); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = sd_name_to_conn[get_subdev_name()]; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = true; //there is no LO, so it must be true! +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = subdev_bandwidth_scalar[get_subdev_name()]*_max_freq; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void basic_tx::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        UHD_ASSERT_THROW(val.as<float>() == float(0)); +        return; + +    case SUBDEV_PROP_ANTENNA: +        if (val.as<std::string>().empty()) return; +        throw std::runtime_error("no selectable antennas on this board"); + +    case SUBDEV_PROP_FREQ: +        return; // it wont do you much good, but you can set it + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("%s: No tunable bandwidth, fixed filtered to %0.2fMHz") +                % get_tx_id().to_pp_string() % _max_freq +            ) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp new file mode 100644 index 000000000..7edc1822c --- /dev/null +++ b/host/lib/usrp/dboard/db_dbsrx.cpp @@ -0,0 +1,600 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +// No RX IO Pins Used + +// RX IO Functions + +#include "max2118_regs.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The DBSRX constants + **********************************************************************/ +static const bool dbsrx_debug = false; + +static const freq_range_t dbsrx_freq_range(0.8e9, 2.4e9); + +static const freq_range_t dbsrx_pfd_freq_range(0.15e6, 2.01e6); + +static const prop_names_t dbsrx_antennas = list_of("J3"); + +static const uhd::dict<std::string, gain_range_t> dbsrx_gain_ranges = map_list_of +    ("GC1", gain_range_t(0, 56, 0.5)) +    ("GC2", gain_range_t(0, 24, 1)) +; + +/*********************************************************************** + * The DBSRX dboard class + **********************************************************************/ +class dbsrx : public rx_dboard_base{ +public: +    dbsrx(ctor_args_t args); +    ~dbsrx(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _lo_freq; +    double _bandwidth; +    uhd::dict<std::string, float> _gains; +    max2118_write_regs_t _max2118_write_regs; +    max2118_read_regs_t _max2118_read_regs; +    boost::uint8_t _max2118_addr(void){ +        return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x65 : 0x67; +    }; + +    void set_lo_freq(double target_freq); +    void set_gain(float gain, const std::string &name); +    void set_bandwidth(double bandwidth); + +    void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +        start_reg = boost::uint8_t(std::clip(int(start_reg), 0x0, 0x5)); +        stop_reg = boost::uint8_t(std::clip(int(stop_reg), 0x0, 0x5)); + +        for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t) - 1){ +            int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) - 1 ? sizeof(boost::uint32_t) - 1 : stop_reg - start_addr + 1; + +            //create buffer for register data (+1 for start address) +            byte_vector_t regs_vector(num_bytes + 1); + +            //first byte is the address of first register +            regs_vector[0] = start_addr; + +            //get the register data +            for(int i=0; i<num_bytes; i++){ +                regs_vector[1+i] = _max2118_write_regs.get_reg(start_addr+i); +                if(dbsrx_debug) std::cerr << boost::format( +                    "DBSRX: send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" +                ) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl; +            } + +            //send the data +            this->get_iface()->write_i2c( +                _max2118_addr(), regs_vector +            ); +        } +    } + +    void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +        static const boost::uint8_t status_addr = 0x0; +        start_reg = boost::uint8_t(std::clip(int(start_reg), 0x0, 0x1)); +        stop_reg = boost::uint8_t(std::clip(int(stop_reg), 0x0, 0x1)); + +        for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t)){ +            int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) ? sizeof(boost::uint32_t) : stop_reg - start_addr + 1; + +            //create buffer for register data +            byte_vector_t regs_vector(num_bytes); + +            //read from i2c +            regs_vector = this->get_iface()->read_i2c( +                _max2118_addr(), num_bytes +            ); + +            for(boost::uint8_t i=0; i < num_bytes; i++){ +                if (i + start_addr >= status_addr){ +                    _max2118_read_regs.set_reg(i + start_addr, regs_vector[i]); +                } +                if(dbsrx_debug) std::cerr << boost::format( +                    "DBSRX: read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" +                ) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl; +            } +        } +    } + +    /*! +     * Is the LO locked? +     * \return true for locked +     */ +    bool get_locked(void){ +        read_reg(0x0, 0x0); + +        //mask and return lock detect +        bool locked = 5 >= _max2118_read_regs.adc and _max2118_read_regs.adc >= 2; + +        if(dbsrx_debug) std::cerr << boost::format( +            "DBSRX: locked %d" +        ) % locked << std::endl; + +        return locked; +    } + +}; + +/*********************************************************************** + * Register the DBSRX dboard + **********************************************************************/ +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::warning::post( +            str(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::warning::post( +            str(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()) +        ); + +    //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 +    this->send_reg(0x0, 0x5); + +    //set defaults for LO, gains, and filter bandwidth +    _bandwidth = 33e6; +    set_lo_freq(dbsrx_freq_range.start()); + +    BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){ +        set_gain(dbsrx_gain_ranges[name].start(), name); +    } + +    set_bandwidth(33e6); // default bandwidth from datasheet +} + +dbsrx::~dbsrx(void){ +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +void 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 = std::sorted(clock_rates).back(); +    BOOST_FOREACH(ref_clock, std::reversed(std::sorted(clock_rates))){ +        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--; } + +        if(dbsrx_debug) std::cerr << 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); +            if (dbsrx_debug) std::cerr << boost::format("DBSRX R:%d\n") % R << std::endl; + +            //compute PFD compare frequency = ref_clock/R +            pfd_freq = ref_clock / R; + +            //constrain the PFD frequency to specified range +            if ((pfd_freq < dbsrx_pfd_freq_range.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)); + +    if(dbsrx_debug) std::cerr << 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; + +    if(dbsrx_debug) std::cerr << boost::format( +        "DBSRX: scaler %d, actual_freq %f MHz, register bit: %d" +    ) % scaler % (actual_freq/1e6) % int(_max2118_write_regs.div2) << std::endl; + +    //compute vco frequency and select vco +    double vco_freq = actual_freq * scaler; +    if (vco_freq < 2433e6) +        _max2118_write_regs.osc_band = 0; +    else if (vco_freq < 2711e6) +        _max2118_write_regs.osc_band = 1; +    else if (vco_freq < 3025e6) +        _max2118_write_regs.osc_band = 2; +    else if (vco_freq < 3341e6) +        _max2118_write_regs.osc_band = 3; +    else if (vco_freq < 3727e6) +        _max2118_write_regs.osc_band = 4; +    else if (vco_freq < 4143e6) +        _max2118_write_regs.osc_band = 5; +    else if (vco_freq < 4493e6) +        _max2118_write_regs.osc_band = 6; +    else +        _max2118_write_regs.osc_band = 7; + +    //send settings over i2c +    send_reg(0x0, 0x4); + +    //check vtune for lock condition +    read_reg(0x0, 0x0); + +    if(dbsrx_debug) std::cerr << boost::format( +        "DBSRX: initial guess for vco %d, vtune adc %d" +    ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + +    //if we are out of lock for chosen vco, change vco +    while ((_max2118_read_regs.adc == 0) or (_max2118_read_regs.adc == 7)){ + +        //vtune is too low, try lower frequency vco +        if (_max2118_read_regs.adc == 0){ +            if (_max2118_write_regs.osc_band == 0){ +                uhd::warning::post( +                    str(boost::format( +                        "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n"  +                        ) % int(_max2118_write_regs.osc_band)) +                ); +                UHD_ASSERT_THROW(_max2118_read_regs.adc != 0); //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::warning::post( +                    str(boost::format( +                        "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n"  +                        ) % int(_max2118_write_regs.osc_band)) +                ); +                UHD_ASSERT_THROW(_max2118_read_regs.adc != 7); //just to cause a throw +            } +            if (_max2118_write_regs.osc_band >= 7) break; +            _max2118_write_regs.osc_band += 1; +        } + +        if(dbsrx_debug) std::cerr << boost::format( +            "DBSRX: trying vco %d, vtune adc %d" +        ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + +        //update vco selection and check vtune +        send_reg(0x2, 0x2); +        read_reg(0x0, 0x0); + +        //allow for setup time before checking condition again +        boost::this_thread::sleep(boost::posix_time::milliseconds(1)); +    } +       +    if(dbsrx_debug) std::cerr << boost::format( +        "DBSRX: final vco %d, vtune adc %d" +    ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + +    //select charge pump bias current +    if (_max2118_read_regs.adc <= 2) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_100UA; +    else if (_max2118_read_regs.adc >= 5) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_400UA; +    else _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_200UA; +     +    //update charge pump bias current setting +    send_reg(0x2, 0x2); + +    //compute actual tuned frequency +    _lo_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) / std::pow(2.0,(1 + _max2118_write_regs.r_divider)) * _max2118_write_regs.get_n_divider(); + +    //debug output of calculated variables +    if (dbsrx_debug) std::cerr +        << boost::format("DBSRX tune:\n") +        << boost::format("    VCO=%d, CP=%d, PFD Freq=%fMHz\n") % int(_max2118_write_regs.osc_band) % _max2118_write_regs.cp_current % (pfd_freq/1e6) +        << boost::format("    R=%d, N=%f, scaler=%d, div2=%d\n") % R % N % scaler % int(_max2118_write_regs.div2) +        << boost::format("    Ref    Freq=%fMHz\n") % (ref_clock/1e6) +        << boost::format("    Target Freq=%fMHz\n") % (target_freq/1e6) +        << boost::format("    Actual Freq=%fMHz\n") % (_lo_freq/1e6) +        << boost::format("    VCO    Freq=%fMHz\n") % (vco_freq/1e6) +        << std::endl; + +    if (update_filter_settings) set_bandwidth(_bandwidth); +    get_locked(); +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Convert a requested gain for the GC2 vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 5 bit the register value + */ +static int gain_to_gc2_vga_reg(float &gain){ +    int reg = 0; +    gain = 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 = float(boost::math::iround(gain) * 0.5); +    } else { +        reg = boost::math::iround(22.0 - (gain - 4.0)); +        gain = float(boost::math::iround(gain)); +    } + +    if (dbsrx_debug) std::cerr << boost::format( +        "DBSRX GC2 Gain: %f dB, reg: %d" +    ) % gain % reg << std::endl; + +    return reg; +} + +/*! + * Convert a requested gain for the GC1 rf vga into the dac_volts value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ +static float gain_to_gc1_rfvga_dac(float &gain){ +    //clip the input +    gain = dbsrx_gain_ranges["GC1"].clip(gain); + +    //voltage level constants +    static const float max_volts = float(1.2), min_volts = float(2.7); +    static const float slope = (max_volts-min_volts)/dbsrx_gain_ranges["GC1"].stop(); + +    //calculate the voltage for the aux dac +    float dac_volts = gain*slope + min_volts; + +    if (dbsrx_debug) std::cerr << boost::format( +        "DBSRX GC1 Gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + +void dbsrx::set_gain(float gain, const std::string &name){ +    assert_has(dbsrx_gain_ranges.keys(), name, "dbsrx gain name"); +    if (name == "GC2"){ +        _max2118_write_regs.gc2 = gain_to_gc2_vga_reg(gain); +        send_reg(0x5, 0x5); +    } +    else if(name == "GC1"){ +        //write the new voltage to the aux dac +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, gain_to_gc1_rfvga_dac(gain)); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _gains[name] = gain; +} + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +void dbsrx::set_bandwidth(double bandwidth){ +    //clip the input +    bandwidth = std::clip<double>(bandwidth, 4e6, 33e6); + +    double ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); +     +    //NOTE: _max2118_write_regs.m_divider set in set_lo_freq + +    //compute f_dac setting +    _max2118_write_regs.f_dac = std::clip<int>(int((((bandwidth*_max2118_write_regs.m_divider)/ref_clock) - 4)/0.145),0,127); + +    //determine actual bandwidth +    _bandwidth = double((ref_clock/(_max2118_write_regs.m_divider))*(4+0.145*_max2118_write_regs.f_dac)); + +    if (dbsrx_debug) std::cerr << boost::format( +        "DBSRX Filter Bandwidth: %f MHz, m: %d, f_dac: %d\n" +    ) % (_bandwidth/1e6) % int(_max2118_write_regs.m_divider) % int(_max2118_write_regs.f_dac) << std::endl; + +    this->send_reg(0x3, 0x4); +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void dbsrx::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_gains.keys(), key.name, "dbsrx gain name"); +        val = _gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(dbsrx_gain_ranges.keys(), key.name, "dbsrx gain name"); +        val = dbsrx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(dbsrx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = dbsrx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string("J3"); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = dbsrx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = this->get_locked(); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*_bandwidth; //_bandwidth is low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void dbsrx::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_bandwidth(val.as<double>()/2.0); //complex double-sided, we want low-pass +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_dbsrx2.cpp b/host/lib/usrp/dboard/db_dbsrx2.cpp new file mode 100644 index 000000000..cdafd6a78 --- /dev/null +++ b/host/lib/usrp/dboard/db_dbsrx2.cpp @@ -0,0 +1,439 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +// No RX IO Pins Used + +#include "max2112_regs.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The DBSRX2 constants + **********************************************************************/ +static const bool dbsrx2_debug = false; + +static const freq_range_t dbsrx2_freq_range(0.8e9, 2.4e9); + +static const int dbsrx2_ref_divider = 4; // Hitachi HMC426 divider (U7) + +static const prop_names_t 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, float(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); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _lo_freq; +    double _bandwidth; +    uhd::dict<std::string, float> _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; +    } + +    void set_lo_freq(double target_freq); +    void set_gain(float gain, const std::string &name); +    void set_bandwidth(double bandwidth); + +    void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +        start_reg = boost::uint8_t(std::clip(int(start_reg), 0x0, 0xB)); +        stop_reg = boost::uint8_t(std::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); +                if(dbsrx2_debug) std::cerr << 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(std::clip(int(start_reg), 0x0, 0xD)); +        stop_reg = boost::uint8_t(std::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]); +                    /* +                    if(dbsrx2_debug) std::cerr << 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; +                    */ +                } +                if(dbsrx2_debug) std::cerr << 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; +            } +        } +    } + +    /*! +     * Is the LO locked? +     * \return true for locked +     */ +    bool 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; + +        if(dbsrx2_debug) std::cerr << boost::format( +            "DBSRX2 locked: %d" +        ) % locked << std::endl; + +        return locked; +    } + +}; + +/*********************************************************************** + * 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){ +    //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 + +    //send initial register settings +    send_reg(0x0, 0xB); +    //for (boost::uint8_t addr=0; addr<=12; addr++) this->send_reg(addr, addr); + +    //set defaults for LO, gains +    set_lo_freq(dbsrx2_freq_range.start()); +    BOOST_FOREACH(const std::string &name, dbsrx2_gain_ranges.keys()){ +        set_gain(dbsrx2_gain_ranges[name].start(), name); +    } + +    set_bandwidth(40e6); // default bandwidth from datasheet +    get_locked(); + +    _max2112_write_regs.bbg = boost::math::iround(dbsrx2_gain_ranges["BBG"].start()); +    send_reg(0x9, 0x9); +} + +dbsrx2::~dbsrx2(void){ +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +void dbsrx2::set_lo_freq(double target_freq){ +    //target_freq = std::clip(target_freq, dbsrx2_freq_range.min, dbsrx2_freq_range.max); + +    //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 +    if (dbsrx2_debug) std::cerr +        << 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(); + +} + +/*********************************************************************** + * 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(float &gain){ +    int reg = boost::math::iround(dbsrx2_gain_ranges["BBG"].clip(gain)); + +    gain = float(reg); + +    if (dbsrx2_debug) std::cerr  +        << 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 float gain_to_gc1_rfvga_dac(float &gain){ +    //clip the input +    gain = dbsrx2_gain_ranges["GC1"].clip(gain); + +    //voltage level constants +    static const float max_volts = float(0.5), min_volts = float(2.7); +    static const float slope = (max_volts-min_volts)/dbsrx2_gain_ranges["GC1"].stop(); + +    //calculate the voltage for the aux dac +    float dac_volts = gain*slope + min_volts; + +    if (dbsrx2_debug) std::cerr  +        << 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; +} + +void dbsrx2::set_gain(float 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; +} + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +void dbsrx2::set_bandwidth(double bandwidth){ +    //clip the input +    bandwidth = std::clip<double>(bandwidth, 4e6, 40e6); + +    _max2112_write_regs.lp = int((bandwidth/1e6 - 4)/0.29 + 12); +    _bandwidth = double(4 + (_max2112_write_regs.lp - 12) * 0.29)*1e6; + +    if (dbsrx2_debug) std::cerr  +        << 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); +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void dbsrx2::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_gains.keys(), key.name, "dbsrx2 gain name"); +        val = _gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(dbsrx2_gain_ranges.keys(), key.name, "dbsrx2 gain name"); +        val = dbsrx2_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(dbsrx2_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = dbsrx2_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string("J3"); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = dbsrx2_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_QI; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = this->get_locked(); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = _bandwidth; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void dbsrx2::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_bandwidth(val.as<double>()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp new file mode 100644 index 000000000..74a9fb37b --- /dev/null +++ b/host/lib/usrp/dboard/db_rfx.cpp @@ -0,0 +1,592 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +// IO Pin functions +#define POWER_IO     (1 << 7)   // Low enables power supply +#define ANTSW_IO     (1 << 6)   // On TX DB, 0 = TX, 1 = RX, on RX DB 0 = main ant, 1 = RX2 +#define MIXER_IO     (1 << 5)   // Enable appropriate mixer +#define LOCKDET_MASK (1 << 2)   // Input pin + +// Mixer constants +#define MIXER_ENB    MIXER_IO +#define MIXER_DIS    0 + +// Power constants +#define POWER_UP     0 +#define POWER_DOWN   POWER_IO + +// Antenna constants +#define ANT_TX       0          //the tx line is transmitting +#define ANT_RX       ANTSW_IO   //the tx line is receiving +#define ANT_TXRX     0          //the rx line is on txrx +#define ANT_RX2      ANTSW_IO   //the rx line in on rx2 +#define ANT_XX       0          //dont care how the antenna is set + +#include "adf4360_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The RFX Series constants + **********************************************************************/ +static const bool rfx_debug = false; + +static const prop_names_t rfx_tx_antennas = list_of("TX/RX"); + +static const prop_names_t rfx_rx_antennas = list_of("TX/RX")("RX2"); + +static const uhd::dict<std::string, gain_range_t> rfx_tx_gain_ranges; //empty + +static const uhd::dict<std::string, gain_range_t> rfx_rx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 70, float(0.022))) +; + +static const uhd::dict<std::string, gain_range_t> rfx400_rx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 45, float(0.022))) +; + +/*********************************************************************** + * The RFX series of dboards + **********************************************************************/ +class rfx_xcvr : public xcvr_dboard_base{ +public: +    rfx_xcvr( +        ctor_args_t args, +        const freq_range_t &freq_range, +        bool rx_div2, bool tx_div2 +    ); +    ~rfx_xcvr(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    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; +    double       _rx_lo_freq, _tx_lo_freq; +    std::string  _rx_ant; +    uhd::dict<std::string, float> _rx_gains; + +    void set_rx_lo_freq(double freq); +    void set_tx_lo_freq(double freq); +    void set_rx_ant(const std::string &ant); +    void set_tx_ant(const std::string &ant); +    void set_rx_gain(float gain, const std::string &name); +    void set_tx_gain(float gain, const std::string &name); + +    /*! +     * Set the LO frequency for the particular dboard unit. +     * \param unit which unit rx or tx +     * \param target_freq the desired frequency in Hz +     * \return the actual frequency in Hz +     */ +    double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + +    /*! +     * Get the lock detect status of the LO. +     * \param unit which unit rx or tx +     * \return true for locked +     */ +    bool get_locked(dboard_iface::unit_t unit){ +        return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; +    } +}; + +/*********************************************************************** + * Register the RFX dboards (min freq, max freq, rx div2, tx div2) + **********************************************************************/ +static dboard_base::sptr make_rfx_flex400(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(400e6, 500e6), false, true)); +} + +static dboard_base::sptr make_rfx_flex900(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(750e6, 1050e6), true, true)); +} + +static dboard_base::sptr make_rfx_flex1800(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(1500e6, 2100e6), false, false)); +} + +static dboard_base::sptr make_rfx_flex1200(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(1150e6, 1450e6), true, true)); +} + +static dboard_base::sptr make_rfx_flex2200(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(2000e6, 2400e6), false, false)); +} + +static dboard_base::sptr make_rfx_flex2400(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(2300e6, 2900e6), false, false)); +} + +UHD_STATIC_BLOCK(reg_rfx_dboards){ +    dboard_manager::register_dboard(0x0024, 0x0028, &make_rfx_flex400,  "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) +    ) +{ +    //enable the clocks that we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set the gpio directions and atr controls (identically) +    boost::uint16_t output_enables = POWER_IO | ANTSW_IO | MIXER_IO; +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, output_enables); +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, output_enables); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, output_enables); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, output_enables); + +    //setup the tx atr (this does not change with antenna) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        POWER_UP | ANT_XX | MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     POWER_UP | ANT_RX | MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     POWER_UP | ANT_TX | MIXER_ENB); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP | ANT_TX | MIXER_ENB); + +    //setup the rx atr (this does not change with antenna) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE,        POWER_UP | ANT_XX | MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY,     POWER_UP | ANT_XX | MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP | ANT_RX2| MIXER_ENB); + +    //set some default values +    set_rx_lo_freq((_freq_range.start() + _freq_range.stop())/2.0); +    set_tx_lo_freq((_freq_range.start() + _freq_range.stop())/2.0); +    set_rx_ant("RX2"); + +    BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){ +        set_rx_gain(_rx_gain_ranges[name].start(), name); +    } +} + +rfx_xcvr::~rfx_xcvr(void){ +    /* NOP */ +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void rfx_xcvr::set_rx_ant(const std::string &ant){ +    //validate input +    assert_has(rfx_rx_antennas, ant, "rfx rx antenna name"); + +    //set the rx atr regs that change with antenna setting +    this->get_iface()->set_atr_reg( +        dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, +        POWER_UP | MIXER_ENB | ((ant == "TX/RX")? ANT_TXRX : ANT_RX2) +    ); + +    //shadow the setting +    _rx_ant = ant; +} + +void rfx_xcvr::set_tx_ant(const std::string &ant){ +    assert_has(rfx_tx_antennas, ant, "rfx tx antenna name"); +    //only one antenna option, do nothing +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +static float rx_pga0_gain_to_dac_volts(float &gain, float range){ +    //voltage level constants (negative slope) +    static const float max_volts = float(.2), min_volts = float(1.2); +    static const float slope = (max_volts-min_volts)/(range); + +    //calculate the voltage for the aux dac +    float dac_volts = std::clip<float>(gain*slope + min_volts, max_volts, min_volts); + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + +void rfx_xcvr::set_tx_gain(float, const std::string &name){ +    assert_has(rfx_tx_gain_ranges.keys(), name, "rfx tx gain name"); +    UHD_THROW_INVALID_CODE_PATH(); //no gains to set +} + +void rfx_xcvr::set_rx_gain(float gain, const std::string &name){ +    assert_has(_rx_gain_ranges.keys(), name, "rfx rx gain name"); +    if(name == "PGA0"){ +        float dac_volts = rx_pga0_gain_to_dac_volts(gain,  +                              (_rx_gain_ranges["PGA0"].stop() - _rx_gain_ranges["PGA0"].start())); +        _rx_gains[name] = gain; + +        //write the new voltage to the aux dac +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, dac_volts); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +void rfx_xcvr::set_rx_lo_freq(double freq){ +    _rx_lo_freq = set_lo_freq(dboard_iface::UNIT_RX, freq); +} + +void rfx_xcvr::set_tx_lo_freq(double freq){ +    _tx_lo_freq = set_lo_freq(dboard_iface::UNIT_TX, freq); +} + +double rfx_xcvr::set_lo_freq( +    dboard_iface::unit_t unit, +    double target_freq +){ +    if (rfx_debug) std::cerr << boost::format( +        "RFX tune: target frequency %f Mhz" +    ) % (target_freq/1e6) << std::endl; + +    //clip the input +    target_freq = _freq_range.clip(target_freq); +    if (_div2[unit]) target_freq *= 2; + +    //map prescalers to the register enums +    static const uhd::dict<int, adf4360_regs_t::prescaler_value_t> prescaler_to_enum = map_list_of +        (8,  adf4360_regs_t::PRESCALER_VALUE_8_9) +        (16, adf4360_regs_t::PRESCALER_VALUE_16_17) +        (32, adf4360_regs_t::PRESCALER_VALUE_32_33) +    ; + +    //map band select clock dividers to enums +    static const uhd::dict<int, adf4360_regs_t::band_select_clock_div_t> bandsel_to_enum = map_list_of +        (1, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_1) +        (2, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_2) +        (4, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_4) +        (8, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_8) +    ; + +    double actual_freq=0, ref_freq = this->get_iface()->get_clock_rate(unit); +    int R=0, BS=0, P=0, B=0, A=0; + +    /* +     * The goal here to to loop though possible R dividers, +     * band select clock dividers, and prescaler values. +     * Calculate the A and B counters for each set of values. +     * The loop exists when it meets all of the constraints. +     * The resulting loop values are loaded into the registers. +     * +     * fvco = [P*B + A] * fref/R +     * fvco*R/fref = P*B + A = N +     */ +    for(R = 2; R <= 32; R+=2){ +        BOOST_FOREACH(BS, bandsel_to_enum.keys()){ +            if (ref_freq/R/BS > 1e6) continue; //constraint on band select clock +            BOOST_FOREACH(P, prescaler_to_enum.keys()){ +                //calculate B and A from N +                double N = target_freq*R/ref_freq; +                B = int(std::floor(N/P)); +                A = boost::math::iround(N - P*B); +                if (B < A or B > 8191 or B < 3 or A > 31) continue; //constraints on A, B +                //calculate the actual frequency +                actual_freq = double(P*B + A)*ref_freq/R; +                if (actual_freq/P > 300e6) continue; //constraint on prescaler output +                //constraints met: exit loop +                goto done_loop; +            } +        } +    } done_loop: + +    if (rfx_debug) std::cerr << boost::format( +        "RFX tune: R=%d, BS=%d, P=%d, B=%d, A=%d" +    ) % R % BS % P % B % A << std::endl; + +    //load the register values +    adf4360_regs_t regs; +    regs.core_power_level        = adf4360_regs_t::CORE_POWER_LEVEL_10MA; +    regs.counter_operation       = adf4360_regs_t::COUNTER_OPERATION_NORMAL; +    regs.muxout_control          = adf4360_regs_t::MUXOUT_CONTROL_DLD; +    regs.phase_detector_polarity = adf4360_regs_t::PHASE_DETECTOR_POLARITY_POS; +    regs.charge_pump_output      = adf4360_regs_t::CHARGE_PUMP_OUTPUT_NORMAL; +    regs.cp_gain_0               = adf4360_regs_t::CP_GAIN_0_SET1; +    regs.mute_till_ld            = adf4360_regs_t::MUTE_TILL_LD_ENB; +    regs.output_power_level      = adf4360_regs_t::OUTPUT_POWER_LEVEL_3_5MA; +    regs.current_setting1        = adf4360_regs_t::CURRENT_SETTING1_0_31MA; +    regs.current_setting2        = adf4360_regs_t::CURRENT_SETTING2_0_31MA; +    regs.power_down              = adf4360_regs_t::POWER_DOWN_NORMAL_OP; +    regs.prescaler_value         = prescaler_to_enum[P]; +    regs.a_counter               = A; +    regs.b_counter               = B; +    regs.cp_gain_1               = adf4360_regs_t::CP_GAIN_1_SET1; +    regs.divide_by_2_output      = (_div2[unit])? +                                    adf4360_regs_t::DIVIDE_BY_2_OUTPUT_DIV2 : +                                    adf4360_regs_t::DIVIDE_BY_2_OUTPUT_FUND ; +    regs.divide_by_2_prescaler   = adf4360_regs_t::DIVIDE_BY_2_PRESCALER_FUND; +    regs.r_counter               = R; +    regs.ablpw                   = adf4360_regs_t::ABLPW_3_0NS; +    regs.lock_detect_precision   = adf4360_regs_t::LOCK_DETECT_PRECISION_5CYCLES; +    regs.test_mode_bit           = 0; +    regs.band_select_clock_div   = bandsel_to_enum[BS]; + +    //write the registers +    std::vector<adf4360_regs_t::addr_t> addrs = list_of //correct power-up sequence to write registers (R, C, N) +        (adf4360_regs_t::ADDR_RCOUNTER) +        (adf4360_regs_t::ADDR_CONTROL) +        (adf4360_regs_t::ADDR_NCOUNTER) +    ; +    BOOST_FOREACH(adf4360_regs_t::addr_t addr, addrs){ +        this->get_iface()->write_spi( +            unit, spi_config_t::EDGE_RISE, +            regs.get_reg(addr), 24 +        ); +    } + +    //return the actual frequency +    if (_div2[unit]) actual_freq /= 2; +    if (rfx_debug) std::cerr << boost::format( +        "RFX tune: actual frequency %f Mhz" +    ) % (actual_freq/1e6) << std::endl; +    return actual_freq; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void rfx_xcvr::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_rx_gains.keys(), key.name, "rfx rx gain name"); +        val = _rx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(_rx_gain_ranges.keys(), key.name, "rfx rx gain name"); +        val = _rx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(_rx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _rx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = _freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _rx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = rfx_rx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_QI; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = this->get_locked(dboard_iface::UNIT_RX); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void rfx_xcvr::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_rx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_rx_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_rx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("RFX: No tunable bandwidth, fixed filtered to 40MHz")) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void rfx_xcvr::tx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(rfx_tx_gain_ranges.keys(), key.name, "rfx tx gain name"); +        //no controllable tx gains, will not get here +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(rfx_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _tx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = _freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string("TX/RX"); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = rfx_tx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = true; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = this->get_locked(dboard_iface::UNIT_TX); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void rfx_xcvr::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_tx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_tx_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_tx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("RFX: No tunable bandwidth, fixed filtered to 40MHz")) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp new file mode 100644 index 000000000..17fdad74a --- /dev/null +++ b/host/lib/usrp/dboard/db_tvrx.cpp @@ -0,0 +1,495 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +// No RX IO Pins Used + +// RX IO Functions + +//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/static.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/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 bool tvrx_debug = false; + +static const freq_range_t tvrx_freq_range(50e6, 860e6); + +static const prop_names_t 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(float(rfmin), float(rfmax), float((rfmax-rfmin)/4096.0))) +        ("IF", gain_range_t(float(ifmin), float(ifmax), float((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); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    uhd::dict<std::string, float> _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 +    }; + +    void set_gain(float gain, const std::string &name); +    void 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); +            if(tvrx_debug) std::cerr << 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){ +    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 +    set_freq(tvrx_freq_range.start()); + +    //set default gains +    BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){ +        set_gain(get_tvrx_gain_ranges()[name].start(), name); +    } +} + +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()){ +            if(tvrx_debug) std::cout << "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 = std::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])); + +    if(tvrx_debug) +        std::cout << "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 float rf_gain_to_voltage(float 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 = std::clip<double>(dac_volts, 0.0, 3.3); + +    if (tvrx_debug) std::cerr << boost::format( +        "tvrx RF AGC gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    return float(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 float if_gain_to_voltage(float 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 = std::clip<double>(dac_volts, 0.0, 3.3); + +    if (tvrx_debug) std::cerr << boost::format( +        "tvrx IF AGC gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    return float(dac_volts); +} + +void tvrx::set_gain(float 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; +} + +/*! + * Set the tuner to center the desired frequency at 43.75MHz + * \param freq the requested frequency + */ + +void 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"); + +    if(tvrx_debug) +        std::cout << 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 +} + +/*********************************************************************** + * Get the alias frequency of frequency freq when sampled at fs. + * \param freq the frequency of interest + * \param fs the sample rate + * \return the alias frequency + **********************************************************************/ + +static double get_alias(double freq, double fs) { +    double alias; +    freq = fmod(freq, fs); +    if(freq >= (fs/2)) { +        alias = fs - freq; +    } else { +        alias = freq; +    } +    return alias; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void tvrx::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); +    double codec_rate; + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_gains.keys(), key.name, "tvrx gain name"); +        val = _gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(get_tvrx_gain_ranges().keys(), key.name, "tvrx gain name"); +        val = get_tvrx_gain_ranges()[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(get_tvrx_gain_ranges().keys()); +        return; + +    case SUBDEV_PROP_FREQ: +    /* +     * so here we have to do some magic. because the TVRX uses a relatively high IF, +     * we have to watch the sample rate to see if the IF will be aliased +     * or if it will fall within Nyquist. +     */ +        codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); +        val = (_lo_freq - tvrx_if_freq) + get_alias(tvrx_if_freq, codec_rate); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = tvrx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = tvrx_antennas.front(); //there's only one +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = tvrx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = true; +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 6.0e6; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void tvrx::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_GAIN: +        this->set_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_FREQ: +        this->set_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("TVRX: No tunable bandwidth, fixed filtered to 6MHz")) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_unknown.cpp b/host/lib/usrp/dboard/db_unknown.cpp new file mode 100644 index 000000000..168e1971c --- /dev/null +++ b/host/lib/usrp/dboard/db_unknown.cpp @@ -0,0 +1,299 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/warning.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::warning::post(str(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); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); +}; + +class unknown_tx : public tx_dboard_base{ +public: +    unknown_tx(ctor_args_t args); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); +}; + +/*********************************************************************** + * Register the unknown dboards + **********************************************************************/ +static dboard_base::sptr make_unknown_rx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new unknown_rx(args)); +} + +static dboard_base::sptr make_unknown_tx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new unknown_tx(args)); +} + +UHD_STATIC_BLOCK(reg_unknown_dboards){ +    dboard_manager::register_dboard(0xfff0, &make_unknown_tx, "Unknown TX"); +    dboard_manager::register_dboard(0xfff1, &make_unknown_rx, "Unknown RX"); +} + +/*********************************************************************** + * Unknown RX dboard + **********************************************************************/ +unknown_rx::unknown_rx(ctor_args_t args) : rx_dboard_base(args){ +    warn_if_old_rfx(this->get_rx_id(), "RX"); +} + +void unknown_rx::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = "Unknown - " + get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        val = float(0); +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        val = gain_range_t(0, 0, 0); +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_FREQ: +        val = double(0); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = true; //there is no LO, so it must be true! +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 0.0; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void unknown_rx::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        UHD_ASSERT_THROW(val.as<float>() == float(0)); +        return; + +    case SUBDEV_PROP_ANTENNA: +        UHD_ASSERT_THROW(val.as<std::string>() == std::string("")); +        return; + +    case SUBDEV_PROP_FREQ: +        return; // it wont do you much good, but you can set it + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("Unknown Daughterboard: No tunable bandwidth, fixed filtered to 0.0MHz")) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * Unknown TX dboard + **********************************************************************/ +unknown_tx::unknown_tx(ctor_args_t args) : tx_dboard_base(args){ +    warn_if_old_rfx(this->get_tx_id(), "TX"); +} + +void unknown_tx::tx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = "Unknown - " + get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        val = float(0); +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        val = gain_range_t(0, 0, 0); +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_FREQ: +        val = double(0); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = true; //there is no LO, so it must be true! +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 0.0; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void unknown_tx::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        UHD_ASSERT_THROW(val.as<float>() == float(0)); +        return; + +    case SUBDEV_PROP_ANTENNA: +        UHD_ASSERT_THROW(val.as<std::string>() == std::string("")); +        return; + +    case SUBDEV_PROP_FREQ: +        return; // it wont do you much good, but you can set it + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("Unknown Daughterboard: No tunable bandwidth, fixed filtered to 0.0MHz")) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_wbx.cpp b/host/lib/usrp/dboard/db_wbx.cpp new file mode 100644 index 000000000..dd5bd600b --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx.cpp @@ -0,0 +1,666 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +// Common IO Pins +#define ANTSW_IO        ((1 << 5)|(1 << 15))    // on UNIT_TX, 0 = TX, 1 = RX, on UNIT_RX 0 = main ant, 1 = RX2 +#define ADF4350_CE      (1 << 3) +#define ADF4350_PDBRF   (1 << 2) +#define ADF4350_MUXOUT  (1 << 1)                // INPUT!!! +#define LOCKDET_MASK    (1 << 0)                // INPUT!!! + +// TX IO Pins +#define TX_PUP_5V       (1 << 7)                // enables 5.0V power supply +#define TX_PUP_3V       (1 << 6)                // enables 3.3V supply +#define TXMOD_EN        (1 << 4)                // on UNIT_TX, 1 enables TX Modulator + +// RX IO Pins +#define RX_PUP_5V       (1 << 7)                // enables 5.0V power supply +#define RX_PUP_3V       (1 << 6)                // enables 3.3V supply +#define RXBB_PDB        (1 << 4)                // on UNIT_RX, 1 powers up RX baseband + +// RX Attenuator Pins +#define RX_ATTN_SHIFT   8                       // lsb of RX Attenuator Control +#define RX_ATTN_MASK    (63 << RX_ATTN_SHIFT)      // valid bits of RX Attenuator Control + +// Mixer functions +#define TX_MIXER_ENB    (TXMOD_EN|ADF4350_PDBRF) +#define TX_MIXER_DIS    0 + +#define RX_MIXER_ENB    (RXBB_PDB|ADF4350_PDBRF) +#define RX_MIXER_DIS    0 + +// Pin functions +#define TX_POWER_IO     (TX_PUP_5V|TX_PUP_3V)   // high enables power supply +#define TXIO_MASK       (TX_POWER_IO|ANTSW_IO|ADF4350_CE|ADF4350_PDBRF|TXMOD_EN) + +#define RX_POWER_IO     (RX_PUP_5V|RX_PUP_3V)   // high enables power supply +#define RXIO_MASK       (RX_POWER_IO|ANTSW_IO|ADF4350_CE|ADF4350_PDBRF|RXBB_PDB|RX_ATTN_MASK) + +// Power functions +#define TX_POWER_UP     (TX_POWER_IO|ADF4350_CE) +#define TX_POWER_DOWN   0 + +#define RX_POWER_UP     (RX_POWER_IO|ADF4350_CE) +#define RX_POWER_DOWN   0 + +// Antenna constants +#define ANT_TX          0                       //the tx line is transmitting +#define ANT_RX          ANTSW_IO                //the tx line is receiving +#define ANT_TXRX        0                       //the rx line is on txrx +#define ANT_RX2         ANTSW_IO                //the rx line in on rx2 +#define ANT_XX          0                       //dont care how the antenna is set + +#include "adf4350_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The WBX dboard constants + **********************************************************************/ +static const bool wbx_debug = false; + +static const freq_range_t wbx_freq_range(68.75e6, 2.2e9); + +static const prop_names_t wbx_tx_antennas = list_of("TX/RX"); + +static const prop_names_t wbx_rx_antennas = list_of("TX/RX")("RX2"); + +static const uhd::dict<std::string, gain_range_t> wbx_tx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 25, float(0.05))) +; + +static const uhd::dict<std::string, gain_range_t> wbx_rx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 31.5, float(0.5))) +; + +/*********************************************************************** + * The WBX dboard + **********************************************************************/ +class wbx_xcvr : public xcvr_dboard_base{ +public: +    wbx_xcvr(ctor_args_t args); +    ~wbx_xcvr(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    uhd::dict<std::string, float> _tx_gains, _rx_gains; +    double       _rx_lo_freq, _tx_lo_freq; +    std::string  _tx_ant, _rx_ant; + +    void set_rx_lo_freq(double freq); +    void set_tx_lo_freq(double freq); +    void set_rx_ant(const std::string &ant); +    void set_tx_ant(const std::string &ant); +    void set_rx_gain(float gain, const std::string &name); +    void set_tx_gain(float gain, const std::string &name); + +    void update_atr(void); + +    /*! +     * Set the LO frequency for the particular dboard unit. +     * \param unit which unit rx or tx +     * \param target_freq the desired frequency in Hz +     * \return the actual frequency in Hz +     */ +    double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + +    /*! +     * Get the lock detect status of the LO. +     * \param unit which unit rx or tx +     * \return true for locked +     */ +    bool get_locked(dboard_iface::unit_t unit){ +        return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; +    } +}; + +/*********************************************************************** + * Register the WBX dboard (min freq, max freq, rx div2, tx div2) + **********************************************************************/ +static dboard_base::sptr make_wbx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new wbx_xcvr(args)); +} + +UHD_STATIC_BLOCK(reg_wbx_dboards){ +    dboard_manager::register_dboard(0x0053, 0x0052, &make_wbx, "WBX"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +wbx_xcvr::wbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ + +    //enable the clocks that we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TXIO_MASK); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RXIO_MASK); +    if (wbx_debug) std::cerr << boost::format( +        "WBX GPIO Direction: RX: 0x%08x, TX: 0x%08x" +    ) % RXIO_MASK % TXIO_MASK << std::endl; + +    //set some default values +    set_rx_lo_freq((wbx_freq_range.start() + wbx_freq_range.stop())/2.0); +    set_tx_lo_freq((wbx_freq_range.start() + wbx_freq_range.stop())/2.0); +    set_rx_ant("RX2"); + +    BOOST_FOREACH(const std::string &name, wbx_tx_gain_ranges.keys()){ +        set_tx_gain(wbx_tx_gain_ranges[name].start(), name); +    } +    BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ +        set_rx_gain(wbx_rx_gain_ranges[name].start(), name); +    } +} + +wbx_xcvr::~wbx_xcvr(void){ +    /* NOP */ +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +static int rx_pga0_gain_to_iobits(float &gain){ +    //clip the input +    gain = wbx_rx_gain_ranges["PGA0"].clip(gain); + +    //convert to attenuation and update iobits for atr +    float 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; + +    if (wbx_debug) std::cerr << boost::format( +        "WBX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" +    ) % attn % attn_code % (iobits & RX_ATTN_MASK) % RX_ATTN_MASK << std::endl; + +    //the actual gain setting +    gain = wbx_rx_gain_ranges["PGA0"].stop() - float(attn_code)/2; + +    return iobits; +} + +static float tx_pga0_gain_to_dac_volts(float &gain){ +    //clip the input +    gain = wbx_tx_gain_ranges["PGA0"].clip(gain); + +    //voltage level constants +    static const float max_volts = float(0.5), min_volts = float(1.4); +    static const float slope = (max_volts-min_volts)/wbx_tx_gain_ranges["PGA0"].stop(); + +    //calculate the voltage for the aux dac +    float dac_volts = gain*slope + min_volts; + +    if (wbx_debug) std::cerr << boost::format( +        "WBX TX Gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + +void wbx_xcvr::set_tx_gain(float gain, const std::string &name){ +    assert_has(wbx_tx_gain_ranges.keys(), name, "wbx tx gain name"); +    if(name == "PGA0"){ +        float dac_volts = tx_pga0_gain_to_dac_volts(gain); +        _tx_gains[name] = gain; + +        //write the new voltage to the aux dac +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + +void wbx_xcvr::set_rx_gain(float gain, const std::string &name){ +    assert_has(wbx_rx_gain_ranges.keys(), name, "wbx rx gain name"); +    if(name == "PGA0"){ +        rx_pga0_gain_to_iobits(gain); +        _rx_gains[name] = gain; + +        //write the new gain to atr regs +        update_atr(); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void wbx_xcvr::update_atr(void){ +    //calculate atr pins +    int pga0_iobits = rx_pga0_gain_to_iobits(_rx_gains["PGA0"]); + +    //setup the tx atr (this does not change with antenna) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        TX_POWER_UP | ANT_XX | TX_MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     TX_POWER_UP | ANT_RX | TX_MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     TX_POWER_UP | ANT_TX | TX_MIXER_ENB); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, TX_POWER_UP | ANT_TX | TX_MIXER_ENB); + +    //setup the rx atr (this does not change with antenna) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, +        pga0_iobits | RX_POWER_UP | ANT_XX | RX_MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, +        pga0_iobits | RX_POWER_UP | ANT_XX | RX_MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, +        pga0_iobits | RX_POWER_UP | ANT_RX2| RX_MIXER_ENB); + +    //set the rx atr regs that change with antenna setting +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, +        pga0_iobits | RX_POWER_UP | RX_MIXER_ENB | ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2)); +    if (wbx_debug) std::cerr << boost::format( +        "WBX RXONLY ATR REG: 0x%08x" +    ) % (pga0_iobits | RX_POWER_UP | RX_MIXER_ENB | ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2)) << std::endl; +} + +void wbx_xcvr::set_rx_ant(const std::string &ant){ +    //validate input +    assert_has(wbx_rx_antennas, ant, "wbx rx antenna name"); + +    //shadow the setting +    _rx_ant = ant; + +    //write the new antenna setting to atr regs +    update_atr(); +} + +void wbx_xcvr::set_tx_ant(const std::string &ant){ +    assert_has(wbx_tx_antennas, ant, "wbx tx antenna name"); +    //only one antenna option, do nothing +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +void wbx_xcvr::set_rx_lo_freq(double freq){ +    _rx_lo_freq = set_lo_freq(dboard_iface::UNIT_RX, freq); +} + +void wbx_xcvr::set_tx_lo_freq(double freq){ +    _tx_lo_freq = set_lo_freq(dboard_iface::UNIT_TX, freq); +} + +double wbx_xcvr::set_lo_freq( +    dboard_iface::unit_t unit, +    double target_freq +){ +    if (wbx_debug) std::cerr << boost::format( +        "WBX tune: target frequency %f Mhz" +    ) % (target_freq/1e6) << std::endl; + +    //clip the input +    target_freq = wbx_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 = this->get_iface()->get_clock_rate(unit); +    int R=0, BS=0, N=0, FRAC=0, MOD=0; +    int RFdiv = 1; +    adf4350_regs_t::reference_divide_by_2_t T     = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; +    adf4350_regs_t::reference_doubler_t     D     = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;     + +    //Reference doubler for 50% duty cycle +    // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 +    if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; + +    //increase RF divider until acceptable VCO frequency +    //start with target_freq*2 because mixer has divide by 2 +    double vco_freq = target_freq*2; +    while (vco_freq < 2.2e9) { +        vco_freq *= 2; +        RFdiv *= 2; +    } + +    //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) +    adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + +    /* +     * The goal here is to loop though possible R dividers, +     * band select clock dividers, N (int) dividers, and FRAC  +     * (frac) dividers. +     * +     * Calculate the N and F dividers for each set of values. +     * The loop exists when it meets all of the constraints. +     * The resulting loop values are loaded into the registers. +     * +     * from pg.21 +     * +     * f_pfd = f_ref*(1+D)/(R*(1+T)) +     * f_vco = (N + (FRAC/MOD))*f_pfd +     *    N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD +     * f_rf = f_vco/RFdiv) +     * f_actual = f_rf/2 +     */ +    for(R = 1; R <= 1023; R+=1){ +        //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) +        pfd_freq = ref_freq*(1+D)/(R*(1+T)); + +        //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) +        if (pfd_freq > 25e6) continue; + +        //ignore fractional part of tuning +        N = int(std::floor(vco_freq/pfd_freq)); + +        //keep N > minimum int divider requirement +        if (N < prescaler_to_min_int_div[prescaler]) continue; + +        for(BS=1; BS <= 255; BS+=1){ +            //keep the band select frequency at or below 100KHz +            //constraint on band select clock +            if (pfd_freq/BS > 100e3) continue; +            goto done_loop; +        } +    } done_loop: + +    //Fractional-N calculation +    MOD = 4095; //max fractional accuracy +    FRAC = int((vco_freq/pfd_freq - N)*MOD); + +    //Reference divide-by-2 for 50% duty cycle +    // if R even, move one divide by 2 to to regs.reference_divide_by_2 +    if(R % 2 == 0){ +        T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; +        R /= 2; +    } + +    //actual frequency calculation +    actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); + + +    if (wbx_debug) { +        std::cerr << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl; + +        std::cerr << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%d" +            ) % R % BS % N % FRAC % MOD % T % D % RFdiv % get_locked(unit)<< std::endl +        << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" +            ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; +    } + +    //load the register values +    adf4350_regs_t regs; + +    regs.frac_12_bit = FRAC; +    regs.int_16_bit = N; +    regs.mod_12_bit = MOD; +    regs.prescaler = prescaler; +    regs.r_counter_10_bit = R; +    regs.reference_divide_by_2 = T; +    regs.reference_doubler = D; +    regs.band_select_clock_div = BS; +    UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); +    regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + +    //write the registers +    //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) +    int addr; + +    for(addr=5; addr>=0; addr--){ +        if (wbx_debug) std::cerr << boost::format( +            "WBX SPI Reg (0x%02x): 0x%08x" +        ) % addr % regs.get_reg(addr) << std::endl; +        this->get_iface()->write_spi( +            unit, spi_config_t::EDGE_RISE, +            regs.get_reg(addr), 32 +        ); +    } + +    //return the actual frequency +    if (wbx_debug) std::cerr << boost::format( +        "WBX tune: actual frequency %f Mhz" +    ) % (actual_freq/1e6) << std::endl; +    return actual_freq; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void wbx_xcvr::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_rx_gains.keys(), key.name, "wbx rx gain name"); +        val = _rx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(wbx_rx_gain_ranges.keys(), key.name, "wbx rx gain name"); +        val = wbx_rx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(wbx_rx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _rx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = wbx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _rx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = wbx_rx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = this->get_locked(dboard_iface::UNIT_RX); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void wbx_xcvr::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_rx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_rx_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_rx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("WBX: No tunable bandwidth, fixed filtered to 40MHz")) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void wbx_xcvr::tx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_tx_gains.keys(), key.name, "wbx tx gain name"); +        val = _tx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(wbx_tx_gain_ranges.keys(), key.name, "wbx tx gain name"); +        val = wbx_tx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(wbx_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _tx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = wbx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string("TX/RX"); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = wbx_tx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = this->get_locked(dboard_iface::UNIT_TX); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void wbx_xcvr::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_tx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_tx_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_tx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        uhd::warning::post( +            str(boost::format("WBX: No tunable bandwidth, fixed filtered to 40MHz")) +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp new file mode 100644 index 000000000..a3a1e6242 --- /dev/null +++ b/host/lib/usrp/dboard/db_xcvr2450.cpp @@ -0,0 +1,760 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +// TX IO Pins +#define HB_PA_OFF_TXIO      (1 << 15)    // 5GHz PA, 1 = off, 0 = on +#define LB_PA_OFF_TXIO      (1 << 14)    // 2.4GHz PA, 1 = off, 0 = on +#define ANTSEL_TX1_RX2_TXIO (1 << 13)    // 1 = Ant 1 to TX, Ant 2 to RX +#define ANTSEL_TX2_RX1_TXIO (1 << 12)    // 1 = Ant 2 to TX, Ant 1 to RX +#define TX_EN_TXIO          (1 << 11)    // 1 = TX on, 0 = TX off +#define AD9515DIV_TXIO      (1 << 4)     // 1 = Div  by 3, 0 = Div by 2 + +#define TXIO_MASK (HB_PA_OFF_TXIO | LB_PA_OFF_TXIO | ANTSEL_TX1_RX2_TXIO | ANTSEL_TX2_RX1_TXIO | TX_EN_TXIO | AD9515DIV_TXIO) + +// TX IO Functions +#define HB_PA_TXIO               LB_PA_OFF_TXIO +#define LB_PA_TXIO               HB_PA_OFF_TXIO +#define TX_ENB_TXIO              TX_EN_TXIO +#define TX_DIS_TXIO              0 +#define AD9515DIV_3_TXIO         AD9515DIV_TXIO +#define AD9515DIV_2_TXIO         0 + +// RX IO Pins +#define LOCKDET_RXIO (1 << 15)           // This is an INPUT!!! +#define POWER_RXIO   (1 << 14)           // 1 = power on, 0 = shutdown +#define RX_EN_RXIO   (1 << 13)           // 1 = RX on, 0 = RX off +#define RX_HP_RXIO   (1 << 12)           // 0 = Fc set by rx_hpf, 1 = 600 KHz + +#define RXIO_MASK (POWER_RXIO | RX_EN_RXIO | RX_HP_RXIO) + +// RX IO Functions +#define POWER_UP_RXIO            POWER_RXIO +#define POWER_DOWN_RXIO          0 +#define RX_ENB_RXIO              RX_EN_RXIO +#define RX_DIS_RXIO              0 + +#include "max2829_regs.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The XCVR 2450 constants + **********************************************************************/ +static const bool xcvr2450_debug = false; + +static const freq_range_t xcvr_freq_range = list_of +    (range_t<double>(2.4e9, 2.5e9)) +    (range_t<double>(4.9e9, 6.0e9)) +; + +static const prop_names_t xcvr_antennas = list_of("J1")("J2"); + +static const uhd::dict<std::string, gain_range_t> xcvr_tx_gain_ranges = map_list_of +    ("VGA", gain_range_t(0, 30, 0.5)) +    ("BB", gain_range_t(0, 5, 1.5)) +; +static const uhd::dict<std::string, gain_range_t> xcvr_rx_gain_ranges = map_list_of +    ("LNA", gain_range_t(list_of +        (range_t<float>(0)) +        (range_t<float>(15)) +        (range_t<float>(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); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _lo_freq; +    double _rx_bandwidth, _tx_bandwidth; +    uhd::dict<std::string, float> _tx_gains, _rx_gains; +    std::string _tx_ant, _rx_ant; +    int _ad9515div; +    max2829_regs_t _max2829_regs; + +    void set_lo_freq(double target_freq); +    void set_tx_ant(const std::string &ant); +    void set_rx_ant(const std::string &ant); +    void set_tx_gain(float gain, const std::string &name); +    void set_rx_gain(float gain, const std::string &name); +    void set_rx_bandwidth(double bandwidth); +    void 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); +        if(xcvr2450_debug) std::cerr << boost::format( +            "XCVR2450: send reg 0x%02x, value 0x%05x" +        ) % int(addr) % value << std::endl; +        this->get_iface()->write_spi( +            dboard_iface::UNIT_RX, +            spi_config_t::EDGE_RISE, +            value, 24 +        ); +    } + +    static bool is_highband(double freq){return freq > 3e9;} + +    /*! +     * Is the LO locked? +     * \return true for locked +     */ +    bool get_locked(void){ +        return (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & LOCKDET_RXIO) != 0; +    } + +    /*! +     * Read the RSSI from the aux adc +     * \return the rssi in dB +     */ +    float get_rssi(void){ +        //constants for the rssi calculation +        static const float min_v = float(0.5), max_v = float(2.5); +        static const float rssi_dyn_range = 60; +        //calculate the rssi from the voltage +        float voltage = this->get_iface()->read_aux_adc(dboard_iface::UNIT_RX, dboard_iface::AUX_ADC_B); +        return rssi_dyn_range*(voltage - min_v)/(max_v - min_v); +    } +}; + +/*********************************************************************** + * Register the XCVR 2450 dboard + **********************************************************************/ +static dboard_base::sptr make_xcvr2450(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new xcvr2450(args)); +} + +UHD_STATIC_BLOCK(reg_xcvr2450_dboard){ +    //register the factory function for the rx and tx dbids +    dboard_manager::register_dboard(0x0061, 0x0060, &make_xcvr2450, "XCVR2450"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ +    //enable only the clocks we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TXIO_MASK); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RXIO_MASK); + +    spi_reset(); //prepare the spi + +    _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); +    } + +    //set defaults for LO, gains, antennas +    set_lo_freq(2.45e9); +    set_rx_ant(xcvr_antennas.at(0)); +    set_tx_ant(xcvr_antennas.at(1)); +    BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){ +        set_tx_gain(xcvr_tx_gain_ranges[name].start(), name); +    } +    BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){ +        set_rx_gain(xcvr_rx_gain_ranges[name].start(), name); +    } +} + +xcvr2450::~xcvr2450(void){ +    spi_reset(); +} + +void xcvr2450::spi_reset(void){ +    //spi reset mode: global enable = off, tx and rx enable = on +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_ENB_TXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); +    boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + +    //take it back out of spi reset mode and wait a bit +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); +    boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +} + +void xcvr2450::update_atr(void){ +    //calculate tx atr pins +    int band_sel   = (xcvr2450::is_highband(_lo_freq))? HB_PA_TXIO : LB_PA_TXIO; +    int tx_ant_sel = (_tx_ant == "J1")? ANTSEL_TX1_RX2_TXIO : ANTSEL_TX2_RX1_TXIO; +    int rx_ant_sel = (_rx_ant == "J2")? ANTSEL_TX1_RX2_TXIO : ANTSEL_TX2_RX1_TXIO; +    int xx_ant_sel = tx_ant_sel; //prefer the tx antenna selection for full duplex (rx will get the other antenna) +    int ad9515div  = (_ad9515div == 3)? AD9515DIV_3_TXIO : AD9515DIV_2_TXIO; + +    //set the tx registers +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        band_sel | ad9515div | TX_DIS_TXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); + +    //set the rx registers +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE,        POWER_UP_RXIO | RX_DIS_RXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY,     POWER_UP_RXIO | RX_ENB_RXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY,     POWER_UP_RXIO | RX_DIS_RXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +void xcvr2450::set_lo_freq(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); + +    if (xcvr2450_debug) std::cerr +        << boost::format("XCVR2450 tune:\n") +        << boost::format("    R=%d, N=%f, ad9515=%d, scaler=%f\n") % R % N % _ad9515div % scaler +        << boost::format("    Ref    Freq=%fMHz\n") % (ref_freq/1e6) +        << boost::format("    Target Freq=%fMHz\n") % (target_freq/1e6) +        << boost::format("    Actual Freq=%fMHz\n") % (_lo_freq/1e6) +        << std::endl; + +    //high-high band or low-high band? +    if(_lo_freq > (5.35e9 + 5.47e9)/2.0){ +        if (xcvr2450_debug) std::cerr << "XCVR2450 tune: Using  high-high band" << std::endl; +        _max2829_regs.band_select_802_11a = max2829_regs_t::BAND_SELECT_802_11A_5_47GHZ_TO_5_875GHZ; +    }else{ +        if (xcvr2450_debug) std::cerr << "XCVR2450 tune: Using  low-high band" << std::endl; +        _max2829_regs.band_select_802_11a = max2829_regs_t::BAND_SELECT_802_11A_4_9GHZ_TO_5_35GHZ; +    } + +    //new band select settings and ad9515 divider +    this->update_atr(); + +    //load new counters into registers +    _max2829_regs.int_div_ratio_word = intdiv; +    _max2829_regs.frac_div_ratio_lsb = fracdiv & 0x3; +    _max2829_regs.frac_div_ratio_msb = fracdiv >> 2; +    this->send_reg(0x3); //integer +    this->send_reg(0x4); //fractional + +    //load the reference divider and band select into registers +    //toggle the bandswitch from off to automatic (which really means start) +    _max2829_regs.ref_divider = R; +    _max2829_regs.band_select = (xcvr2450::is_highband(_lo_freq))? +                                max2829_regs_t::BAND_SELECT_5GHZ   : +                                max2829_regs_t::BAND_SELECT_2_4GHZ ; +    _max2829_regs.vco_bandswitch = max2829_regs_t::VCO_BANDSWITCH_DISABLE; +    this->send_reg(0x5); +    _max2829_regs.vco_bandswitch = max2829_regs_t::VCO_BANDSWITCH_AUTOMATIC;; +    this->send_reg(0x5); +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void xcvr2450::set_tx_ant(const std::string &ant){ +    assert_has(xcvr_antennas, ant, "xcvr antenna name"); +   _tx_ant = ant; +    this->update_atr(); //sets the atr to the new antenna setting +} + +void xcvr2450::set_rx_ant(const std::string &ant){ +    assert_has(xcvr_antennas, ant, "xcvr antenna name"); +    _rx_ant = ant; +    this->update_atr(); //sets the atr to the new antenna setting +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Convert a requested gain for the tx vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 6 bit the register value + */ +static int gain_to_tx_vga_reg(float &gain){ +    //calculate the register value +    int reg = std::clip(boost::math::iround(gain*60/30.0) + 3, 0, 63); + +    //calculate the actual gain value +    if (reg < 4)       gain = 0; +    else if (reg < 48) gain = float(reg/2 - 1); +    else               gain = float(reg/2.0 - 1.5); + +    //return register value +    return reg; +} + +/*! + * Convert a requested gain for the tx bb into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return gain enum value + */ +static max2829_regs_t::tx_baseband_gain_t gain_to_tx_bb_reg(float &gain){ +    int reg = std::clip(boost::math::iround(gain*3/5.0), 0, 3); +    switch(reg){ +    case 0: +        gain = 0; +        return max2829_regs_t::TX_BASEBAND_GAIN_0DB; +    case 1: +        gain = 2; +        return max2829_regs_t::TX_BASEBAND_GAIN_2DB; +    case 2: +        gain = 3.5; +        return max2829_regs_t::TX_BASEBAND_GAIN_3_5DB; +    case 3: +        gain = 5; +        return max2829_regs_t::TX_BASEBAND_GAIN_5DB; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +/*! + * Convert a requested gain for the rx vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 5 bit the register value + */ +static int gain_to_rx_vga_reg(float &gain){ +    int reg = std::clip(boost::math::iround(gain/2.0), 0, 31); +    gain = float(reg*2); +    return reg; +} + +/*! + * Convert a requested gain for the rx lna into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 2 bit the register value + */ +static int gain_to_rx_lna_reg(float &gain){ +    int reg = std::clip(boost::math::iround(gain*2/30.5) + 1, 0, 3); +    switch(reg){ +    case 0: +    case 1: gain = 0;    break; +    case 2: gain = 15;   break; +    case 3: gain = 30.5; break; +    } +    return reg; +} + +void xcvr2450::set_tx_gain(float gain, const std::string &name){ +    assert_has(xcvr_tx_gain_ranges.keys(), name, "xcvr tx gain name"); +    if (name == "VGA"){ +        _max2829_regs.tx_vga_gain = gain_to_tx_vga_reg(gain); +        send_reg(0xC); +    } +    else if(name == "BB"){ +        _max2829_regs.tx_baseband_gain = gain_to_tx_bb_reg(gain); +        send_reg(0x9); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _tx_gains[name] = gain; +} + +void xcvr2450::set_rx_gain(float gain, const std::string &name){ +    assert_has(xcvr_rx_gain_ranges.keys(), name, "xcvr rx gain name"); +    if (name == "VGA"){ +        _max2829_regs.rx_vga_gain = gain_to_rx_vga_reg(gain); +        send_reg(0xB); +    } +    else if(name == "LNA"){ +        _max2829_regs.rx_lna_gain = gain_to_rx_lna_reg(gain); +        send_reg(0xB); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _rx_gains[name] = gain; +} + + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +static max2829_regs_t::tx_lpf_coarse_adj_t bandwidth_to_tx_lpf_coarse_reg(double &bandwidth){ +    int reg = std::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 = std::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 = std::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(); +} + +void xcvr2450::set_rx_bandwidth(double bandwidth){ +    double requested_bandwidth = bandwidth; + +    //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); + +    if (xcvr2450_debug) std::cerr << 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; +} + +void xcvr2450::set_tx_bandwidth(double bandwidth){ +    //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); + +    if (xcvr2450_debug) std::cerr << boost::format( +        "XCVR2450 TX Bandwidth (lp_fc): %f Hz, coarse reg: %d" +    ) % _tx_bandwidth % (int(_max2829_regs.tx_lpf_coarse_adj)) << std::endl; +} + + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void xcvr2450::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_rx_gains.keys(), key.name, "xcvr rx gain name"); +        val = _rx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(xcvr_rx_gain_ranges.keys(), key.name, "xcvr rx gain name"); +        val = xcvr_rx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(xcvr_rx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = xcvr_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _rx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = xcvr_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = this->get_locked(); +        return; + +    case SUBDEV_PROP_RSSI: +        val = this->get_rssi(); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*_rx_bandwidth; //_tx_bandwidth is low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void xcvr2450::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_rx_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_rx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_rx_bandwidth(val.as<double>()/2.0); //complex double-sided, we want low-pass +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void xcvr2450::tx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_tx_gains.keys(), key.name, "xcvr tx gain name"); +        val = _tx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(xcvr_tx_gain_ranges.keys(), key.name, "xcvr tx gain name"); +        val = xcvr_tx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(xcvr_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = xcvr_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _tx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = xcvr_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_QI; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = this->get_locked(); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*_tx_bandwidth; //_tx_bandwidth is low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void xcvr2450::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_tx_gain(val.as<float>(), key.name); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_tx_bandwidth(val.as<double>()/2.0); //complex double-sided, we want low-pass +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_tx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard_base.cpp b/host/lib/usrp/dboard_base.cpp new file mode 100644 index 000000000..6c4e29d9e --- /dev/null +++ b/host/lib/usrp/dboard_base.cpp @@ -0,0 +1,123 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "dboard_ctor_args.hpp" +#include <uhd/usrp/dboard_base.hpp> +#include <boost/format.hpp> +#include <stdexcept> + +using namespace uhd::usrp; + +/*********************************************************************** + * dboard_base dboard dboard_base class + **********************************************************************/ +struct dboard_base::impl{ +    dboard_ctor_args_t args; +}; + +dboard_base::dboard_base(ctor_args_t args){ +    _impl = UHD_PIMPL_MAKE(impl, ()); +    _impl->args = *static_cast<dboard_ctor_args_t *>(args); +} + +dboard_base::~dboard_base(void){ +   /* NOP */ +} + +std::string dboard_base::get_subdev_name(void){ +    return _impl->args.sd_name; +} + +dboard_iface::sptr dboard_base::get_iface(void){ +    return _impl->args.db_iface; +} + +dboard_id_t dboard_base::get_rx_id(void){ +    return _impl->args.rx_id; +} + +dboard_id_t dboard_base::get_tx_id(void){ +    return _impl->args.tx_id; +} + +/*********************************************************************** + * xcvr dboard dboard_base class + **********************************************************************/ +xcvr_dboard_base::xcvr_dboard_base(ctor_args_t args) : dboard_base(args){ +    if (get_rx_id() == dboard_id_t::none()){ +        throw std::runtime_error(str(boost::format( +            "cannot create xcvr board when the rx id is \"%s\"" +        ) % dboard_id_t::none().to_pp_string())); +    } +    if (get_tx_id() == dboard_id_t::none()){ +        throw std::runtime_error(str(boost::format( +            "cannot create xcvr board when the tx id is \"%s\"" +        ) % dboard_id_t::none().to_pp_string())); +    } +} + +xcvr_dboard_base::~xcvr_dboard_base(void){ +    /* NOP */ +} + +/*********************************************************************** + * rx dboard dboard_base class + **********************************************************************/ +rx_dboard_base::rx_dboard_base(ctor_args_t args) : dboard_base(args){ +    if (get_tx_id() != dboard_id_t::none()){ +        throw std::runtime_error(str(boost::format( +            "cannot create rx board when the tx id is \"%s\"" +            " -> expected a tx id of \"%s\"" +        ) % get_tx_id().to_pp_string() % dboard_id_t::none().to_pp_string())); +    } +} + +rx_dboard_base::~rx_dboard_base(void){ +    /* NOP */ +} + +void rx_dboard_base::tx_get(const wax::obj &, wax::obj &){ +    throw std::runtime_error("cannot call tx_get on a rx dboard"); +} + +void rx_dboard_base::tx_set(const wax::obj &, const wax::obj &){ +    throw std::runtime_error("cannot call tx_set on a rx dboard"); +} + +/*********************************************************************** + * tx dboard dboard_base class + **********************************************************************/ +tx_dboard_base::tx_dboard_base(ctor_args_t args) : dboard_base(args){ +    if (get_rx_id() != dboard_id_t::none()){ +        throw std::runtime_error(str(boost::format( +            "cannot create tx board when the rx id is \"%s\"" +            " -> expected a rx id of \"%s\"" +        ) % get_rx_id().to_pp_string() % dboard_id_t::none().to_pp_string())); +    } +} + +tx_dboard_base::~tx_dboard_base(void){ +    /* NOP */ +} + +void tx_dboard_base::rx_get(const wax::obj &, wax::obj &){ +    throw std::runtime_error("cannot call rx_get on a tx dboard"); +} + +void tx_dboard_base::rx_set(const wax::obj &, const wax::obj &){ +    throw std::runtime_error("cannot call rx_set on a tx dboard"); +} diff --git a/host/lib/usrp/dboard_ctor_args.hpp b/host/lib/usrp/dboard_ctor_args.hpp new file mode 100644 index 000000000..708f2ea08 --- /dev/null +++ b/host/lib/usrp/dboard_ctor_args.hpp @@ -0,0 +1,36 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP + +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + +    struct dboard_ctor_args_t{ +        std::string               sd_name; +        dboard_iface::sptr        db_iface; +        dboard_id_t               rx_id, tx_id; +    }; + +}} //namespace + +#endif /* INCLUDED_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP */ diff --git a/host/lib/usrp/dboard_eeprom.cpp b/host/lib/usrp/dboard_eeprom.cpp new file mode 100644 index 000000000..fa3631948 --- /dev/null +++ b/host/lib/usrp/dboard_eeprom.cpp @@ -0,0 +1,103 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/format.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +static const bool _dboard_eeprom_debug = false; + +//////////////////////////////////////////////////////////////////////// +// format of daughterboard EEPROM +// 00: 0xDB code for ``I'm a daughterboard'' +// 01:   .. Daughterboard ID (LSB) +// 02:   .. Daughterboard ID (MSB) +// 03:   .. io bits  7-0 direction (bit set if it's an output from m'board) +// 04:   .. io bits 15-8 direction (bit set if it's an output from m'board) +// 05:   .. ADC0 DC offset correction (LSB) +// 06:   .. ADC0 DC offset correction (MSB) +// 07:   .. ADC1 DC offset correction (LSB) +// 08:   .. ADC1 DC offset correction (MSB) +//  ... +// 1f:   .. negative of the sum of bytes [0x00, 0x1e] + +#define DB_EEPROM_MAGIC         0x00 +#define DB_EEPROM_MAGIC_VALUE   0xDB +#define DB_EEPROM_ID_LSB        0x01 +#define DB_EEPROM_ID_MSB        0x02 +#define DB_EEPROM_OE_LSB        0x03 +#define DB_EEPROM_OE_MSB        0x04 +#define DB_EEPROM_OFFSET_0_LSB  0x05 // offset correction for ADC or DAC 0 +#define DB_EEPROM_OFFSET_0_MSB  0x06 +#define DB_EEPROM_OFFSET_1_LSB  0x07 // offset correction for ADC or DAC 1 +#define DB_EEPROM_OFFSET_1_MSB  0x08 +#define DB_EEPROM_CHKSUM        0x1f + +#define DB_EEPROM_CLEN          0x20 // length of common portion of eeprom + +#define DB_EEPROM_CUSTOM_BASE   DB_EEPROM_CLEN // first avail offset for +                                               //   daughterboard specific use +//////////////////////////////////////////////////////////////////////// + +//negative sum of bytes excluding checksum byte +static boost::uint8_t checksum(const byte_vector_t &bytes){ +    int sum = 0; +    for (size_t i = 0; i < std::min(bytes.size(), size_t(DB_EEPROM_CHKSUM)); i++){ +        sum -= int(bytes.at(i)); +    } +    if (_dboard_eeprom_debug) +        std::cout << boost::format("sum: 0x%02x") % sum << std::endl; +    return boost::uint8_t(sum); +} + +dboard_eeprom_t::dboard_eeprom_t(const byte_vector_t &bytes){ +    if (_dboard_eeprom_debug){ +        for (size_t i = 0; i < bytes.size(); i++){ +            std::cout << boost::format( +                "eeprom byte[0x%02x] = 0x%02x") % i % int(bytes.at(i) +            ) << std::endl; +        } +    } +    try{ +        UHD_ASSERT_THROW(bytes.size() >= DB_EEPROM_CLEN); +        UHD_ASSERT_THROW(bytes[DB_EEPROM_MAGIC] == DB_EEPROM_MAGIC_VALUE); +        UHD_ASSERT_THROW(bytes[DB_EEPROM_CHKSUM] == checksum(bytes)); +        id = dboard_id_t::from_uint16(0 +            | (boost::uint16_t(bytes[DB_EEPROM_ID_LSB]) << 0) +            | (boost::uint16_t(bytes[DB_EEPROM_ID_MSB]) << 8) +        ); +    }catch(const uhd::assert_error &){ +        id = dboard_id_t::none(); +    } +} + +byte_vector_t dboard_eeprom_t::get_eeprom_bytes(void){ +    byte_vector_t bytes(DB_EEPROM_CLEN, 0); //defaults to all zeros +    bytes[DB_EEPROM_MAGIC] = DB_EEPROM_MAGIC_VALUE; +    bytes[DB_EEPROM_ID_LSB] = boost::uint8_t(id.to_uint16() >> 0); +    bytes[DB_EEPROM_ID_MSB] = boost::uint8_t(id.to_uint16() >> 8); +    bytes[DB_EEPROM_CHKSUM] = checksum(bytes); +    return bytes; +} + +size_t dboard_eeprom_t::num_bytes(void){ +    return DB_EEPROM_CLEN; +} diff --git a/host/lib/usrp/dboard_id.cpp b/host/lib/usrp/dboard_id.cpp new file mode 100644 index 000000000..3028d2a3b --- /dev/null +++ b/host/lib/usrp/dboard_id.cpp @@ -0,0 +1,68 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_id.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <sstream> +#include <iostream> + +using namespace uhd::usrp; + +dboard_id_t::dboard_id_t(boost::uint16_t id){ +    _id = id; +} + +dboard_id_t dboard_id_t::none(void){ +    return dboard_id_t(); +} + +dboard_id_t dboard_id_t::from_uint16(boost::uint16_t uint16){ +    return dboard_id_t(uint16); +} + +boost::uint16_t dboard_id_t::to_uint16(void) const{ +    return _id; +} + +//used with lexical cast to parse a hex string +template <class T> struct to_hex{ +    T value; +    operator T() const {return value;} +    friend std::istream& operator>>(std::istream& in, to_hex& out){ +        in >> std::hex >> out.value; +        return in; +    } +}; + +dboard_id_t dboard_id_t::from_string(const std::string &string){ +    if (string.substr(0, 2) == "0x"){ +        return dboard_id_t::from_uint16(boost::lexical_cast<to_hex<boost::uint16_t> >(string)); +    } +    return dboard_id_t::from_uint16(boost::lexical_cast<boost::uint16_t>(string)); +} + +std::string dboard_id_t::to_string(void) const{ +    return str(boost::format("0x%04x") % this->to_uint16()); +} + +//Note: to_pp_string is implemented in the dboard manager +//because it needs access to the dboard registration table + +bool uhd::usrp::operator==(const dboard_id_t &lhs, const dboard_id_t &rhs){ +    return lhs.to_uint16() == rhs.to_uint16(); +} diff --git a/host/lib/usrp/dboard_iface.cpp b/host/lib/usrp/dboard_iface.cpp new file mode 100644 index 000000000..5cc5ea470 --- /dev/null +++ b/host/lib/usrp/dboard_iface.cpp @@ -0,0 +1,78 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> + +using namespace uhd::usrp; + +struct dboard_iface::impl{ +    uhd::dict<unit_t, boost::uint16_t> pin_ctrl_shadow; +    uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > atr_reg_shadow; +    uhd::dict<unit_t, boost::uint16_t> gpio_ddr_shadow; +    uhd::dict<unit_t, boost::uint16_t> gpio_out_shadow; +}; + +dboard_iface::dboard_iface(void){ +    _impl = UHD_PIMPL_MAKE(impl, ()); +} + +template <typename T> +static T shadow_it(T &shadow, const T &value, const T &mask){ +    shadow = (shadow & ~mask) | (value & mask); +    return shadow; +} + +void dboard_iface::set_pin_ctrl( +    unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ +    _set_pin_ctrl(unit, shadow_it(_impl->pin_ctrl_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_pin_ctrl(unit_t unit){ +    return _impl->pin_ctrl_shadow[unit]; +} + +void dboard_iface::set_atr_reg( +    unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask +){ +    _set_atr_reg(unit, reg, shadow_it(_impl->atr_reg_shadow[unit][reg], value, mask)); +} + +boost::uint16_t dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ +    return _impl->atr_reg_shadow[unit][reg]; +} + +void dboard_iface::set_gpio_ddr( +    unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ +    _set_gpio_ddr(unit, shadow_it(_impl->gpio_ddr_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_gpio_ddr(unit_t unit){ +    return _impl->gpio_ddr_shadow[unit]; +} + +void dboard_iface::set_gpio_out( +    unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ +    _set_gpio_out(unit, shadow_it(_impl->gpio_out_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_gpio_out(unit_t unit){ +    return _impl->gpio_out_shadow[unit]; +} diff --git a/host/lib/usrp/dboard_manager.cpp b/host/lib/usrp/dboard_manager.cpp new file mode 100644 index 000000000..72bfd89e8 --- /dev/null +++ b/host/lib/usrp/dboard_manager.cpp @@ -0,0 +1,344 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "dboard_ctor_args.hpp" +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/types/dict.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * storage and registering for dboards + **********************************************************************/ +//dboard registry tuple: dboard constructor, canonical name, subdev names +typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, prop_names_t> args_t; + +//map a dboard id to a dboard constructor +typedef uhd::dict<dboard_id_t, args_t> id_to_args_map_t; +UHD_SINGLETON_FCN(id_to_args_map_t, get_id_to_args_map) + +void dboard_manager::register_dboard( +    const dboard_id_t &dboard_id, +    dboard_ctor_t dboard_ctor, +    const std::string &name, +    const prop_names_t &subdev_names +){ +    //std::cout << "registering: " << name << std::endl; +    if (get_id_to_args_map().has_key(dboard_id)){ +        throw std::runtime_error(str(boost::format( +            "The dboard id %s is already registered to %s." +        ) % dboard_id.to_string() % dboard_id.to_pp_string())); +    } +    get_id_to_args_map()[dboard_id] = args_t(dboard_ctor, name, subdev_names); +} + +//map an xcvr dboard id to its partner dboard id +typedef uhd::dict<dboard_id_t, dboard_id_t> xcvr_id_to_id_map_t; +UHD_SINGLETON_FCN(xcvr_id_to_id_map_t, get_xcvr_id_to_id_map) + +void dboard_manager::register_dboard( +    const dboard_id_t &rx_dboard_id, +    const dboard_id_t &tx_dboard_id, +    dboard_ctor_t dboard_ctor, +    const std::string &name, +    const prop_names_t &subdev_names +){ +    //regular registration for ids +    register_dboard(rx_dboard_id, dboard_ctor, name, subdev_names); +    register_dboard(tx_dboard_id, dboard_ctor, name, subdev_names); + +    //register xcvr mapping for ids +    get_xcvr_id_to_id_map()[rx_dboard_id] = tx_dboard_id; +    get_xcvr_id_to_id_map()[tx_dboard_id] = rx_dboard_id; +} + +std::string dboard_id_t::to_cname(void) const{ +    if (not get_id_to_args_map().has_key(*this)) return "Unknown"; +    return get_id_to_args_map()[*this].get<1>(); +} + +std::string dboard_id_t::to_pp_string(void) const{ +    return str(boost::format("%s (%s)") % this->to_cname() % this->to_string()); +} + +/*********************************************************************** + * internal helper classes + **********************************************************************/ +/*! + * A special wax proxy object that forwards calls to a subdev. + * A sptr to an instance will be used in the properties structure.  + */ +class subdev_proxy : boost::noncopyable, public wax::obj{ +public: +    typedef boost::shared_ptr<subdev_proxy> sptr; +    enum type_t{RX_TYPE, TX_TYPE}; + +    //structors +    subdev_proxy(dboard_base::sptr subdev, type_t type): +        _subdev(subdev), _type(type) +    { +        /* NOP */ +    } + +private: +    dboard_base::sptr   _subdev; +    type_t              _type; + +    //forward the get calls to the rx or tx +    void get(const wax::obj &key, wax::obj &val){ +        switch(_type){ +        case RX_TYPE: return _subdev->rx_get(key, val); +        case TX_TYPE: return _subdev->tx_get(key, val); +        } +    } + +    //forward the set calls to the rx or tx +    void set(const wax::obj &key, const wax::obj &val){ +        switch(_type){ +        case RX_TYPE: return _subdev->rx_set(key, val); +        case TX_TYPE: return _subdev->tx_set(key, val); +        } +    } +}; + +/*********************************************************************** + * dboard manager implementation class + **********************************************************************/ +class dboard_manager_impl : public dboard_manager{ + +public: +    dboard_manager_impl( +        dboard_id_t rx_dboard_id, +        dboard_id_t tx_dboard_id, +        dboard_iface::sptr iface +    ); +    ~dboard_manager_impl(void); + +    //dboard_iface +    prop_names_t get_rx_subdev_names(void); +    prop_names_t get_tx_subdev_names(void); +    wax::obj get_rx_subdev(const std::string &subdev_name); +    wax::obj get_tx_subdev(const std::string &subdev_name); + +private: +    //list of rx and tx dboards in this dboard_manager +    //each dboard here is actually a subdevice proxy +    //the subdevice proxy is internal to the cpp file +    uhd::dict<std::string, subdev_proxy::sptr> _rx_dboards; +    uhd::dict<std::string, subdev_proxy::sptr> _tx_dboards; +    dboard_iface::sptr _iface; +    void set_nice_dboard_if(void); +}; + +/*********************************************************************** + * make routine for dboard manager + **********************************************************************/ +dboard_manager::sptr dboard_manager::make( +    dboard_id_t rx_dboard_id, +    dboard_id_t tx_dboard_id, +    dboard_iface::sptr iface +){ +    return dboard_manager::sptr( +        new dboard_manager_impl(rx_dboard_id, tx_dboard_id, iface) +    ); +} + +/*********************************************************************** + * implementation class methods + **********************************************************************/ +static args_t get_dboard_args( +    dboard_iface::unit_t unit, +    dboard_id_t dboard_id, +    bool force_to_unknown = false +){ +    //special case, the none id was provided, use the following ids +    if (dboard_id == dboard_id_t::none() or force_to_unknown){ +        UHD_ASSERT_THROW(get_id_to_args_map().has_key(0xfff1)); +        UHD_ASSERT_THROW(get_id_to_args_map().has_key(0xfff0)); +        switch(unit){ +        case dboard_iface::UNIT_RX: return get_dboard_args(unit, 0xfff1); +        case dboard_iface::UNIT_TX: return get_dboard_args(unit, 0xfff0); +        default: UHD_THROW_INVALID_CODE_PATH(); +        } +    } + +    //verify that there is a registered constructor for this id +    if (not get_id_to_args_map().has_key(dboard_id)){ +        uhd::warning::post(str(boost::format( +            "Unknown dboard ID: %s.\n" +        ) % dboard_id.to_pp_string())); +        return get_dboard_args(unit, dboard_id, true); +    } + +    //return the dboard args for this id +    return get_id_to_args_map()[dboard_id]; +} + +dboard_manager_impl::dboard_manager_impl( +    dboard_id_t rx_dboard_id, +    dboard_id_t tx_dboard_id, +    dboard_iface::sptr iface +){ +    _iface = iface; + +    //determine xcvr status +    bool rx_dboard_is_xcvr = get_xcvr_id_to_id_map().has_key(rx_dboard_id); +    bool tx_dboard_is_xcvr = get_xcvr_id_to_id_map().has_key(tx_dboard_id); +    bool this_dboard_is_xcvr = ( +        rx_dboard_is_xcvr and tx_dboard_is_xcvr and +        (get_xcvr_id_to_id_map()[rx_dboard_id] == tx_dboard_id) and +        (get_xcvr_id_to_id_map()[tx_dboard_id] == rx_dboard_id) +    ); + +    //warn for invalid dboard id xcvr combinations +    if (rx_dboard_is_xcvr != this_dboard_is_xcvr or tx_dboard_is_xcvr != this_dboard_is_xcvr){ +        uhd::warning::post(str(boost::format( +            "Unknown transceiver board ID combination...\n" +            "RX dboard ID: %s\n" +            "TX dboard ID: %s\n" +        ) % rx_dboard_id.to_pp_string() % tx_dboard_id.to_pp_string())); +    } + +    //extract dboard constructor and settings (force to unknown for messed up xcvr status) +    dboard_ctor_t rx_dboard_ctor; std::string rx_name; prop_names_t rx_subdevs; +    boost::tie(rx_dboard_ctor, rx_name, rx_subdevs) = get_dboard_args( +        dboard_iface::UNIT_RX, rx_dboard_id, rx_dboard_is_xcvr != this_dboard_is_xcvr +    ); + +    dboard_ctor_t tx_dboard_ctor; std::string tx_name; prop_names_t tx_subdevs; +    boost::tie(tx_dboard_ctor, tx_name, tx_subdevs) = get_dboard_args( +        dboard_iface::UNIT_TX, tx_dboard_id, tx_dboard_is_xcvr != this_dboard_is_xcvr +    ); + +    //initialize the gpio pins before creating subdevs +    set_nice_dboard_if(); + +    //dboard constructor args +    dboard_ctor_args_t db_ctor_args; +    db_ctor_args.db_iface = iface; + +    //make xcvr subdevs (make one subdev for both rx and tx dboards) +    if (this_dboard_is_xcvr){ +        UHD_ASSERT_THROW(rx_dboard_ctor == tx_dboard_ctor); +        UHD_ASSERT_THROW(rx_subdevs == tx_subdevs); +        BOOST_FOREACH(const std::string &subdev, rx_subdevs){ +            db_ctor_args.sd_name = subdev; +            db_ctor_args.rx_id = rx_dboard_id; +            db_ctor_args.tx_id = tx_dboard_id; +            dboard_base::sptr xcvr_dboard = rx_dboard_ctor(&db_ctor_args); +            //create a rx proxy for this xcvr board +            _rx_dboards[subdev] = subdev_proxy::sptr( +                new subdev_proxy(xcvr_dboard, subdev_proxy::RX_TYPE) +            ); +            //create a tx proxy for this xcvr board +            _tx_dboards[subdev] = subdev_proxy::sptr( +                new subdev_proxy(xcvr_dboard, subdev_proxy::TX_TYPE) +            ); +        } +    } + +    //make tx and rx subdevs (separate subdevs for rx and tx dboards) +    else{ +        //make the rx subdevs +        BOOST_FOREACH(const std::string &subdev, rx_subdevs){ +            db_ctor_args.sd_name = subdev; +            db_ctor_args.rx_id = rx_dboard_id; +            db_ctor_args.tx_id = dboard_id_t::none(); +            dboard_base::sptr rx_dboard = rx_dboard_ctor(&db_ctor_args); +            //create a rx proxy for this rx board +            _rx_dboards[subdev] = subdev_proxy::sptr( +                new subdev_proxy(rx_dboard, subdev_proxy::RX_TYPE) +            ); +        } +        //make the tx subdevs +        BOOST_FOREACH(const std::string &subdev, tx_subdevs){ +            db_ctor_args.sd_name = subdev; +            db_ctor_args.rx_id = dboard_id_t::none(); +            db_ctor_args.tx_id = tx_dboard_id; +            dboard_base::sptr tx_dboard = tx_dboard_ctor(&db_ctor_args); +            //create a tx proxy for this tx board +            _tx_dboards[subdev] = subdev_proxy::sptr( +                new subdev_proxy(tx_dboard, subdev_proxy::TX_TYPE) +            ); +        } +    } +} + +dboard_manager_impl::~dboard_manager_impl(void){ +    set_nice_dboard_if(); +} + +prop_names_t dboard_manager_impl::get_rx_subdev_names(void){ +    return _rx_dboards.keys(); +} + +prop_names_t dboard_manager_impl::get_tx_subdev_names(void){ +    return _tx_dboards.keys(); +} + +wax::obj dboard_manager_impl::get_rx_subdev(const std::string &subdev_name){ +    if (not _rx_dboards.has_key(subdev_name)) throw std::invalid_argument( +        str(boost::format("Unknown rx subdev name %s") % subdev_name) +    ); +    //get a link to the rx subdev proxy +    return _rx_dboards[subdev_name]->get_link(); +} + +wax::obj dboard_manager_impl::get_tx_subdev(const std::string &subdev_name){ +    if (not _tx_dboards.has_key(subdev_name)) throw std::invalid_argument( +        str(boost::format("Unknown tx subdev name %s") % subdev_name) +    ); +    //get a link to the tx subdev proxy +    return _tx_dboards[subdev_name]->get_link(); +} + +void dboard_manager_impl::set_nice_dboard_if(void){ +    //make a list of possible unit types +    std::vector<dboard_iface::unit_t> units = boost::assign::list_of +        (dboard_iface::UNIT_RX) +        (dboard_iface::UNIT_TX) +    ; + +    //set nice settings on each unit +    BOOST_FOREACH(dboard_iface::unit_t unit, units){ +        _iface->set_gpio_ddr(unit, 0x0000); //all inputs +        _iface->set_gpio_out(unit, 0x0000); //all low +        _iface->set_pin_ctrl(unit, 0x0000); //all gpio +        _iface->set_clock_enabled(unit, false); //clock off +    } + +    //disable all rx subdevices +    BOOST_FOREACH(const std::string &sd_name, this->get_rx_subdev_names()){ +        this->get_rx_subdev(sd_name)[SUBDEV_PROP_ENABLED] = false; +    } + +    //disable all tx subdevices +    BOOST_FOREACH(const std::string &sd_name, this->get_tx_subdev_names()){ +        this->get_tx_subdev(sd_name)[SUBDEV_PROP_ENABLED] = false; +    } +} diff --git a/host/lib/usrp/dsp_utils.cpp b/host/lib/usrp/dsp_utils.cpp new file mode 100644 index 000000000..2553e4a25 --- /dev/null +++ b/host/lib/usrp/dsp_utils.cpp @@ -0,0 +1,136 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; + +template <class T> T ceil_log2(T num){ +    return std::ceil(std::log(num)/std::log(T(2))); +} + +/*! + *     3                   2                   1                   0 + *   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + *  +-------------------------------+-------+-------+-------+-------+ + *  |                                               | DDC0Q | DDC0I | + *  +-------------------------------+-------+-------+-------+-------+ + */ +boost::uint32_t dsp_type1::calc_rx_mux_word(subdev_conn_t subdev_conn){ +    switch(subdev_conn){ +    case SUBDEV_CONN_COMPLEX_IQ: return (0x1 << 4) | (0x0 << 0); //DDC0Q=ADC0Q, DDC0I=ADC0I +    case SUBDEV_CONN_COMPLEX_QI: return (0x0 << 4) | (0x1 << 0); //DDC0Q=ADC0I, DDC0I=ADC0Q +    case SUBDEV_CONN_REAL_I:     return (0xf << 4) | (0x0 << 0); //DDC0Q=ZERO,  DDC0I=ADC0I +    case SUBDEV_CONN_REAL_Q:     return (0x1 << 4) | (0xf << 0); //DDC0Q=ADC0Q, DDC0I=ZERO +    default:                     UHD_THROW_INVALID_CODE_PATH(); +    } +} + +/*! + *     3                   2                   1                   0 + *   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + *  +-------------------------------+-------+-------+-------+-------+ + *  |                                               | DAC0Q | DAC0I | + *  +-------------------------------+-------+-------+-------+-------+ + */ +boost::uint32_t dsp_type1::calc_tx_mux_word(subdev_conn_t subdev_conn){ +    switch(subdev_conn){ +    case SUBDEV_CONN_COMPLEX_IQ: return (0x1 << 4) | (0x0 << 0); //DAC0Q=DUC0Q, DAC0I=DUC0I +    case SUBDEV_CONN_COMPLEX_QI: return (0x0 << 4) | (0x1 << 0); //DAC0Q=DUC0I, DAC0I=DUC0Q +    case SUBDEV_CONN_REAL_I:     return (0xf << 4) | (0x0 << 0); //DAC0Q=ZERO,  DAC0I=DUC0I +    case SUBDEV_CONN_REAL_Q:     return (0x0 << 4) | (0xf << 0); //DAC0Q=DUC0I, DAC0I=ZERO +    default:                     UHD_THROW_INVALID_CODE_PATH(); +    } +} + +boost::uint32_t dsp_type1::calc_cordic_word_and_update( +    double &freq, +    double codec_rate +){ +    UHD_ASSERT_THROW(std::abs(freq) <= codec_rate/2.0); +    static const double scale_factor = std::pow(2.0, 32); + +    //calculate the freq register word (signed) +    boost::int32_t freq_word = boost::int32_t(boost::math::round((freq / codec_rate) * scale_factor)); + +    //update the actual frequency +    freq = (double(freq_word) / scale_factor) * codec_rate; + +    return boost::uint32_t(freq_word); +} + +boost::uint32_t dsp_type1::calc_cic_filter_word(unsigned rate){ +    int hb0 = 0, hb1 = 0; +    if (not (rate & 0x1)){ +        hb0 = 1; +        rate /= 2; +    } +    if (not (rate & 0x1)){ +        hb1 = 1; +        rate /= 2; +    } +    return (hb1 << 9) | (hb0 << 8) | (rate & 0xff); +} + +boost::uint32_t dsp_type1::calc_iq_scale_word( +    boost::int16_t i, boost::int16_t q +){ +    return (boost::uint32_t(i) << 16) | (boost::uint32_t(q) << 0); +} + +boost::uint32_t dsp_type1::calc_iq_scale_word(unsigned rate){ +    // Calculate CIC interpolation (i.e., without halfband interpolators) +    unsigned tmp_rate = calc_cic_filter_word(rate) & 0xff; + +    // Calculate closest multiplier constant to reverse gain absent scale multipliers +    double rate_cubed = std::pow(double(tmp_rate), 3); +    boost::int16_t scale = boost::math::iround((4096*std::pow(2, ceil_log2(rate_cubed)))/(1.65*rate_cubed)); +    return calc_iq_scale_word(scale, scale); +} + +boost::uint32_t dsp_type1::calc_stream_cmd_word(const stream_cmd_t &stream_cmd){ +    UHD_ASSERT_THROW(stream_cmd.num_samps <= 0x3fffffff); + +    //setup the mode to instruction flags +    typedef boost::tuple<bool, bool, bool> inst_t; +    static const uhd::dict<stream_cmd_t::stream_mode_t, inst_t> mode_to_inst = boost::assign::map_list_of +                                                            //reload, chain, samps +        (stream_cmd_t::STREAM_MODE_START_CONTINUOUS,   inst_t(true,  true,  false)) +        (stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS,    inst_t(false, false, false)) +        (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE, inst_t(false, false, true)) +        (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE, inst_t(false, true,  true)) +    ; + +    //setup the instruction flag values +    bool inst_reload, inst_chain, inst_samps; +    boost::tie(inst_reload, inst_chain, inst_samps) = mode_to_inst[stream_cmd.stream_mode]; + +    //calculate the word from flags and length +    boost::uint32_t word = 0; +    word |= boost::uint32_t((stream_cmd.stream_now)? 1 : 0) << 31; +    word |= boost::uint32_t((inst_chain)?            1 : 0) << 30; +    word |= boost::uint32_t((inst_reload)?           1 : 0) << 29; +    word |= (inst_samps)? stream_cmd.num_samps : ((inst_chain)? 1 : 0); +    return word; +} diff --git a/host/lib/usrp/mboard_eeprom.cpp b/host/lib/usrp/mboard_eeprom.cpp new file mode 100644 index 000000000..863a80191 --- /dev/null +++ b/host/lib/usrp/mboard_eeprom.cpp @@ -0,0 +1,280 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/mac_addr.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/foreach.hpp> +#include <cstddef> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const size_t SERIAL_LEN = 9; +static const size_t NAME_MAX_LEN = 32 - SERIAL_LEN; + +/*********************************************************************** + * Utility functions + **********************************************************************/ + +//! create a string from a byte vector, return empty if invalid ascii +static const std::string bytes_to_string(const byte_vector_t &bytes){ +    std::string out; +    BOOST_FOREACH(boost::uint8_t byte, bytes){ +        if (byte < 32 or byte > 127) return out; +        out += byte; +    } +    return out; +} + +//! create a byte vector from a string, null terminate unless max length +static const byte_vector_t string_to_bytes(const std::string &string, size_t max_length){ +    byte_vector_t bytes; +    for (size_t i = 0; i < std::min(string.size(), max_length); i++){ +        bytes.push_back(string[i]); +    } +    if (bytes.size() < max_length - 1) bytes.push_back('\0'); +    return bytes; +} + +/*********************************************************************** + * Implementation of N100 load/store + **********************************************************************/ +static const boost::uint8_t N100_EEPROM_ADDR = 0x50; + +static const uhd::dict<std::string, boost::uint8_t> USRP_N100_OFFSETS = boost::assign::map_list_of +    ("rev-lsb-msb", 0x00) +    ("mac-addr", 0x02) +    ("ip-addr", 0x0C) +    //leave space here for other addresses (perhaps) +    ("serial", 0x18) +    ("name", 0x18 + SERIAL_LEN) +; + +static void load_n100(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    //extract the revision number +    byte_vector_t rev_lsb_msb = iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["rev-lsb-msb"], 2); +    boost::uint16_t rev = (boost::uint16_t(rev_lsb_msb.at(0)) << 0) | (boost::uint16_t(rev_lsb_msb.at(1)) << 8); +    mb_eeprom["rev"] = boost::lexical_cast<std::string>(rev); + +    //extract the addresses +    mb_eeprom["mac-addr"] = mac_addr_t::from_bytes(iface.read_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["mac-addr"], 6 +    )).to_string(); + +    boost::asio::ip::address_v4::bytes_type ip_addr_bytes; +    std::copy(iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["ip-addr"], 4), ip_addr_bytes); +    mb_eeprom["ip-addr"] = boost::asio::ip::address_v4(ip_addr_bytes).to_string(); + +    //extract the serial +    mb_eeprom["serial"] = bytes_to_string(iface.read_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["serial"], SERIAL_LEN +    )); + +    //extract the name +    mb_eeprom["name"] = bytes_to_string(iface.read_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["name"], NAME_MAX_LEN +    )); + +    //empty serial correction: use the mac address +    if (mb_eeprom["serial"].empty()) mb_eeprom["serial"] = mb_eeprom["mac-addr"]; +} + +static void store_n100(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    //parse the revision number +    if (mb_eeprom.has_key("rev")){ +        boost::uint16_t rev = boost::lexical_cast<boost::uint16_t>(mb_eeprom["rev"]); +        byte_vector_t rev_lsb_msb = boost::assign::list_of +            (boost::uint8_t(rev >> 0)) +            (boost::uint8_t(rev >> 8)) +        ; +        iface.write_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["rev-lsb-msb"], rev_lsb_msb); +    } + +    //store the addresses +    if (mb_eeprom.has_key("mac-addr")) iface.write_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["mac-addr"], +        mac_addr_t::from_string(mb_eeprom["mac-addr"]).to_bytes() +    ); + +    if (mb_eeprom.has_key("ip-addr")){ +        byte_vector_t ip_addr_bytes(4); +        std::copy(boost::asio::ip::address_v4::from_string(mb_eeprom["ip-addr"]).to_bytes(), ip_addr_bytes); +        iface.write_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["ip-addr"], ip_addr_bytes); +    } + +    //store the serial +    if (mb_eeprom.has_key("serial")) iface.write_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["serial"], +        string_to_bytes(mb_eeprom["serial"], SERIAL_LEN) +    ); + +    //store the name +    if (mb_eeprom.has_key("name")) iface.write_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["name"], +        string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) +    ); +} + +/*********************************************************************** + * Implementation of B000 load/store + **********************************************************************/ +static const boost::uint8_t B000_EEPROM_ADDR = 0x50; +static const size_t B000_SERIAL_LEN = 8; + +static const uhd::dict<std::string, boost::uint8_t> USRP_B000_OFFSETS = boost::assign::map_list_of +    ("serial", 0xf8) +    ("name", 0xf8 - NAME_MAX_LEN) +; + +static void load_b000(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    //extract the serial +    mb_eeprom["serial"] = bytes_to_string(iface.read_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["serial"], B000_SERIAL_LEN +    )); + +    //extract the name +    mb_eeprom["name"] = bytes_to_string(iface.read_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["name"], NAME_MAX_LEN +    )); +} + +static void store_b000(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    //store the serial +    if (mb_eeprom.has_key("serial")) iface.write_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["serial"], +        string_to_bytes(mb_eeprom["serial"], B000_SERIAL_LEN) +    ); + +    //store the name +    if (mb_eeprom.has_key("name")) iface.write_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["name"], +        string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) +    ); +} +/*********************************************************************** + * Implementation of E100 load/store + **********************************************************************/ +static const boost::uint8_t E100_EEPROM_ADDR = 0x51; + +struct e100_eeprom_map{ +    boost::uint16_t vendor; +    boost::uint16_t device; +    unsigned char revision; +    unsigned char content; +    unsigned char model[8]; +    unsigned char env_var[16]; +    unsigned char env_setting[64]; +    unsigned char serial[10]; +    unsigned char name[NAME_MAX_LEN]; +}; + +template <typename T> static const byte_vector_t to_bytes(const T &item){ +    return byte_vector_t( +        reinterpret_cast<const byte_vector_t::value_type *>(&item), +        reinterpret_cast<const byte_vector_t::value_type *>(&item)+sizeof(item) +    ); +} + +#define sizeof_member(struct_name, member_name) \ +    sizeof(reinterpret_cast<struct_name*>(NULL)->member_name) + +static void load_e100(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    const size_t num_bytes = offsetof(e100_eeprom_map, model); +    byte_vector_t map_bytes = iface.read_eeprom(E100_EEPROM_ADDR, 0, num_bytes); +    e100_eeprom_map map; std::memcpy(&map, &map_bytes[0], map_bytes.size()); + +    mb_eeprom["vendor"] = boost::lexical_cast<std::string>(uhd::ntohx(map.vendor)); +    mb_eeprom["device"] = boost::lexical_cast<std::string>(uhd::ntohx(map.device)); +    mb_eeprom["revision"] = boost::lexical_cast<std::string>(unsigned(map.revision)); +    mb_eeprom["content"] = boost::lexical_cast<std::string>(unsigned(map.content)); + +    #define load_e100_string_xx(key) mb_eeprom[#key] = bytes_to_string(iface.read_eeprom( \ +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, key), sizeof_member(e100_eeprom_map, key) \ +    )); + +    load_e100_string_xx(model); +    load_e100_string_xx(env_var); +    load_e100_string_xx(env_setting); +    load_e100_string_xx(serial); +    load_e100_string_xx(name); +} + +static void store_e100(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + +    if (mb_eeprom.has_key("vendor")) iface.write_eeprom( +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, vendor), +        to_bytes(uhd::htonx(boost::lexical_cast<boost::uint16_t>(mb_eeprom["vendor"]))) +    ); + +    if (mb_eeprom.has_key("device")) iface.write_eeprom( +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, device), +        to_bytes(uhd::htonx(boost::lexical_cast<boost::uint16_t>(mb_eeprom["device"]))) +    ); + +    if (mb_eeprom.has_key("revision")) iface.write_eeprom( +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, revision), +        byte_vector_t(1, boost::lexical_cast<unsigned>(mb_eeprom["revision"])) +    ); + +    if (mb_eeprom.has_key("content")) iface.write_eeprom( +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, content), +        byte_vector_t(1, boost::lexical_cast<unsigned>(mb_eeprom["content"])) +    ); + +    #define store_e100_string_xx(key) if (mb_eeprom.has_key(#key)) iface.write_eeprom( \ +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, key), \ +        string_to_bytes(mb_eeprom[#key], sizeof_member(e100_eeprom_map, key)) \ +    ); + +    store_e100_string_xx(model); +    store_e100_string_xx(env_var); +    store_e100_string_xx(env_setting); +    store_e100_string_xx(serial); +    store_e100_string_xx(name); + +} + +/*********************************************************************** + * Implementation of mboard eeprom + **********************************************************************/ +mboard_eeprom_t::mboard_eeprom_t(void){ +    /* NOP */ +} + +mboard_eeprom_t::mboard_eeprom_t(i2c_iface &iface, map_type map){ +    switch(map){ +    case MAP_N100: load_n100(*this, iface); break; +    case MAP_B000: load_b000(*this, iface); break; +    case MAP_E100: load_e100(*this, iface); break; +    } +} + +void mboard_eeprom_t::commit(i2c_iface &iface, map_type map){ +    switch(map){ +    case MAP_N100: store_n100(*this, iface); break; +    case MAP_B000: store_b000(*this, iface); break; +    case MAP_E100: store_e100(*this, iface); break; +    } +} diff --git a/host/lib/usrp/misc_utils.cpp b/host/lib/usrp/misc_utils.cpp new file mode 100644 index 000000000..5856d706f --- /dev/null +++ b/host/lib/usrp/misc_utils.cpp @@ -0,0 +1,225 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/gain_group.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/codec_props.hpp> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * codec gain group helper functions: + *    do this so we dont have to bind a templated function + **********************************************************************/ +static gain_range_t get_codec_gain_range(wax::obj codec, const std::string &name){ +    return codec[named_prop_t(CODEC_PROP_GAIN_RANGE, name)].as<gain_range_t>(); +} + +static float get_codec_gain_i(wax::obj codec, const std::string &name){ +    return codec[named_prop_t(CODEC_PROP_GAIN_I, name)].as<float>(); +} + +static float get_codec_gain_q(wax::obj codec, const std::string &name){ +    return codec[named_prop_t(CODEC_PROP_GAIN_Q, name)].as<float>(); +} + +static void set_codec_gain_both(wax::obj codec, const std::string &name, float gain){ +    codec[named_prop_t(CODEC_PROP_GAIN_I, name)] = gain; +    codec[named_prop_t(CODEC_PROP_GAIN_Q, name)] = gain; +} + +static void set_codec_gain_i(wax::obj codec, const std::string &name, float gain){ +    codec[named_prop_t(CODEC_PROP_GAIN_I, name)] = gain; +} + +static void set_codec_gain_q(wax::obj codec, const std::string &name, float gain){ +    codec[named_prop_t(CODEC_PROP_GAIN_Q, name)] = gain; +} + +/*********************************************************************** + * subdev gain group helper functions: + *    do this so we dont have to bind a templated function + **********************************************************************/ +static float get_subdev_gain(wax::obj subdev, const std::string &name){ +    return subdev[named_prop_t(SUBDEV_PROP_GAIN, name)].as<float>(); +} + +static gain_range_t get_subdev_gain_range(wax::obj subdev, const std::string &name){ +    return subdev[named_prop_t(SUBDEV_PROP_GAIN_RANGE, name)].as<gain_range_t>(); +} + +static void set_subdev_gain(wax::obj subdev, const std::string &name, float gain){ +    subdev[named_prop_t(SUBDEV_PROP_GAIN, name)] = gain; +} + +/*********************************************************************** + * gain group factory function for usrp + **********************************************************************/ +gain_group::sptr usrp::make_gain_group( +    const dboard_id_t &dboard_id, +    wax::obj subdev, wax::obj codec, +    gain_group_policy_t gain_group_policy +){ +    const size_t subdev_gain_priority = 1; +    const size_t codec_gain_priority = (gain_group_policy == GAIN_GROUP_POLICY_RX)? +        (subdev_gain_priority - 1): //RX policy, codec gains fill last (lower priority) +        (subdev_gain_priority + 1); //TX policy, codec gains fill first (higher priority) +    const std::string subdev_prefix = dboard_id.to_cname() + "-"; +    const std::string codec_prefix = (gain_group_policy == GAIN_GROUP_POLICY_RX)? "ADC-" : "DAC-"; + +    gain_group::sptr gg = gain_group::make(); +    gain_fcns_t fcns; +    //add all the subdev gains first (antenna to dsp order) +    BOOST_FOREACH(const std::string &name, subdev[SUBDEV_PROP_GAIN_NAMES].as<prop_names_t>()){ +        fcns.get_range = boost::bind(&get_subdev_gain_range, subdev, name); +        fcns.get_value = boost::bind(&get_subdev_gain, subdev, name); +        fcns.set_value = boost::bind(&set_subdev_gain, subdev, name, _1); +        gg->register_fcns(subdev_prefix+name, fcns, subdev_gain_priority); +    } +    //add all the codec gains last (antenna to dsp order) +    BOOST_FOREACH(const std::string &name, codec[CODEC_PROP_GAIN_NAMES].as<prop_names_t>()){ +        fcns.get_range = boost::bind(&get_codec_gain_range, codec, name); + +        //register the value functions depending upon the connection type +        switch(subdev[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>()){ +        case SUBDEV_CONN_COMPLEX_IQ: +        case SUBDEV_CONN_COMPLEX_QI: +            fcns.get_value = boost::bind(&get_codec_gain_i, codec, name); //same as Q +            fcns.set_value = boost::bind(&set_codec_gain_both, codec, name, _1); //sets both +            break; + +        case SUBDEV_CONN_REAL_I: +            fcns.get_value = boost::bind(&get_codec_gain_i, codec, name); +            fcns.set_value = boost::bind(&set_codec_gain_i, codec, name, _1); +            break; + +        case SUBDEV_CONN_REAL_Q: +            fcns.get_value = boost::bind(&get_codec_gain_q, codec, name); +            fcns.set_value = boost::bind(&set_codec_gain_q, codec, name, _1); +            break; +        } +        gg->register_fcns(codec_prefix+name, fcns, codec_gain_priority); +    } +    return gg; +} + +/*********************************************************************** + * verify subdev specs + **********************************************************************/ +static void verify_xx_subdev_spec( +    mboard_prop_t dboard_names_prop, +    mboard_prop_t dboard_prop, +    subdev_spec_t &subdev_spec, +    wax::obj mboard, +    std::string xx_type +){ +    try{ +        prop_names_t dboard_names = mboard[dboard_names_prop].as<prop_names_t>(); +        UHD_ASSERT_THROW(dboard_names.size() > 0); //well i hope there is a dboard + +        //the subdevice specification is empty: handle automatic +        if (subdev_spec.empty()){ +            BOOST_FOREACH(const std::string &db_name, dboard_names){ +                wax::obj dboard = mboard[named_prop_t(dboard_prop, db_name)]; + +                //if the dboard slot is populated, take the first subdevice +                if (dboard[DBOARD_PROP_DBOARD_ID].as<dboard_id_t>() != dboard_id_t::none()){ +                    std::string sd_name = dboard[DBOARD_PROP_SUBDEV_NAMES].as<prop_names_t>().front(); +                    subdev_spec.push_back(subdev_spec_pair_t(db_name, sd_name)); +                    break; +                } +            } + +            //didnt find any populated dboards: add the first subdevice +            if (subdev_spec.empty()){ +                std::string db_name = dboard_names.front(); +                wax::obj dboard = mboard[named_prop_t(dboard_prop, db_name)]; +                std::string sd_name = dboard[DBOARD_PROP_SUBDEV_NAMES].as<prop_names_t>().front(); +                subdev_spec.push_back(subdev_spec_pair_t(db_name, sd_name)); +            } +        } + +        //sanity check that the dboard/subdevice names exist for this mboard +        BOOST_FOREACH(subdev_spec_pair_t &pair, subdev_spec){ +            //empty db name means select dboard automatically +            if (pair.db_name.empty()){ +                if (dboard_names.size() != 1) throw std::runtime_error( +                    "A daughterboard name must be provided for multi-slot motherboards: " + subdev_spec.to_string() +                ); +                pair.db_name = dboard_names.front(); +            } +            uhd::assert_has(dboard_names, pair.db_name, xx_type + " dboard name"); +            wax::obj dboard = mboard[named_prop_t(dboard_prop, pair.db_name)]; +            prop_names_t subdev_names = dboard[DBOARD_PROP_SUBDEV_NAMES].as<prop_names_t>(); + +            //empty sd name means select the subdev automatically +            if (pair.sd_name.empty()){ +                if (subdev_names.size() != 1) throw std::runtime_error( +                    "A subdevice name must be provided for multi-subdev daughterboards: " + subdev_spec.to_string() +                ); +                pair.sd_name = subdev_names.front(); +            } +            uhd::assert_has(subdev_names, pair.sd_name, xx_type + " subdev name"); +        } +    }catch(const std::exception &e){ +        throw std::runtime_error(str(boost::format( +            "Validate %s subdev spec failed: %s\n    %s" +        ) % xx_type % subdev_spec.to_string() % e.what())); +    } + +    //now use the subdev spec to enable the subdevices in use and vice-versa +    BOOST_FOREACH(const std::string &db_name, mboard[dboard_names_prop].as<prop_names_t>()){ +        wax::obj dboard = mboard[named_prop_t(dboard_prop, db_name)]; +        BOOST_FOREACH(const std::string &sd_name, dboard[DBOARD_PROP_SUBDEV_NAMES].as<prop_names_t>()){ +            try{ +                bool enable = std::has(subdev_spec, subdev_spec_pair_t(db_name, sd_name)); +                dboard[named_prop_t(DBOARD_PROP_SUBDEV, sd_name)][SUBDEV_PROP_ENABLED] = enable; +            } +            catch(const std::exception &e){ +                throw std::runtime_error(str(boost::format( +                    "Cannot set enabled property on subdevice %s:%s\n    %s" +                ) % db_name % sd_name % e.what())); +            } +        } +    } +} + +void usrp::verify_rx_subdev_spec(subdev_spec_t &subdev_spec, wax::obj mboard){ +    return verify_xx_subdev_spec( +        MBOARD_PROP_RX_DBOARD_NAMES, +        MBOARD_PROP_RX_DBOARD, +        subdev_spec, mboard, "rx" +    ); +} + +void usrp::verify_tx_subdev_spec(subdev_spec_t &subdev_spec, wax::obj mboard){ +    return verify_xx_subdev_spec( +        MBOARD_PROP_TX_DBOARD_NAMES, +        MBOARD_PROP_TX_DBOARD, +        subdev_spec, mboard, "tx" +    ); +} diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp new file mode 100644 index 000000000..876f1a3fc --- /dev/null +++ b/host/lib/usrp/multi_usrp.cpp @@ -0,0 +1,442 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "wrapper_utils.hpp" +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/usrp/tune_helper.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/utils/gain_group.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/usrp/device_props.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <boost/thread.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <stdexcept> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +const std::string multi_usrp::ALL_GAINS = ""; + +/*********************************************************************** + * Simple USRP Implementation + **********************************************************************/ +class multi_usrp_impl : public multi_usrp{ +public: +    multi_usrp_impl(const device_addr_t &addr){ +        _dev = device::make(addr); +    } + +    device::sptr get_device(void){ +        return _dev; +    } + +    /******************************************************************* +     * Mboard methods +     ******************************************************************/ +    std::string get_pp_string(void){ +        std::string buff = str(boost::format( +            "Multi USRP:\n" +            "  Device: %s\n" +        ) +            % (*_dev)[DEVICE_PROP_NAME].as<std::string>() +        ); +        for (size_t m = 0; m < get_num_mboards(); m++){ +            buff += str(boost::format( +                "  Mboard %d: %s\n" +            ) % m +                % _mboard(m)[MBOARD_PROP_NAME].as<std::string>() +            ); +        } + +        //----------- rx side of life ---------------------------------- +        for (size_t m = 0, chan = 0; m < get_num_mboards(); m++){ +            buff += str(boost::format( +                "  RX DSP %d: %s\n" +            ) % m +                % _rx_dsp(m)[DSP_PROP_NAME].as<std::string>() +            ); +            for (; chan < (m + 1)*get_rx_subdev_spec(m).size(); chan++){ +                buff += str(boost::format( +                    "  RX Channel: %u\n" +                    "    RX Dboard: %s\n" +                    "    RX Subdev: %s\n" +                ) % chan +                    % _rx_dboard(chan)[DBOARD_PROP_NAME].as<std::string>() +                    % _rx_subdev(chan)[SUBDEV_PROP_NAME].as<std::string>() +                ); +            } +        } + +        //----------- tx side of life ---------------------------------- +        for (size_t m = 0, chan = 0; m < get_num_mboards(); m++){ +            buff += str(boost::format( +                "  TX DSP %d: %s\n" +            ) % m +                % _tx_dsp(m)[DSP_PROP_NAME].as<std::string>() +            ); +            for (; chan < (m + 1)*get_tx_subdev_spec(m).size(); chan++){ +                buff += str(boost::format( +                    "  TX Channel: %u\n" +                    "    TX Dboard: %s\n" +                    "    TX Subdev: %s\n" +                ) % chan +                    % _tx_dboard(chan)[DBOARD_PROP_NAME].as<std::string>() +                    % _tx_subdev(chan)[SUBDEV_PROP_NAME].as<std::string>() +                ); +            } +        } + +        return buff; +    } + +    std::string get_mboard_name(size_t mboard){ +        return _mboard(mboard)[MBOARD_PROP_NAME].as<std::string>(); +    } + +    time_spec_t get_time_now(void){ +        return _mboard(0)[MBOARD_PROP_TIME_NOW].as<time_spec_t>(); +    } + +    void set_time_next_pps(const time_spec_t &time_spec){ +        for (size_t m = 0; m < get_num_mboards(); m++){ +            _mboard(m)[MBOARD_PROP_TIME_NEXT_PPS] = time_spec; +        } +    } + +    void set_time_unknown_pps(const time_spec_t &time_spec){ +        std::cout << "Set time with unknown pps edge:" << std::endl; +        std::cout << "    1) set times next pps (race condition)" << std::endl; +        set_time_next_pps(time_spec); +        boost::this_thread::sleep(boost::posix_time::seconds(1)); + +        std::cout << "    2) catch seconds rollover at pps edge" << std::endl; +        time_t last_secs = 0, curr_secs = 0; +        while(curr_secs == last_secs){ +            last_secs = curr_secs; +            curr_secs = get_time_now().get_full_secs(); +        } + +        std::cout << "    3) set times next pps (synchronously)" << std::endl; +        set_time_next_pps(time_spec); +        boost::this_thread::sleep(boost::posix_time::seconds(1)); + +        //verify that the time registers are read to be within a few RTT +        for (size_t m = 1; m < get_num_mboards(); m++){ +            time_spec_t time_0 = _mboard(0)[MBOARD_PROP_TIME_NOW].as<time_spec_t>(); +            time_spec_t time_i = _mboard(m)[MBOARD_PROP_TIME_NOW].as<time_spec_t>(); +            if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)){ //10 ms: greater than RTT but not too big +                uhd::warning::post(str(boost::format( +                    "Detected time deviation between board %d and board 0.\n" +                    "Board 0 time is %f seconds.\n" +                    "Board %d time is %f seconds.\n" +                ) % m % time_0.get_real_secs() % m % time_i.get_real_secs())); +            } +        } +    } + +    bool get_time_synchronized(void){ +        for (size_t m = 1; m < get_num_mboards(); m++){ +            time_spec_t time_0 = _mboard(0)[MBOARD_PROP_TIME_NOW].as<time_spec_t>(); +            time_spec_t time_i = _mboard(m)[MBOARD_PROP_TIME_NOW].as<time_spec_t>(); +            if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) return false; +        } +        return true; +    } + +    void issue_stream_cmd(const stream_cmd_t &stream_cmd){ +        for (size_t m = 0; m < get_num_mboards(); m++){ +            _mboard(m)[MBOARD_PROP_STREAM_CMD] = stream_cmd; +        } +    } + +    void set_clock_config(const clock_config_t &clock_config, size_t mboard){ +        if (mboard != ALL_MBOARDS){ +            _mboard(mboard)[MBOARD_PROP_CLOCK_CONFIG] = clock_config; +            return; +        } +        for (size_t m = 0; m < get_num_mboards(); m++){ +            set_clock_config(clock_config, m); +        } +    } + +    size_t get_num_mboards(void){ +        return (*_dev)[DEVICE_PROP_MBOARD_NAMES].as<prop_names_t>().size(); +    } + +    /******************************************************************* +     * RX methods +     ******************************************************************/ +    void set_rx_subdev_spec(const subdev_spec_t &spec, size_t mboard){ +        if (mboard != ALL_MBOARDS){ +            _mboard(mboard)[MBOARD_PROP_RX_SUBDEV_SPEC] = spec; +            return; +        } +        for (size_t m = 0; m < get_num_mboards(); m++){ +            set_rx_subdev_spec(spec, m); +        } +    } + +    subdev_spec_t get_rx_subdev_spec(size_t mboard){ +        return _mboard(mboard)[MBOARD_PROP_RX_SUBDEV_SPEC].as<subdev_spec_t>(); +    } + +    size_t get_rx_num_channels(void){ +        return rx_cpm()*get_num_mboards(); //total num channels +    } + +    std::string get_rx_subdev_name(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_NAME].as<std::string>(); +    } + +    void set_rx_rate(double rate){ +        for (size_t m = 0; m < get_num_mboards(); m++){ +            _rx_dsp(m)[DSP_PROP_HOST_RATE] = rate; +        } +        do_samp_rate_warning_message(rate, get_rx_rate(), "RX"); +    } + +    double get_rx_rate(void){ +        return _rx_dsp(0)[DSP_PROP_HOST_RATE].as<double>(); +    } + +    tune_result_t set_rx_freq(const tune_request_t &tune_request, size_t chan){ +        tune_result_t r = tune_rx_subdev_and_dsp(_rx_subdev(chan), _rx_dsp(chan/rx_cpm()), chan%rx_cpm(), tune_request); +        do_tune_freq_warning_message(tune_request.target_freq, get_rx_freq(chan), "RX"); +        return r; +    } + +    double get_rx_freq(size_t chan){ +        return derive_freq_from_rx_subdev_and_dsp(_rx_subdev(chan), _rx_dsp(chan/rx_cpm()), chan%rx_cpm()); +    } + +    freq_range_t get_rx_freq_range(size_t chan){ +        return add_dsp_shift(_rx_subdev(chan)[SUBDEV_PROP_FREQ_RANGE].as<freq_range_t>(), _rx_dsp(chan/rx_cpm())); +    } + +    void set_rx_gain(float gain, const std::string &name, size_t chan){ +        return _rx_gain_group(chan)->set_value(gain, name); +    } + +    float get_rx_gain(const std::string &name, size_t chan){ +        return _rx_gain_group(chan)->get_value(name); +    } + +    gain_range_t get_rx_gain_range(const std::string &name, size_t chan){ +        return _rx_gain_group(chan)->get_range(name); +    } + +    std::vector<std::string> get_rx_gain_names(size_t chan){ +        return _rx_gain_group(chan)->get_names(); +    } + +    void set_rx_antenna(const std::string &ant, size_t chan){ +        _rx_subdev(chan)[SUBDEV_PROP_ANTENNA] = ant; +    } + +    std::string get_rx_antenna(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_ANTENNA].as<std::string>(); +    } + +    std::vector<std::string> get_rx_antennas(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_ANTENNA_NAMES].as<prop_names_t>(); +    } + +    bool get_rx_lo_locked(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_LO_LOCKED].as<bool>(); +    } + +    void set_rx_bandwidth(double bandwidth, size_t chan){ +        _rx_subdev(chan)[SUBDEV_PROP_BANDWIDTH] = bandwidth; +    } + +    double get_rx_bandwidth(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_BANDWIDTH].as<double>(); +    } + +    float read_rssi(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_RSSI].as<float>(); +    } + +    dboard_iface::sptr get_rx_dboard_iface(size_t chan){ +        return _rx_dboard(chan)[DBOARD_PROP_DBOARD_IFACE].as<dboard_iface::sptr>(); +    } + +    /******************************************************************* +     * TX methods +     ******************************************************************/ +    void set_tx_subdev_spec(const subdev_spec_t &spec, size_t mboard){ +        if (mboard != ALL_MBOARDS){ +            _mboard(mboard)[MBOARD_PROP_TX_SUBDEV_SPEC] = spec; +            return; +        } +        for (size_t m = 0; m < get_num_mboards(); m++){ +            set_tx_subdev_spec(spec, m); +        } +    } + +    subdev_spec_t get_tx_subdev_spec(size_t mboard){ +        return _mboard(mboard)[MBOARD_PROP_TX_SUBDEV_SPEC].as<subdev_spec_t>(); +    } + +    std::string get_tx_subdev_name(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_NAME].as<std::string>(); +    } + +    size_t get_tx_num_channels(void){ +        return tx_cpm()*get_num_mboards(); //total num channels +    } + +    void set_tx_rate(double rate){ +        for (size_t m = 0; m < get_num_mboards(); m++){ +            _tx_dsp(m)[DSP_PROP_HOST_RATE] = rate; +        } +        do_samp_rate_warning_message(rate, get_tx_rate(), "TX"); +    } + +    double get_tx_rate(void){ +        return _tx_dsp(0)[DSP_PROP_HOST_RATE].as<double>(); +    } + +    tune_result_t set_tx_freq(const tune_request_t &tune_request, size_t chan){ +        tune_result_t r = tune_tx_subdev_and_dsp(_tx_subdev(chan), _tx_dsp(chan/tx_cpm()), chan%tx_cpm(), tune_request); +        do_tune_freq_warning_message(tune_request.target_freq, get_tx_freq(chan), "TX"); +        return r; +    } + +    double get_tx_freq(size_t chan){ +        return derive_freq_from_tx_subdev_and_dsp(_tx_subdev(chan), _tx_dsp(chan/tx_cpm()), chan%tx_cpm()); +    } + +    freq_range_t get_tx_freq_range(size_t chan){ +        return add_dsp_shift(_tx_subdev(chan)[SUBDEV_PROP_FREQ_RANGE].as<freq_range_t>(), _tx_dsp(chan/tx_cpm())); +    } + +    void set_tx_gain(float gain, const std::string &name, size_t chan){ +        return _tx_gain_group(chan)->set_value(gain, name); +    } + +    float get_tx_gain(const std::string &name, size_t chan){ +        return _tx_gain_group(chan)->get_value(name); +    } + +    gain_range_t get_tx_gain_range(const std::string &name, size_t chan){ +        return _tx_gain_group(chan)->get_range(name); +    } + +    std::vector<std::string> get_tx_gain_names(size_t chan){ +        return _tx_gain_group(chan)->get_names(); +    } + +    void set_tx_antenna(const std::string &ant, size_t chan){ +        _tx_subdev(chan)[SUBDEV_PROP_ANTENNA] = ant; +    } + +    std::string get_tx_antenna(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_ANTENNA].as<std::string>(); +    } + +    std::vector<std::string> get_tx_antennas(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_ANTENNA_NAMES].as<prop_names_t>(); +    } + +    bool get_tx_lo_locked(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_LO_LOCKED].as<bool>(); +    } + +    void set_tx_bandwidth(double bandwidth, size_t chan){ +        _tx_subdev(chan)[SUBDEV_PROP_BANDWIDTH] = bandwidth; +    } + +    double get_tx_bandwidth(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_BANDWIDTH].as<double>(); +    } + +    dboard_iface::sptr get_tx_dboard_iface(size_t chan){ +        return _tx_dboard(chan)[DBOARD_PROP_DBOARD_IFACE].as<dboard_iface::sptr>(); +    } + +private: +    device::sptr _dev; + +    size_t rx_cpm(void){ //channels per mboard +        size_t nchan = get_rx_subdev_spec(0).size(); +        for (size_t m = 1; m < get_num_mboards(); m++){ +            if (nchan != get_rx_subdev_spec(m).size()){ +                throw std::runtime_error("rx subdev spec size inconsistent across all mboards"); +            } +        } +        return nchan; +    } + +    size_t tx_cpm(void){ //channels per mboard +        size_t nchan = get_tx_subdev_spec(0).size(); +        for (size_t m = 1; m < get_num_mboards(); m++){ +            if (nchan != get_tx_subdev_spec(m).size()){ +                throw std::runtime_error("tx subdev spec size inconsistent across all mboards"); +            } +        } +        return nchan; +    } + +    wax::obj _mboard(size_t mboard){ +        std::string mb_name = (*_dev)[DEVICE_PROP_MBOARD_NAMES].as<prop_names_t>().at(mboard); +        return (*_dev)[named_prop_t(DEVICE_PROP_MBOARD, mb_name)]; +    } +    wax::obj _rx_dsp(size_t mboard){ +        return _mboard(mboard)[MBOARD_PROP_RX_DSP]; +    } +    wax::obj _tx_dsp(size_t mboard){ +        return _mboard(mboard)[MBOARD_PROP_TX_DSP]; +    } +    wax::obj _rx_dboard(size_t chan){ +        std::string db_name = get_rx_subdev_spec(chan/rx_cpm()).at(chan%rx_cpm()).db_name; +        return _mboard(chan/rx_cpm())[named_prop_t(MBOARD_PROP_RX_DBOARD, db_name)]; +    } +    wax::obj _tx_dboard(size_t chan){ +        std::string db_name = get_tx_subdev_spec(chan/tx_cpm()).at(chan%tx_cpm()).db_name; +        return _mboard(chan/tx_cpm())[named_prop_t(MBOARD_PROP_TX_DBOARD, db_name)]; +    } +    wax::obj _rx_subdev(size_t chan){ +        std::string sd_name = get_rx_subdev_spec(chan/rx_cpm()).at(chan%rx_cpm()).sd_name; +        return _rx_dboard(chan)[named_prop_t(DBOARD_PROP_SUBDEV, sd_name)]; +    } +    wax::obj _tx_subdev(size_t chan){ +        std::string sd_name = get_tx_subdev_spec(chan/tx_cpm()).at(chan%tx_cpm()).sd_name; +        return _tx_dboard(chan)[named_prop_t(DBOARD_PROP_SUBDEV, sd_name)]; +    } +    gain_group::sptr _rx_gain_group(size_t chan){ +        std::string sd_name = get_rx_subdev_spec(chan/rx_cpm()).at(chan%rx_cpm()).sd_name; +        return _rx_dboard(chan)[named_prop_t(DBOARD_PROP_GAIN_GROUP, sd_name)].as<gain_group::sptr>(); +    } +    gain_group::sptr _tx_gain_group(size_t chan){ +        std::string sd_name = get_tx_subdev_spec(chan/tx_cpm()).at(chan%tx_cpm()).sd_name; +        return _tx_dboard(chan)[named_prop_t(DBOARD_PROP_GAIN_GROUP, sd_name)].as<gain_group::sptr>(); +    } +}; + +/*********************************************************************** + * The Make Function + **********************************************************************/ +multi_usrp::sptr multi_usrp::make(const device_addr_t &dev_addr){ +    return sptr(new multi_usrp_impl(dev_addr)); +} diff --git a/host/lib/usrp/single_usrp.cpp b/host/lib/usrp/single_usrp.cpp new file mode 100644 index 000000000..a0456d1f0 --- /dev/null +++ b/host/lib/usrp/single_usrp.cpp @@ -0,0 +1,335 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "wrapper_utils.hpp" +#include <uhd/usrp/single_usrp.hpp> +#include <uhd/usrp/tune_helper.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/gain_group.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/usrp/device_props.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <stdexcept> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +const std::string single_usrp::ALL_GAINS = ""; + +/*********************************************************************** + * Simple USRP Implementation + **********************************************************************/ +class single_usrp_impl : public single_usrp{ +public: +    single_usrp_impl(const device_addr_t &addr){ +        _dev = device::make(addr); +    } + +    device::sptr get_device(void){ +        return _dev; +    } + +    /******************************************************************* +     * Mboard methods +     ******************************************************************/ +    std::string get_pp_string(void){ +        std::string buff = str(boost::format( +            "Single USRP:\n" +            "  Device: %s\n" +            "  Mboard: %s\n" +        ) +            % (*_dev)[DEVICE_PROP_NAME].as<std::string>() +            % _mboard()[MBOARD_PROP_NAME].as<std::string>() +        ); + +        //----------- rx side of life ---------------------------------- +        buff += str(boost::format( +            "  RX DSP: %s\n" +        ) +            % _rx_dsp()[DSP_PROP_NAME].as<std::string>() +        ); +        for (size_t chan = 0; chan < this->get_rx_subdev_spec().size(); chan++){ +            buff += str(boost::format( +                "  RX Channel: %u\n" +                "    RX Dboard: %s\n" +                "    RX Subdev: %s\n" +            ) % chan +                % _rx_dboard(chan)[DBOARD_PROP_NAME].as<std::string>() +                % _rx_subdev(chan)[SUBDEV_PROP_NAME].as<std::string>() +            ); +        } + +        //----------- tx side of life ---------------------------------- +        buff += str(boost::format( +            "  TX DSP: %s\n" +        ) +            % _tx_dsp()[DSP_PROP_NAME].as<std::string>() +        ); +        for (size_t chan = 0; chan < this->get_tx_subdev_spec().size(); chan++){ +            buff += str(boost::format( +                "  TX Channel: %u\n" +                "    TX Dboard: %s\n" +                "    TX Subdev: %s\n" +            ) % chan +                % _tx_dboard(chan)[DBOARD_PROP_NAME].as<std::string>() +                % _tx_subdev(chan)[SUBDEV_PROP_NAME].as<std::string>() +            ); +        } + +        return buff; +    } + +    std::string get_mboard_name(void){ +        return _mboard()[MBOARD_PROP_NAME].as<std::string>(); +    } + +    time_spec_t get_time_now(void){ +        return _mboard()[MBOARD_PROP_TIME_NOW].as<time_spec_t>(); +    } + +    void set_time_now(const time_spec_t &time_spec){ +        _mboard()[MBOARD_PROP_TIME_NOW] = time_spec; +    } + +    void set_time_next_pps(const time_spec_t &time_spec){ +        _mboard()[MBOARD_PROP_TIME_NEXT_PPS] = time_spec; +    } + +    void issue_stream_cmd(const stream_cmd_t &stream_cmd){ +        _mboard()[MBOARD_PROP_STREAM_CMD] = stream_cmd; +    } + +    void set_clock_config(const clock_config_t &clock_config){ +        _mboard()[MBOARD_PROP_CLOCK_CONFIG] = clock_config; +    } + +    /******************************************************************* +     * RX methods +     ******************************************************************/ +    void set_rx_subdev_spec(const subdev_spec_t &spec){ +        _mboard()[MBOARD_PROP_RX_SUBDEV_SPEC] = spec; +    } + +    subdev_spec_t get_rx_subdev_spec(void){ +        return _mboard()[MBOARD_PROP_RX_SUBDEV_SPEC].as<subdev_spec_t>(); +    } + +    std::string get_rx_subdev_name(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_NAME].as<std::string>(); +    } + +    void set_rx_rate(double rate){ +        _rx_dsp()[DSP_PROP_HOST_RATE] = rate; +        do_samp_rate_warning_message(rate, get_rx_rate(), "RX"); +    } + +    double get_rx_rate(void){ +        return _rx_dsp()[DSP_PROP_HOST_RATE].as<double>(); +    } + +    tune_result_t set_rx_freq(const tune_request_t &tune_request, size_t chan){ +        tune_result_t r = tune_rx_subdev_and_dsp(_rx_subdev(chan), _rx_dsp(), chan, tune_request); +        do_tune_freq_warning_message(tune_request.target_freq, get_rx_freq(chan), "RX"); +        return r; +    } + +    double get_rx_freq(size_t chan){ +        return derive_freq_from_rx_subdev_and_dsp(_rx_subdev(chan), _rx_dsp(), chan); +    } + +    freq_range_t get_rx_freq_range(size_t chan){ +        return add_dsp_shift(_rx_subdev(chan)[SUBDEV_PROP_FREQ_RANGE].as<freq_range_t>(), _rx_dsp()); +    } + +    void set_rx_gain(float gain, const std::string &name, size_t chan){ +        return _rx_gain_group(chan)->set_value(gain, name); +    } + +    float get_rx_gain(const std::string &name, size_t chan){ +        return _rx_gain_group(chan)->get_value(name); +    } + +    gain_range_t get_rx_gain_range(const std::string &name, size_t chan){ +        return _rx_gain_group(chan)->get_range(name); +    } + +    std::vector<std::string> get_rx_gain_names(size_t chan){ +        return _rx_gain_group(chan)->get_names(); +    } + +    void set_rx_antenna(const std::string &ant, size_t chan){ +        _rx_subdev(chan)[SUBDEV_PROP_ANTENNA] = ant; +    } + +    std::string get_rx_antenna(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_ANTENNA].as<std::string>(); +    } + +    std::vector<std::string> get_rx_antennas(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_ANTENNA_NAMES].as<prop_names_t>(); +    } + +    bool get_rx_lo_locked(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_LO_LOCKED].as<bool>(); +    } + +    void set_rx_bandwidth(double bandwidth, size_t chan){ +        _rx_subdev(chan)[SUBDEV_PROP_BANDWIDTH] = bandwidth; +    } + +    double get_rx_bandwidth(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_BANDWIDTH].as<double>(); +    } + +    float read_rssi(size_t chan){ +        return _rx_subdev(chan)[SUBDEV_PROP_RSSI].as<float>(); +    } + +    dboard_iface::sptr get_rx_dboard_iface(size_t chan){ +        return _rx_dboard(chan)[DBOARD_PROP_DBOARD_IFACE].as<dboard_iface::sptr>(); +    } + +    /******************************************************************* +     * TX methods +     ******************************************************************/ +    void set_tx_subdev_spec(const subdev_spec_t &spec){ +        _mboard()[MBOARD_PROP_TX_SUBDEV_SPEC] = spec; +    } + +    subdev_spec_t get_tx_subdev_spec(void){ +        return _mboard()[MBOARD_PROP_TX_SUBDEV_SPEC].as<subdev_spec_t>(); +    } + +    std::string get_tx_subdev_name(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_NAME].as<std::string>(); +    } + +    void set_tx_rate(double rate){ +        _tx_dsp()[DSP_PROP_HOST_RATE] = rate; +        do_samp_rate_warning_message(rate, get_tx_rate(), "TX"); +    } + +    double get_tx_rate(void){ +        return _tx_dsp()[DSP_PROP_HOST_RATE].as<double>(); +    } + +    tune_result_t set_tx_freq(const tune_request_t &tune_request, size_t chan){ +        tune_result_t r = tune_tx_subdev_and_dsp(_tx_subdev(chan), _tx_dsp(), chan, tune_request); +        do_tune_freq_warning_message(tune_request.target_freq, get_tx_freq(chan), "TX"); +        return r; +    } + +    double get_tx_freq(size_t chan){ +        return derive_freq_from_tx_subdev_and_dsp(_tx_subdev(chan), _tx_dsp(), chan); +    } + +    freq_range_t get_tx_freq_range(size_t chan){ +        return add_dsp_shift(_tx_subdev(chan)[SUBDEV_PROP_FREQ_RANGE].as<freq_range_t>(), _tx_dsp()); +    } + +    void set_tx_gain(float gain, const std::string &name, size_t chan){ +        return _tx_gain_group(chan)->set_value(gain, name); +    } + +    float get_tx_gain(const std::string &name, size_t chan){ +        return _tx_gain_group(chan)->get_value(name); +    } + +    gain_range_t get_tx_gain_range(const std::string &name, size_t chan){ +        return _tx_gain_group(chan)->get_range(name); +    } + +    std::vector<std::string> get_tx_gain_names(size_t chan){ +        return _tx_gain_group(chan)->get_names(); +    } + +    void set_tx_antenna(const std::string &ant, size_t chan){ +        _tx_subdev(chan)[SUBDEV_PROP_ANTENNA] = ant; +    } + +    std::string get_tx_antenna(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_ANTENNA].as<std::string>(); +    } + +    std::vector<std::string> get_tx_antennas(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_ANTENNA_NAMES].as<prop_names_t>(); +    } + +    bool get_tx_lo_locked(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_LO_LOCKED].as<bool>(); +    } + +    void set_tx_bandwidth(double bandwidth, size_t chan){ +        _tx_subdev(chan)[SUBDEV_PROP_BANDWIDTH] = bandwidth; +    } + +    double get_tx_bandwidth(size_t chan){ +        return _tx_subdev(chan)[SUBDEV_PROP_BANDWIDTH].as<double>(); +    } + +    dboard_iface::sptr get_tx_dboard_iface(size_t chan){ +        return _tx_dboard(chan)[DBOARD_PROP_DBOARD_IFACE].as<dboard_iface::sptr>(); +    } + +private: +    device::sptr _dev; +    wax::obj _mboard(void){ +        return (*_dev)[DEVICE_PROP_MBOARD]; +    } +    wax::obj _rx_dsp(void){ +        return _mboard()[MBOARD_PROP_RX_DSP]; +    } +    wax::obj _tx_dsp(void){ +        return _mboard()[MBOARD_PROP_TX_DSP]; +    } +    wax::obj _rx_dboard(size_t chan){ +        std::string db_name = this->get_rx_subdev_spec().at(chan).db_name; +        return _mboard()[named_prop_t(MBOARD_PROP_RX_DBOARD, db_name)]; +    } +    wax::obj _tx_dboard(size_t chan){ +        std::string db_name = this->get_tx_subdev_spec().at(chan).db_name; +        return _mboard()[named_prop_t(MBOARD_PROP_TX_DBOARD, db_name)]; +    } +    wax::obj _rx_subdev(size_t chan){ +        std::string sd_name = this->get_rx_subdev_spec().at(chan).sd_name; +        return _rx_dboard(chan)[named_prop_t(DBOARD_PROP_SUBDEV, sd_name)]; +    } +    wax::obj _tx_subdev(size_t chan){ +        std::string sd_name = this->get_tx_subdev_spec().at(chan).sd_name; +        return _tx_dboard(chan)[named_prop_t(DBOARD_PROP_SUBDEV, sd_name)]; +    } +    gain_group::sptr _rx_gain_group(size_t chan){ +        std::string sd_name = this->get_rx_subdev_spec().at(chan).sd_name; +        return _rx_dboard(chan)[named_prop_t(DBOARD_PROP_GAIN_GROUP, sd_name)].as<gain_group::sptr>(); +    } +    gain_group::sptr _tx_gain_group(size_t chan){ +        std::string sd_name = this->get_tx_subdev_spec().at(chan).sd_name; +        return _tx_dboard(chan)[named_prop_t(DBOARD_PROP_GAIN_GROUP, sd_name)].as<gain_group::sptr>(); +    } +}; + +/*********************************************************************** + * The Make Function + **********************************************************************/ +single_usrp::sptr single_usrp::make(const device_addr_t &dev_addr){ +    return sptr(new single_usrp_impl(dev_addr)); +} diff --git a/host/lib/usrp/subdev_spec.cpp b/host/lib/usrp/subdev_spec.cpp new file mode 100644 index 000000000..95d2cbb12 --- /dev/null +++ b/host/lib/usrp/subdev_spec.cpp @@ -0,0 +1,74 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <stdexcept> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; + +subdev_spec_pair_t::subdev_spec_pair_t( +    const std::string &db_name, const std::string &sd_name +): +    db_name(db_name), +    sd_name(sd_name) +{ +    /* NOP */ +} + +bool usrp::operator==(const subdev_spec_pair_t &lhs, const subdev_spec_pair_t &rhs){ +    return (lhs.db_name == rhs.db_name) and (lhs.sd_name == rhs.sd_name); +} + +subdev_spec_t::subdev_spec_t(const std::string &markup){ +    BOOST_FOREACH(const std::string &pair, std::split_string(markup)){ +        if (pair == "") continue; +        std::vector<std::string> db_sd = std::split_string(pair, ":"); +        switch(db_sd.size()){ +        case 1: this->push_back(subdev_spec_pair_t("", db_sd.front())); break; +        case 2: this->push_back(subdev_spec_pair_t(db_sd.front(), db_sd.back())); break; +        default: throw std::runtime_error("invalid subdev-spec markup string: "+markup); +        } +    } +} + +std::string subdev_spec_t::to_pp_string(void) const{ +    if (this->size() == 0) return "Empty Subdevice Specification"; + +    std::stringstream ss; +    size_t count = 0; +    ss << "Subdevice Specification:" << std::endl; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, *this){ +        ss << boost::format( +            "    Channel %d: Daughterboard %s, Subdevice %s" +        ) % (count++) % pair.db_name % pair.sd_name << std::endl; +    } +    return ss.str(); +} + +std::string subdev_spec_t::to_string(void) const{ +    std::string markup; +    size_t count = 0; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, *this){ +        markup += ((count++)? " " : "") + pair.db_name + ":" + pair.sd_name; +    } +    return markup; +} diff --git a/host/lib/usrp/tune_helper.cpp b/host/lib/usrp/tune_helper.cpp new file mode 100644 index 000000000..fa40a8a26 --- /dev/null +++ b/host/lib/usrp/tune_helper.cpp @@ -0,0 +1,157 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/tune_helper.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <uhd/usrp/dboard_iface.hpp> //unit_t +#include <uhd/utils/algorithm.hpp> +#include <boost/math/special_functions/sign.hpp> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Tune Helper Functions + **********************************************************************/ +static tune_result_t tune_xx_subdev_and_dsp( +    dboard_iface::unit_t unit, +    wax::obj subdev, wax::obj dsp, size_t chan, +    const tune_request_t &tune_request +){ +    wax::obj subdev_freq_proxy = subdev[SUBDEV_PROP_FREQ]; +    std::string freq_name = dsp[DSP_PROP_FREQ_SHIFT_NAMES].as<prop_names_t>().at(chan); +    wax::obj dsp_freq_proxy = dsp[named_prop_t(DSP_PROP_FREQ_SHIFT, freq_name)]; +    double dsp_sample_rate = dsp[DSP_PROP_CODEC_RATE].as<double>(); + +    //------------------------------------------------------------------ +    //-- calculate the LO offset, only used with automatic policy +    //------------------------------------------------------------------ +    double lo_offset = 0.0; +    if (subdev[SUBDEV_PROP_USE_LO_OFFSET].as<bool>()){ +        //If the local oscillator will be in the passband, use an offset. +        //But constrain the LO offset by the width of the filter bandwidth. +        double rate = dsp[DSP_PROP_HOST_RATE].as<double>(); +        double bw = subdev[SUBDEV_PROP_BANDWIDTH].as<double>(); +        if (bw > rate) lo_offset = std::min((bw - rate)/2, rate/2); +    } + +    //------------------------------------------------------------------ +    //-- set the intermediate frequency depending upon the IF policy +    //------------------------------------------------------------------ +    double target_inter_freq = 0.0; +    switch (tune_request.inter_freq_policy){ +    case tune_request_t::POLICY_AUTO: +        target_inter_freq = tune_request.target_freq + lo_offset; +        subdev_freq_proxy = target_inter_freq; +        break; + +    case tune_request_t::POLICY_MANUAL: +        target_inter_freq = tune_request.inter_freq; +        subdev_freq_proxy = target_inter_freq; +        break; + +    case tune_request_t::POLICY_NONE: break; //does not set +    } +    double actual_inter_freq = subdev_freq_proxy.as<double>(); + +    //------------------------------------------------------------------ +    //-- calculate the dsp freq, only used with automatic policy +    //------------------------------------------------------------------ +    double delta_freq = std::fmod(tune_request.target_freq - actual_inter_freq, dsp_sample_rate); +    bool outside_of_nyquist = std::abs(delta_freq) > dsp_sample_rate/2.0; +    double target_dsp_freq = (outside_of_nyquist)? +        boost::math::sign(delta_freq)*dsp_sample_rate - delta_freq : -delta_freq; + +    //invert the sign on the dsp freq given the following conditions +    if (unit == dboard_iface::UNIT_TX) target_dsp_freq *= -1.0; + +    //------------------------------------------------------------------ +    //-- set the dsp frequency depending upon the dsp frequency policy +    //------------------------------------------------------------------ +    switch (tune_request.dsp_freq_policy){ +    case tune_request_t::POLICY_AUTO: +        dsp_freq_proxy = target_dsp_freq; +        break; + +    case tune_request_t::POLICY_MANUAL: +        target_dsp_freq = tune_request.dsp_freq; +        dsp_freq_proxy = target_dsp_freq; +        break; + +    case tune_request_t::POLICY_NONE: break; //does not set +    } +    double actual_dsp_freq = dsp_freq_proxy.as<double>(); + +    //------------------------------------------------------------------ +    //-- load and return the tune result +    //------------------------------------------------------------------ +    tune_result_t tune_result; +    tune_result.target_inter_freq = target_inter_freq; +    tune_result.actual_inter_freq = actual_inter_freq; +    tune_result.target_dsp_freq = target_dsp_freq; +    tune_result.actual_dsp_freq = actual_dsp_freq; +    return tune_result; +} + +static double derive_freq_from_xx_subdev_and_dsp( +    dboard_iface::unit_t unit, +    wax::obj subdev, wax::obj dsp, size_t chan +){ +    //extract actual dsp and IF frequencies +    double actual_inter_freq = subdev[SUBDEV_PROP_FREQ].as<double>(); +    std::string freq_name = dsp[DSP_PROP_FREQ_SHIFT_NAMES].as<prop_names_t>().at(chan); +    double actual_dsp_freq = dsp[named_prop_t(DSP_PROP_FREQ_SHIFT, freq_name)].as<double>(); + +    //invert the sign on the dsp freq given the following conditions +    if (unit == dboard_iface::UNIT_TX) actual_dsp_freq *= -1.0; + +    return actual_inter_freq - actual_dsp_freq; +} + +/*********************************************************************** + * RX Tune + **********************************************************************/ +tune_result_t usrp::tune_rx_subdev_and_dsp( +    wax::obj subdev, wax::obj ddc, size_t chan, +    const tune_request_t &tune_request +){ +    return tune_xx_subdev_and_dsp(dboard_iface::UNIT_RX, subdev, ddc, chan, tune_request); +} + +double usrp::derive_freq_from_rx_subdev_and_dsp( +    wax::obj subdev, wax::obj ddc, size_t chan +){ +    return derive_freq_from_xx_subdev_and_dsp(dboard_iface::UNIT_RX, subdev, ddc, chan); +} + +/*********************************************************************** + * TX Tune + **********************************************************************/ +tune_result_t usrp::tune_tx_subdev_and_dsp( +    wax::obj subdev, wax::obj duc, size_t chan, +    const tune_request_t &tune_request +){ +    return tune_xx_subdev_and_dsp(dboard_iface::UNIT_TX, subdev, duc, chan, tune_request); +} + +double usrp::derive_freq_from_tx_subdev_and_dsp( +    wax::obj subdev, wax::obj duc, size_t chan +){ +    return derive_freq_from_xx_subdev_and_dsp(dboard_iface::UNIT_TX, subdev, duc, chan); +} diff --git a/host/lib/usrp/usrp1/CMakeLists.txt b/host/lib/usrp/usrp1/CMakeLists.txt new file mode 100644 index 000000000..8b6ba78d2 --- /dev/null +++ b/host/lib/usrp/usrp1/CMakeLists.txt @@ -0,0 +1,51 @@ +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +#This file will be included by cmake, use absolute paths! + +######################################################################## +# Conditionally configure the USRP1 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ${HAVE_USB_SUPPORT}) + +#sanity check when USRP1 support enabled +IF(ENABLE_USRP1 AND NOT HAVE_USB_SUPPORT) +    MESSAGE(FATAL_ERROR "USRP1 support enabled without USB support") +ENDIF(ENABLE_USRP1 AND NOT HAVE_USB_SUPPORT) + +IF(ENABLE_USRP1) +    INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/../firmware/fx2/common) + +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/clock_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/clock_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/codec_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/codec_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/codec_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/dboard_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/dboard_iface.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/dsp_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/io_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/mboard_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_iface.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_iface.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_impl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp1/usrp1_ctrl.hpp +    ) +ENDIF(ENABLE_USRP1) diff --git a/host/lib/usrp/usrp1/clock_ctrl.cpp b/host/lib/usrp/usrp1/clock_ctrl.cpp new file mode 100644 index 000000000..68c5f5320 --- /dev/null +++ b/host/lib/usrp/usrp1/clock_ctrl.cpp @@ -0,0 +1,60 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "fpga_regs_standard.h" +#include <uhd/utils/assert.hpp> +#include <boost/cstdint.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <utility> +#include <iostream> + +using namespace uhd; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const double master_clock_rate = 64e6; + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class usrp1_clock_ctrl_impl : public usrp1_clock_ctrl { +public: +    usrp1_clock_ctrl_impl(usrp1_iface::sptr iface) +    { +        _iface = iface; +    } + +    double get_master_clock_freq(void) +    { +        return master_clock_rate;  +    } + +private: +    usrp1_iface::sptr _iface; + +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +usrp1_clock_ctrl::sptr usrp1_clock_ctrl::make(usrp1_iface::sptr iface) +{ +    return sptr(new usrp1_clock_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp1/clock_ctrl.hpp b/host/lib/usrp/usrp1/clock_ctrl.hpp new file mode 100644 index 000000000..366869dab --- /dev/null +++ b/host/lib/usrp/usrp1/clock_ctrl.hpp @@ -0,0 +1,50 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP1_CLOCK_CTRL_HPP +#define INCLUDED_USRP1_CLOCK_CTRL_HPP + +#include "usrp1_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp1 clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class usrp1_clock_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp1_clock_ctrl> sptr; + +    /*! +     * Make a new clock control object. +     * \param iface the usrp1 iface object +     * \return the clock control object +     */ +    static sptr make(usrp1_iface::sptr iface); + +    /*! +     * Get the rate of the fpga clock line. +     * \return the fpga clock rate in Hz +     */ +    virtual double get_master_clock_freq(void) = 0; + +}; + +#endif /* INCLUDED_USRP1_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/codec_ctrl.cpp b/host/lib/usrp/usrp1/codec_ctrl.cpp new file mode 100644 index 000000000..18f794632 --- /dev/null +++ b/host/lib/usrp/usrp1/codec_ctrl.cpp @@ -0,0 +1,443 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "usrp_commands.h" +#include "clock_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> +#include <iomanip> + +using namespace uhd; + +static const bool codec_debug = false; + +const gain_range_t usrp1_codec_ctrl::tx_pga_gain_range(-20, 0, float(0.1)); +const gain_range_t usrp1_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class usrp1_codec_ctrl_impl : public usrp1_codec_ctrl { +public: +    //structors +    usrp1_codec_ctrl_impl(usrp1_iface::sptr iface, +                          usrp1_clock_ctrl::sptr clock, +                          int spi_slave); +    ~usrp1_codec_ctrl_impl(void); + +    //aux adc and dac control +    float read_aux_adc(aux_adc_t which); +    void write_aux_dac(aux_dac_t which, float volts); + +    //duc control +    void set_duc_freq(double freq); + +    //pga gain control +    void set_tx_pga_gain(float); +    float get_tx_pga_gain(void); +    void set_rx_pga_gain(float, char); +    float get_rx_pga_gain(char); +     +    //rx adc buffer control +    void bypass_adc_buffers(bool bypass); + +private: +    usrp1_iface::sptr _iface; +    usrp1_clock_ctrl::sptr _clock_ctrl; +    int _spi_slave; +    ad9862_regs_t _ad9862_regs; +    aux_adc_t _last_aux_adc_a, _last_aux_adc_b; +    void send_reg(boost::uint8_t addr); +    void recv_reg(boost::uint8_t addr); + +    double coarse_tune(double codec_rate, double freq); +    double fine_tune(double codec_rate, double freq); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +usrp1_codec_ctrl_impl::usrp1_codec_ctrl_impl(usrp1_iface::sptr iface, +                                             usrp1_clock_ctrl::sptr clock, +                                             int spi_slave) +{ +    _iface = iface; +    _clock_ctrl = clock; +    _spi_slave = spi_slave; + +    //soft reset +    _ad9862_regs.soft_reset = 1; +    this->send_reg(0); + +    //initialize the codec register settings +    _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; +    _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; +    _ad9862_regs.soft_reset = 0; + +    //setup rx side of codec +    _ad9862_regs.byp_buffer_a = 1; +    _ad9862_regs.byp_buffer_b = 1; +    _ad9862_regs.buffer_a_pd = 1; +    _ad9862_regs.buffer_b_pd = 1; +    _ad9862_regs.rx_pga_a = 0; +    _ad9862_regs.rx_pga_b = 0; +    _ad9862_regs.rx_twos_comp = 1; +    _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; + +    //setup tx side of codec +    _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; +    _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; +    _ad9862_regs.tx_pga_gain = 199; +    _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; +    _ad9862_regs.interp = ad9862_regs_t::INTERP_4; +    _ad9862_regs.tx_twos_comp = 1; +    _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_NCO; +    _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; +    _ad9862_regs.dac_a_coarse_gain = 0x3; +    _ad9862_regs.dac_b_coarse_gain = 0x3; + +    //setup the dll +    _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; +    _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; +    _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; + +    //setup clockout +    _ad9862_regs.clkout2_div_factor = ad9862_regs_t::CLKOUT2_DIV_FACTOR_2; + +    //write the register settings to the codec +    for (boost::uint8_t addr = 0; addr <= 25; addr++) { +        this->send_reg(addr); +    } + +    //aux adc clock +    _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; +    this->send_reg(34); +} + +usrp1_codec_ctrl_impl::~usrp1_codec_ctrl_impl(void) +{ +    //set aux dacs to zero +    this->write_aux_dac(AUX_DAC_A, 0); +    this->write_aux_dac(AUX_DAC_B, 0); +    this->write_aux_dac(AUX_DAC_C, 0); +    this->write_aux_dac(AUX_DAC_D, 0); + +    //power down +    _ad9862_regs.all_rx_pd = 1; +    this->send_reg(1); +    _ad9862_regs.tx_digital_pd = 1; +    _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; +    this->send_reg(8); +} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void usrp1_codec_ctrl_impl::set_tx_pga_gain(float gain){ +    int gain_word = int(mtpgw*(gain - tx_pga_gain_range.start())/(tx_pga_gain_range.stop() - tx_pga_gain_range.start())); +    _ad9862_regs.tx_pga_gain = std::clip(gain_word, 0, mtpgw); +    this->send_reg(16); +} + +float usrp1_codec_ctrl_impl::get_tx_pga_gain(void){ +    return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.stop() - tx_pga_gain_range.start())/mtpgw) + tx_pga_gain_range.start(); +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void usrp1_codec_ctrl_impl::set_rx_pga_gain(float gain, char which){ +    int gain_word = int(mrpgw*(gain - rx_pga_gain_range.start())/(rx_pga_gain_range.stop() - rx_pga_gain_range.start())); +    gain_word = std::clip(gain_word, 0, mrpgw); +    switch(which){ +    case 'A': +        _ad9862_regs.rx_pga_a = gain_word; +        this->send_reg(2); +        return; +    case 'B': +        _ad9862_regs.rx_pga_b = gain_word; +        this->send_reg(3); +        return; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +float usrp1_codec_ctrl_impl::get_rx_pga_gain(char which){ +    int gain_word; +    switch(which){ +    case 'A': gain_word = _ad9862_regs.rx_pga_a; break; +    case 'B': gain_word = _ad9862_regs.rx_pga_b; break; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +    return (gain_word*(rx_pga_gain_range.stop() - rx_pga_gain_range.start())/mrpgw) + rx_pga_gain_range.start(); +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static float aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low) +{ +    return float(((boost::uint16_t(high) << 2) | low)*3.3)/0x3ff; +} + +float usrp1_codec_ctrl_impl::read_aux_adc(aux_adc_t which) +{ +    //check to see if the switch needs to be set +    bool write_switch = false; +    switch(which) { + +    case AUX_ADC_A1: +    case AUX_ADC_A2: +        if (which != _last_aux_adc_a) { +            _ad9862_regs.select_a = (which == AUX_ADC_A1)? +                ad9862_regs_t::SELECT_A_AUX_ADC1: ad9862_regs_t::SELECT_A_AUX_ADC2; +            _last_aux_adc_a = which; +            write_switch = true; +        } +        break; + +    case AUX_ADC_B1: +    case AUX_ADC_B2: +        if (which != _last_aux_adc_b) { +            _ad9862_regs.select_b = (which == AUX_ADC_B1)? +                ad9862_regs_t::SELECT_B_AUX_ADC1: ad9862_regs_t::SELECT_B_AUX_ADC2; +            _last_aux_adc_b = which; +            write_switch = true; +        } +        break; + +    } + +    //write the switch if it changed +    if(write_switch) this->send_reg(34); + +    //map aux adcs to register values to read +    static const uhd::dict<aux_adc_t, boost::uint8_t> aux_dac_to_addr = boost::assign::map_list_of +        (AUX_ADC_A2, 26) (AUX_ADC_A1, 28) +        (AUX_ADC_B2, 30) (AUX_ADC_B1, 32) +    ; + +    //read the value +    this->recv_reg(aux_dac_to_addr[which]+0); +    this->recv_reg(aux_dac_to_addr[which]+1); + +    //return the value scaled to volts +    switch(which) { +    case AUX_ADC_A1: return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); +    case AUX_ADC_A2: return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); +    case AUX_ADC_B1: return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); +    case AUX_ADC_B2: return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); +    } +    UHD_ASSERT_THROW(false); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void usrp1_codec_ctrl_impl::write_aux_dac(aux_dac_t which, float volts) +{ +    //special case for aux dac d (aka sigma delta word) +    if (which == AUX_DAC_D) { +        boost::uint16_t dac_word = std::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); +        _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); +        _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); +        this->send_reg(42); +        this->send_reg(43); +        return; +    } + +    //calculate the dac word for aux dac a, b, c +    boost::uint8_t dac_word = std::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + +    //setup a lookup table for the aux dac params (reg ref, reg addr) +    typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; +    uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of +        (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) +        (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) +        (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) +    ; + +    //set the aux dac register +    UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); +    boost::uint8_t *reg_ref, reg_addr; +    boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; +    *reg_ref = dac_word; +    this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void usrp1_codec_ctrl_impl::send_reg(boost::uint8_t addr) +{ +    boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); + +    if (codec_debug) { +        std::cout.fill('0'); +        std::cout << "codec control write reg: 0x"; +        std::cout << std::setw(8) << std::hex << reg << std::endl; +    } +    _iface->transact_spi(_spi_slave, +                         spi_config_t::EDGE_RISE, reg, 16, false); +} + +void usrp1_codec_ctrl_impl::recv_reg(boost::uint8_t addr) +{ +    boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); + +    if (codec_debug) { +        std::cout.fill('0'); +        std::cout << "codec control read reg: 0x"; +        std::cout << std::setw(8) << std::hex << reg << std::endl; +    } + +    boost::uint32_t ret = _iface->transact_spi(_spi_slave, +                                        spi_config_t::EDGE_RISE, reg, 16, true); + +    if (codec_debug) { +        std::cout.fill('0'); +        std::cout << "codec control read ret: 0x"; +        std::cout << std::setw(8) << std::hex << ret << std::endl; +    } + +    _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * DUC tuning  + **********************************************************************/ +double usrp1_codec_ctrl_impl::coarse_tune(double codec_rate, double freq) +{ +    double coarse_freq; + +    double coarse_freq_1 = codec_rate / 8; +    double coarse_freq_2 = codec_rate / 4; +    double coarse_limit_1 = coarse_freq_1 / 2; +    double coarse_limit_2 = (coarse_freq_1 + coarse_freq_2) / 2; +    double max_freq = coarse_freq_2 + .09375 * codec_rate; +  +    if (freq < -max_freq) { +        return false; +    } +    else if (freq < -coarse_limit_2) { +        _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_NEG_SHIFT; +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_4; +        coarse_freq = -coarse_freq_2; +    } +    else if (freq < -coarse_limit_1) { +        _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_NEG_SHIFT; +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_8; +        coarse_freq = -coarse_freq_1; +    } +    else if (freq < coarse_limit_1) { +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; +        coarse_freq = 0;  +    } +    else if (freq < coarse_limit_2) { +        _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_POS_SHIFT; +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_8; +        coarse_freq = coarse_freq_1; +    } +    else if (freq <= max_freq) { +        _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_POS_SHIFT; +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_4; +        coarse_freq = coarse_freq_2; +    } +    else { +        return 0;  +    } + +    return coarse_freq; +} + +double usrp1_codec_ctrl_impl::fine_tune(double codec_rate, double target_freq) +{ +    static const double scale_factor = std::pow(2.0, 24); + +    boost::uint32_t freq_word = boost::uint32_t( +        boost::math::round(abs((target_freq / codec_rate) * scale_factor))); + +    double actual_freq = freq_word * codec_rate / scale_factor; + +    if (target_freq < 0) { +        _ad9862_regs.neg_fine_tune = ad9862_regs_t::NEG_FINE_TUNE_NEG_SHIFT; +        actual_freq = -actual_freq;  +    } +    else { +        _ad9862_regs.neg_fine_tune = ad9862_regs_t::NEG_FINE_TUNE_POS_SHIFT; +    }  + +    _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_NCO; +    _ad9862_regs.ftw_23_16 = (freq_word >> 16) & 0xff; +    _ad9862_regs.ftw_15_8  = (freq_word >>  8) & 0xff; +    _ad9862_regs.ftw_7_0   = (freq_word >>  0) & 0xff; + +    return actual_freq; +} + +void usrp1_codec_ctrl_impl::set_duc_freq(double freq) +{ +    double codec_rate = _clock_ctrl->get_master_clock_freq() * 2; +    double coarse_freq = coarse_tune(codec_rate, freq); +    double fine_freq = fine_tune(codec_rate / 4, freq - coarse_freq); + +    if (codec_debug) { +        std::cout << "ad9862 tuning result:" << std::endl; +        std::cout << "   requested:   " << freq << std::endl; +        std::cout << "   actual:      " << coarse_freq + fine_freq << std::endl; +        std::cout << "   coarse freq: " << coarse_freq << std::endl; +        std::cout << "   fine freq:   " << fine_freq << std::endl; +        std::cout << "   codec rate:  " << codec_rate << std::endl; +    }     + +    this->send_reg(20); +    this->send_reg(21); +    this->send_reg(22); +    this->send_reg(23); +} + +/*********************************************************************** + * Codec Control ADC buffer bypass + * Disable this for AC-coupled daughterboards (TVRX) + * By default it is initialized TRUE. + **********************************************************************/ +void usrp1_codec_ctrl_impl::bypass_adc_buffers(bool bypass) { +    _ad9862_regs.byp_buffer_a = bypass; +    _ad9862_regs.byp_buffer_b = bypass; +    this->send_reg(2); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +usrp1_codec_ctrl::sptr usrp1_codec_ctrl::make(usrp1_iface::sptr iface, +                                              usrp1_clock_ctrl::sptr clock, +                                              int spi_slave) +{ +    return sptr(new usrp1_codec_ctrl_impl(iface, clock, spi_slave)); +} diff --git a/host/lib/usrp/usrp1/codec_ctrl.hpp b/host/lib/usrp/usrp1/codec_ctrl.hpp new file mode 100644 index 000000000..e2e8a010d --- /dev/null +++ b/host/lib/usrp/usrp1/codec_ctrl.hpp @@ -0,0 +1,100 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP1_CODEC_CTRL_HPP +#define INCLUDED_USRP1_CODEC_CTRL_HPP + +#include "usrp1_iface.hpp" +#include "clock_ctrl.hpp" +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp1 codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class usrp1_codec_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp1_codec_ctrl> sptr; + +    static const uhd::gain_range_t tx_pga_gain_range; +    static const uhd::gain_range_t rx_pga_gain_range; + +    /*! +     * Make a new clock control object. +     * \param iface the usrp1 iface object +     * \param spi_slave which spi device +     * \return the clock control object +     */ +    static sptr make(usrp1_iface::sptr iface, +        usrp1_clock_ctrl::sptr clock, int spi_slave +    ); + +    //! aux adc identifier constants +    enum aux_adc_t{ +        AUX_ADC_A2 = 0xA2, +        AUX_ADC_A1 = 0xA1, +        AUX_ADC_B2 = 0xB2, +        AUX_ADC_B1 = 0xB1 +    }; + +    /*! +     * Read an auxiliary adc: +     * The internals remember which aux adc was read last. +     * Therefore, the aux adc switch is only changed as needed. +     * \param which which of the 4 adcs +     * \return a value in volts +     */ +    virtual float read_aux_adc(aux_adc_t which) = 0; + +    //! aux dac identifier constants +    enum aux_dac_t{ +        AUX_DAC_A = 0xA, +        AUX_DAC_B = 0xB, +        AUX_DAC_C = 0xC, +        AUX_DAC_D = 0xD +    }; + +    /*! +     * Write an auxiliary dac. +     * \param which which of the 4 dacs +     * \param volts the level in in volts +     */ +    virtual void write_aux_dac(aux_dac_t which, float volts) = 0; + +    //! Set the TX PGA gain +    virtual void set_tx_pga_gain(float gain) = 0; + +    //! Get the TX PGA gain +    virtual float get_tx_pga_gain(void) = 0; + +    //! Set the RX PGA gain ('A' or 'B') +    virtual void set_rx_pga_gain(float gain, char which) = 0; + +    //! Get the RX PGA gain ('A' or 'B') +    virtual float get_rx_pga_gain(char which) = 0; + +    //! Set the TX modulator frequency +    virtual void set_duc_freq(double freq) = 0; +     +    //! Enable or disable ADC buffer bypass +    virtual void bypass_adc_buffers(bool bypass) = 0; +}; + +#endif /* INCLUDED_USRP1_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/codec_impl.cpp b/host/lib/usrp/usrp1/codec_impl.cpp new file mode 100644 index 000000000..db53be53e --- /dev/null +++ b/host/lib/usrp/usrp1/codec_impl.cpp @@ -0,0 +1,159 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_impl.hpp" +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/codec_props.hpp> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Helper Methods + **********************************************************************/ +void usrp1_impl::codec_init(void) +{ +    //make proxies +    BOOST_FOREACH(dboard_slot_t dboard_slot, _dboard_slots){ +        _rx_codec_proxies[dboard_slot] = wax_obj_proxy::make( +              boost::bind(&usrp1_impl::rx_codec_get, this, _1, _2, dboard_slot), +              boost::bind(&usrp1_impl::rx_codec_set, this, _1, _2, dboard_slot)); + +        _tx_codec_proxies[dboard_slot] = wax_obj_proxy::make( +              boost::bind(&usrp1_impl::tx_codec_get, this, _1, _2, dboard_slot), +              boost::bind(&usrp1_impl::tx_codec_set, this, _1, _2, dboard_slot)); +    } +}     + +/*********************************************************************** + * RX Codec Properties + **********************************************************************/ +static const std::string adc_pga_gain_name = "PGA"; + +void usrp1_impl::rx_codec_get(const wax::obj &key_, wax::obj &val, dboard_slot_t dboard_slot) +{ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<codec_prop_t>()) { +    case CODEC_PROP_NAME: +        val = str(boost::format("usrp1 adc - ad9862 - slot %c") % char(dboard_slot)); +        return; + +    case CODEC_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case CODEC_PROP_GAIN_NAMES: +        val = prop_names_t(1, adc_pga_gain_name); +        return; + +    case CODEC_PROP_GAIN_RANGE: +        UHD_ASSERT_THROW(key.name == adc_pga_gain_name); +        val = usrp1_codec_ctrl::rx_pga_gain_range; +        return; + +    case CODEC_PROP_GAIN_I: +        UHD_ASSERT_THROW(key.name == adc_pga_gain_name); +        val = _codec_ctrls[dboard_slot]->get_rx_pga_gain('A'); +        return; + +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == adc_pga_gain_name); +        val = _codec_ctrls[dboard_slot]->get_rx_pga_gain('B'); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp1_impl::rx_codec_set(const wax::obj &key_, const wax::obj &val, dboard_slot_t dboard_slot) +{ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the set request conditioned on the key +    switch(key.as<codec_prop_t>()) { +    case CODEC_PROP_GAIN_I: +        UHD_ASSERT_THROW(key.name == adc_pga_gain_name); +        _codec_ctrls[dboard_slot]->set_rx_pga_gain(val.as<float>(), 'A'); +        return; + +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == adc_pga_gain_name); +        _codec_ctrls[dboard_slot]->set_rx_pga_gain(val.as<float>(), 'B'); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Codec Properties + **********************************************************************/ +static const std::string dac_pga_gain_name = "PGA"; + +void usrp1_impl::tx_codec_get(const wax::obj &key_, wax::obj &val, dboard_slot_t dboard_slot) +{ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<codec_prop_t>()) { +    case CODEC_PROP_NAME: +        val = str(boost::format("usrp1 dac - ad9862 - slot %c") % char(dboard_slot)); +        return; + +    case CODEC_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case CODEC_PROP_GAIN_NAMES: +        val = prop_names_t(1, dac_pga_gain_name); +        return; + +    case CODEC_PROP_GAIN_RANGE: +        UHD_ASSERT_THROW(key.name == dac_pga_gain_name); +        val = usrp1_codec_ctrl::tx_pga_gain_range; +        return; + +    case CODEC_PROP_GAIN_I: //only one gain for I and Q +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == dac_pga_gain_name); +        val = _codec_ctrls[dboard_slot]->get_tx_pga_gain(); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp1_impl::tx_codec_set(const wax::obj &key_, const wax::obj &val, dboard_slot_t dboard_slot) +{ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the set request conditioned on the key +    switch(key.as<codec_prop_t>()){ +    case CODEC_PROP_GAIN_I: //only one gain for I and Q +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == dac_pga_gain_name); +        _codec_ctrls[dboard_slot]->set_tx_pga_gain(val.as<float>()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp new file mode 100644 index 000000000..70ce3da76 --- /dev/null +++ b/host/lib/usrp/usrp1/dboard_iface.cpp @@ -0,0 +1,382 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_iface.hpp" +#include "usrp1_impl.hpp" +#include "fpga_regs_common.h" +#include "usrp_spi_defs.h" +#include "fpga_regs_standard.h" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +static const dboard_id_t tvrx_id(0x0040); + +class usrp1_dboard_iface : public dboard_iface { +public: + +    usrp1_dboard_iface(usrp1_iface::sptr iface, +                       usrp1_clock_ctrl::sptr clock, +                       usrp1_codec_ctrl::sptr codec, +                       usrp1_impl::dboard_slot_t dboard_slot, +                       const dboard_id_t &rx_dboard_id +    ): +        _dboard_slot(dboard_slot), +        _rx_dboard_id(rx_dboard_id) +    { +        _iface = iface; +        _clock = clock; +        _codec = codec; + +        //init the clock rate shadows +        this->set_clock_rate(UNIT_RX, this->get_clock_rates(UNIT_RX).front()); +        this->set_clock_rate(UNIT_TX, this->get_clock_rates(UNIT_TX).front()); +         +        //yes this is evil but it's necessary for TVRX to work on USRP1 +        if(_rx_dboard_id == tvrx_id) _codec->bypass_adc_buffers(false); +        //else _codec->bypass_adc_buffers(false); //don't think this is necessary +    } + +    ~usrp1_dboard_iface() +    { +        /* NOP */ +    } + +    special_props_t get_special_props() +    { +        special_props_t props; +        props.soft_clock_divider = true; +        props.mangle_i2c_addrs = (_dboard_slot == usrp1_impl::DBOARD_SLOT_B); +        return props; +    } + +    void write_aux_dac(unit_t, aux_dac_t, float); +    float read_aux_adc(unit_t, aux_adc_t); + +    void _set_pin_ctrl(unit_t, boost::uint16_t); +    void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); +    void _set_gpio_ddr(unit_t, boost::uint16_t); +    void _set_gpio_out(unit_t, boost::uint16_t); +    void set_gpio_debug(unit_t, int); +    boost::uint16_t read_gpio(unit_t); + +    void write_i2c(boost::uint8_t, const byte_vector_t &); +    byte_vector_t read_i2c(boost::uint8_t, size_t); + +    void write_spi(unit_t unit, +                   const spi_config_t &config, +                   boost::uint32_t data, +                   size_t num_bits); + +    boost::uint32_t read_write_spi(unit_t unit, +                                   const spi_config_t &config, +                                   boost::uint32_t data, +                                   size_t num_bits); + +    void set_clock_rate(unit_t, double); +    std::vector<double> get_clock_rates(unit_t); +    double get_clock_rate(unit_t); +    void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t); + +private: +    usrp1_iface::sptr _iface; +    usrp1_clock_ctrl::sptr _clock; +    usrp1_codec_ctrl::sptr _codec; +    uhd::dict<unit_t, double> _clock_rates; +    const usrp1_impl::dboard_slot_t _dboard_slot; +    const dboard_id_t &_rx_dboard_id; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr usrp1_impl::make_dboard_iface(usrp1_iface::sptr iface, +                                           usrp1_clock_ctrl::sptr clock, +                                           usrp1_codec_ctrl::sptr codec, +                                           dboard_slot_t dboard_slot, +                                           const dboard_id_t &rx_dboard_id +){ +    return dboard_iface::sptr(new usrp1_dboard_iface( +        iface, clock, codec, dboard_slot, rx_dboard_id +    )); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +static const dboard_id_t dbsrx_classic_id(0x0002); + +/* + * Daughterboard reference clock register + * + * Bit  7    - 1 turns on refclk, 0 allows IO use + * Bits 6:0  - Divider value + */ +void usrp1_dboard_iface::set_clock_rate(unit_t unit, double rate) +{ +    assert_has(this->get_clock_rates(unit), rate, "dboard clock rate"); +    _clock_rates[unit] = rate; + +    if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ +        size_t divider = size_t(_clock->get_master_clock_freq()/rate); +        switch(_dboard_slot){ +        case usrp1_impl::DBOARD_SLOT_A: +            _iface->poke32(FR_RX_A_REFCLK, (divider & 0x7f) | 0x80); +            break; + +        case usrp1_impl::DBOARD_SLOT_B: +            _iface->poke32(FR_RX_B_REFCLK, (divider & 0x7f) | 0x80); +            break; +        } +    } +} + +std::vector<double> usrp1_dboard_iface::get_clock_rates(unit_t unit) +{ +    std::vector<double> rates; +    if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ +        for (size_t div = 1; div <= 127; div++) +            rates.push_back(_clock->get_master_clock_freq() / div); +    } +    else{ +        rates.push_back(_clock->get_master_clock_freq()); +    } +    return rates; +} + +double usrp1_dboard_iface::get_clock_rate(unit_t unit) +{ +    return _clock_rates[unit]; +} + +void usrp1_dboard_iface::set_clock_enabled(unit_t, bool) +{ +    //TODO we can only enable for special case anyway... +} + +double usrp1_dboard_iface::get_codec_rate(unit_t){ +    return _clock->get_master_clock_freq(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) +{ +    switch(unit) { +    case UNIT_RX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +             _iface->poke32(FR_ATR_MASK_1, value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +             _iface->poke32(FR_ATR_MASK_3, value); +        break; +    case UNIT_TX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_ATR_MASK_0, value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_ATR_MASK_2, value); +        break; +    } +} + +void usrp1_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) +{ +    switch(unit) { +    case UNIT_RX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_OE_1, 0xffff0000 | value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_OE_3, 0xffff0000 | value); +        break; +    case UNIT_TX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_OE_0, 0xffff0000 | value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_OE_2, 0xffff0000 | value); +        break; +    } +} + +void usrp1_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) +{ +    switch(unit) { +    case UNIT_RX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_IO_1, 0xffff0000 | value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_IO_3, 0xffff0000 | value); +        break; +    case UNIT_TX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_IO_0, 0xffff0000 | value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_IO_2, 0xffff0000 | value); +        break; +    } +} + +void usrp1_dboard_iface::set_gpio_debug(unit_t, int) +{ +    /* NOP */ +} + +boost::uint16_t usrp1_dboard_iface::read_gpio(unit_t unit) +{ +    boost::uint32_t out_value; + +    if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +        out_value = _iface->peek32(1); +    else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +        out_value = _iface->peek32(2); +    else +        UHD_THROW_INVALID_CODE_PATH(); + +    switch(unit) { +    case UNIT_RX: +        return (boost::uint16_t)((out_value >> 16) & 0x0000ffff); +    case UNIT_TX: +        return (boost::uint16_t)((out_value >>  0) & 0x0000ffff); +    } +    UHD_ASSERT_THROW(false); +} + +void usrp1_dboard_iface::_set_atr_reg(unit_t unit, +                                     atr_reg_t atr, boost::uint16_t value) +{ +    // Ignore unsupported states +    if ((atr == ATR_REG_IDLE) || (atr == ATR_REG_FULL_DUPLEX)) +        return; + +    switch(unit) { +    case UNIT_RX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_ATR_RXVAL_1, value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_ATR_RXVAL_3, value); +        break; +    case UNIT_TX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_ATR_TXVAL_0, value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_ATR_TXVAL_2, value); +        break; +    } +} +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \param slot the side (A or B) the dboard is attached + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit, +                                           usrp1_impl::dboard_slot_t slot) +{ +    switch(unit) { +    case dboard_iface::UNIT_TX: +        if (slot == usrp1_impl::DBOARD_SLOT_A) +            return SPI_ENABLE_TX_A; +        else if (slot == usrp1_impl::DBOARD_SLOT_B) +            return SPI_ENABLE_TX_B; +        else +            break; +    case dboard_iface::UNIT_RX: +        if (slot == usrp1_impl::DBOARD_SLOT_A) +            return SPI_ENABLE_RX_A; +        else if (slot == usrp1_impl::DBOARD_SLOT_B) +            return SPI_ENABLE_RX_B; +        else +            break; +    } +    throw std::invalid_argument("unknown unit type"); +} + +void usrp1_dboard_iface::write_spi(unit_t unit, +                                   const spi_config_t &config, +                                   boost::uint32_t data, +                                   size_t num_bits) +{ +    _iface->transact_spi(unit_to_otw_spi_dev(unit, _dboard_slot), +                         config, data, num_bits, false); +} + +boost::uint32_t usrp1_dboard_iface::read_write_spi(unit_t unit, +                                                   const spi_config_t &config, +                                                   boost::uint32_t data, +                                                   size_t num_bits) +{ +    return _iface->transact_spi(unit_to_otw_spi_dev(unit, _dboard_slot), +                                config, data, num_bits, true); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp1_dboard_iface::write_i2c(boost::uint8_t addr, +                                   const byte_vector_t &bytes) +{ +    return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp1_dboard_iface::read_i2c(boost::uint8_t addr, +                                           size_t num_bytes) +{ +    return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp1_dboard_iface::write_aux_dac(dboard_iface::unit_t, +                                       aux_dac_t which, float value) +{ +    //same aux dacs for each unit +    static const uhd::dict<aux_dac_t, usrp1_codec_ctrl::aux_dac_t> +        which_to_aux_dac = map_list_of +                                     (AUX_DAC_A, usrp1_codec_ctrl::AUX_DAC_A) +                                     (AUX_DAC_B, usrp1_codec_ctrl::AUX_DAC_B) +                                     (AUX_DAC_C, usrp1_codec_ctrl::AUX_DAC_C) +                                     (AUX_DAC_D, usrp1_codec_ctrl::AUX_DAC_D); + +    _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +float usrp1_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, +                                       aux_adc_t which) +{ +    static const +    uhd::dict<unit_t, uhd::dict<aux_adc_t, usrp1_codec_ctrl::aux_adc_t> > +        unit_to_which_to_aux_adc = map_list_of(UNIT_RX, map_list_of +                                    (AUX_ADC_A, usrp1_codec_ctrl::AUX_ADC_A1) +                                    (AUX_ADC_B, usrp1_codec_ctrl::AUX_ADC_B1)) +                                              (UNIT_TX, map_list_of +                                    (AUX_ADC_A, usrp1_codec_ctrl::AUX_ADC_A2) +                                    (AUX_ADC_B, usrp1_codec_ctrl::AUX_ADC_B2)); + +    return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/usrp1/dboard_impl.cpp b/host/lib/usrp/usrp1/dboard_impl.cpp new file mode 100644 index 000000000..2a2762a82 --- /dev/null +++ b/host/lib/usrp/usrp1/dboard_impl.cpp @@ -0,0 +1,219 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_impl.hpp" +#include "usrp_i2c_addr.h" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +static boost::uint8_t get_rx_ee_addr(usrp1_impl::dboard_slot_t dboard_slot){ +    switch(dboard_slot){ +    case usrp1_impl::DBOARD_SLOT_A: return I2C_ADDR_RX_A; +    case usrp1_impl::DBOARD_SLOT_B: return I2C_ADDR_RX_B; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +static boost::uint8_t get_tx_ee_addr(usrp1_impl::dboard_slot_t dboard_slot){ +    switch(dboard_slot){ +    case usrp1_impl::DBOARD_SLOT_A: return I2C_ADDR_TX_A; +    case usrp1_impl::DBOARD_SLOT_B: return I2C_ADDR_TX_B; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +/*********************************************************************** + * Dboard Initialization + **********************************************************************/ +void usrp1_impl::dboard_init(void) +{ +    BOOST_FOREACH(dboard_slot_t dboard_slot, _dboard_slots){ + +        //read the tx and rx dboard eeproms +        _rx_db_eeproms[dboard_slot] = dboard_eeprom_t(_iface->read_eeprom( +            get_rx_ee_addr(dboard_slot), 0, dboard_eeprom_t::num_bytes() +        )); + +        _tx_db_eeproms[dboard_slot] = dboard_eeprom_t(_iface->read_eeprom( +            get_tx_ee_addr(dboard_slot), 0, dboard_eeprom_t::num_bytes() +        )); + +        //create a new dboard interface and manager +        _dboard_ifaces[dboard_slot] = make_dboard_iface( +            _iface, _clock_ctrl, _codec_ctrls[dboard_slot], +            dboard_slot, _rx_db_eeproms[dboard_slot].id +        ); + +        _dboard_managers[dboard_slot] = dboard_manager::make( +            _rx_db_eeproms[dboard_slot].id, +            _tx_db_eeproms[dboard_slot].id, +            _dboard_ifaces[dboard_slot] +        ); + +        //setup the dboard proxies +        _rx_dboard_proxies[dboard_slot] = wax_obj_proxy::make( +             boost::bind(&usrp1_impl::rx_dboard_get, this, _1, _2, dboard_slot), +             boost::bind(&usrp1_impl::rx_dboard_set, this, _1, _2, dboard_slot)); + +        _tx_dboard_proxies[dboard_slot] = wax_obj_proxy::make( +             boost::bind(&usrp1_impl::tx_dboard_get, this, _1, _2, dboard_slot), +             boost::bind(&usrp1_impl::tx_dboard_set, this, _1, _2, dboard_slot)); +    } + +} + +/*********************************************************************** + * RX Dboard Get + **********************************************************************/ +void usrp1_impl::rx_dboard_get(const wax::obj &key_, wax::obj &val, dboard_slot_t dboard_slot) +{ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_NAME: +        val = str(boost::format("usrp1 dboard (rx unit) - %c") % char(dboard_slot)); +        return; + +    case DBOARD_PROP_SUBDEV: +        val = _dboard_managers[dboard_slot]->get_rx_subdev(key.name); +        return; + +    case DBOARD_PROP_SUBDEV_NAMES: +        val = _dboard_managers[dboard_slot]->get_rx_subdev_names(); +        return; + +    case DBOARD_PROP_DBOARD_ID: +        val = _rx_db_eeproms[dboard_slot].id; +        return; + +    case DBOARD_PROP_DBOARD_IFACE: +        val = _dboard_ifaces[dboard_slot]; +        return; + +    case DBOARD_PROP_CODEC: +        val = _rx_codec_proxies[dboard_slot]->get_link(); +        return; + +    case DBOARD_PROP_GAIN_GROUP: +        val = make_gain_group( +            _rx_db_eeproms[dboard_slot].id, +            _dboard_managers[dboard_slot]->get_rx_subdev(key.name), +            _rx_codec_proxies[dboard_slot]->get_link(), +            GAIN_GROUP_POLICY_RX +        ); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * RX Dboard Set + **********************************************************************/ +void usrp1_impl::rx_dboard_set(const wax::obj &key, const wax::obj &val, dboard_slot_t dboard_slot) +{ +    switch(key.as<dboard_prop_t>()) { +    case DBOARD_PROP_DBOARD_ID: +        _rx_db_eeproms[dboard_slot].id = val.as<dboard_id_t>(); +        _iface->write_eeprom( +            get_rx_ee_addr(dboard_slot), 0, +            _rx_db_eeproms[dboard_slot].get_eeprom_bytes() +        ); +        return; + +    default: +        UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Dboard Get + **********************************************************************/ +void usrp1_impl::tx_dboard_get(const wax::obj &key_, wax::obj &val, dboard_slot_t dboard_slot) +{ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_NAME: +        val = str(boost::format("usrp1 dboard (tx unit) - %c") % char(dboard_slot)); +        return; + +    case DBOARD_PROP_SUBDEV: +        val = _dboard_managers[dboard_slot]->get_tx_subdev(key.name); +        return; + +    case DBOARD_PROP_SUBDEV_NAMES: +        val = _dboard_managers[dboard_slot]->get_tx_subdev_names(); +        return; + +    case DBOARD_PROP_DBOARD_ID: +        val = _tx_db_eeproms[dboard_slot].id; +        return; + +    case DBOARD_PROP_DBOARD_IFACE: +        val = _dboard_ifaces[dboard_slot]; +        return; + +    case DBOARD_PROP_CODEC: +        val = _tx_codec_proxies[dboard_slot]->get_link(); +        return; + +    case DBOARD_PROP_GAIN_GROUP: +        val = make_gain_group( +            _tx_db_eeproms[dboard_slot].id, +            _dboard_managers[dboard_slot]->get_tx_subdev(key.name), +            _tx_codec_proxies[dboard_slot]->get_link(), +            GAIN_GROUP_POLICY_TX +        ); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * TX Dboard Set + **********************************************************************/ +void usrp1_impl::tx_dboard_set(const wax::obj &key, const wax::obj &val, dboard_slot_t dboard_slot) +{ +    switch(key.as<dboard_prop_t>()) { +    case DBOARD_PROP_DBOARD_ID: +        _tx_db_eeproms[dboard_slot].id = val.as<dboard_id_t>(); +        _iface->write_eeprom( +            get_tx_ee_addr(dboard_slot), 0, +            _tx_db_eeproms[dboard_slot].get_eeprom_bytes() +        ); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp1/dsp_impl.cpp b/host/lib/usrp/usrp1/dsp_impl.cpp new file mode 100644 index 000000000..e9a5e60a6 --- /dev/null +++ b/host/lib/usrp/usrp1/dsp_impl.cpp @@ -0,0 +1,228 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_impl.hpp" +#include "fpga_regs_standard.h" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * RX DDC Initialization + **********************************************************************/ +void usrp1_impl::rx_dsp_init(void) +{ +    _rx_dsp_proxy = wax_obj_proxy::make( +        boost::bind(&usrp1_impl::rx_dsp_get, this, _1, _2), +        boost::bind(&usrp1_impl::rx_dsp_set, this, _1, _2)); + +    rx_dsp_set(DSP_PROP_HOST_RATE, _clock_ctrl->get_master_clock_freq() / 16); +} + +/*********************************************************************** + * RX DDC Get + **********************************************************************/ +void usrp1_impl::rx_dsp_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ +    case DSP_PROP_NAME: +        val = str(boost::format("usrp1 ddc %uX %s") +            % this->get_num_ddcs() +            % (this->has_rx_halfband()? "+ hb" : "") +        ); +        return; + +    case DSP_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case DSP_PROP_FREQ_SHIFT: +        val = _rx_dsp_freqs[key.name]; +        return; + +    case DSP_PROP_FREQ_SHIFT_NAMES:{ +            prop_names_t names; +            for(size_t i = 0; i < this->get_num_ddcs(); i++){ +                names.push_back(boost::lexical_cast<std::string>(i)); +            } +            val = names; +        } +        return; + +    case DSP_PROP_CODEC_RATE: +        val = _clock_ctrl->get_master_clock_freq(); +        return; + +    case DSP_PROP_HOST_RATE: +        val = _clock_ctrl->get_master_clock_freq()/_rx_dsp_decim; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } + +} + +/*********************************************************************** + * RX DDC Set + **********************************************************************/ +void usrp1_impl::rx_dsp_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()) { +    case DSP_PROP_FREQ_SHIFT: { +            double new_freq = val.as<double>(); +            boost::uint32_t reg_word = dsp_type1::calc_cordic_word_and_update( +                new_freq, _clock_ctrl->get_master_clock_freq()); + +            static const uhd::dict<std::string, boost::uint32_t> +            freq_name_to_reg_val = boost::assign::map_list_of +                ("0", FR_RX_FREQ_0) ("1", FR_RX_FREQ_1) +                ("2", FR_RX_FREQ_2) ("3", FR_RX_FREQ_3) +            ; +            _iface->poke32(freq_name_to_reg_val[key.name], reg_word); +            _rx_dsp_freqs[key.name] = new_freq; +            return; +        } +    case DSP_PROP_HOST_RATE: { +            size_t rate = size_t(_clock_ctrl->get_master_clock_freq() / val.as<double>()); + +            if ((rate & 0x01) || (rate < 4) || (rate > 256)) { +                std::cerr << "Decimation must be even and between 4 and 256" +                          << std::endl; +                return; +            } + +            _rx_dsp_decim = rate; +            //TODO Poll every 100ms. Make it selectable? +            _rx_samps_per_poll_interval = size_t(0.1 * _clock_ctrl->get_master_clock_freq() / rate); + +            _iface->poke32(FR_DECIM_RATE, _rx_dsp_decim/2 - 1); +        } +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } + +} + +/*********************************************************************** + * TX DUC Initialization + **********************************************************************/ +void usrp1_impl::tx_dsp_init(void) +{ +    _tx_dsp_proxy = wax_obj_proxy::make( +                          boost::bind(&usrp1_impl::tx_dsp_get, this, _1, _2), +                          boost::bind(&usrp1_impl::tx_dsp_set, this, _1, _2)); + +    //initial config and update +    tx_dsp_set(DSP_PROP_HOST_RATE, _clock_ctrl->get_master_clock_freq() * 2 / 16); +} + +/*********************************************************************** + * TX DUC Get + **********************************************************************/ +void usrp1_impl::tx_dsp_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()) { +    case DSP_PROP_NAME: +        val = str(boost::format("usrp1 duc %uX %s") +            % this->get_num_ducs() +            % (this->has_tx_halfband()? "+ hb" : "") +        ); +        return; + +    case DSP_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case DSP_PROP_FREQ_SHIFT: +        val = _tx_dsp_freqs[key.name]; +        return; + +    case DSP_PROP_FREQ_SHIFT_NAMES:{ +            prop_names_t names; +            for(size_t i = 0; i < this->get_num_ducs(); i++){ +                names.push_back(boost::lexical_cast<std::string>(i)); +            } +            val = names; +        } +        return; + +    case DSP_PROP_CODEC_RATE: +        val = _clock_ctrl->get_master_clock_freq() * 2; +        return; + +    case DSP_PROP_HOST_RATE: +        val = _clock_ctrl->get_master_clock_freq() * 2 / _tx_dsp_interp; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } + +} + +/*********************************************************************** + * TX DUC Set + **********************************************************************/ +void usrp1_impl::tx_dsp_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()) { + +    case DSP_PROP_FREQ_SHIFT: { +            double new_freq = val.as<double>(); + +            //map the freq shift key to a subdev spec to a particular codec chip +            std::string db_name = _tx_subdev_spec.at(boost::lexical_cast<size_t>(key.name)).db_name; +            if (db_name == "A") _codec_ctrls[DBOARD_SLOT_A]->set_duc_freq(new_freq); +            if (db_name == "B") _codec_ctrls[DBOARD_SLOT_B]->set_duc_freq(new_freq); + +            _tx_dsp_freqs[key.name] = new_freq; +            return; +        } + +    case DSP_PROP_HOST_RATE: { +            size_t rate = size_t(_clock_ctrl->get_master_clock_freq() * 2 / val.as<double>()); + +            if ((rate & 0x01) || (rate < 8) || (rate > 512)) { +                std::cerr << "Interpolation rate must be even and between 8 and 512" +                          << std::endl; +                return; +            } + +            _tx_dsp_interp = rate; + +            //TODO Poll every 100ms. Make it selectable?  +            _tx_samps_per_poll_interval = size_t(0.1 * _clock_ctrl->get_master_clock_freq() * 2 / rate); + +            _iface->poke32(FR_INTERP_RATE, _tx_dsp_interp / 4 - 1); +            return; +        } +    default: UHD_THROW_PROP_SET_ERROR(); +    } + +} diff --git a/host/lib/usrp/usrp1/io_impl.cpp b/host/lib/usrp/usrp1/io_impl.cpp new file mode 100644 index 000000000..6728d9b15 --- /dev/null +++ b/host/lib/usrp/usrp1/io_impl.cpp @@ -0,0 +1,313 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "../../transport/vrt_packet_handler.hpp" +#include "usrp_commands.h" +#include "usrp1_impl.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/convert_types.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; + +static const size_t alignment_padding = 512; + +/*********************************************************************** + * Helper struct to associate an offset with a buffer + **********************************************************************/ +class offset_send_buffer{ +public: +    typedef boost::shared_ptr<offset_send_buffer> sptr; + +    static sptr make(managed_send_buffer::sptr buff, size_t offset = 0){ +        return sptr(new offset_send_buffer(buff, offset)); +    } + +    //member variables +    managed_send_buffer::sptr buff; +    size_t offset; /* in bytes */ + +private: +    offset_send_buffer(managed_send_buffer::sptr buff, size_t offset): +        buff(buff), offset(offset){/* NOP */} +}; + +/*********************************************************************** + * IO Implementation Details + **********************************************************************/ +struct usrp1_impl::io_impl{ +    io_impl(zero_copy_if::sptr data_transport): +        data_transport(data_transport), +        underflow_poll_samp_count(0), +        overflow_poll_samp_count(0), +        curr_buff_committed(true), +        curr_buff(offset_send_buffer::make(data_transport->get_send_buff())) +    { +        /* NOP */ +    } + +    ~io_impl(void){ +        flush_send_buff(); +    } + +    zero_copy_if::sptr data_transport; + +    //state management for the vrt packet handler code +    vrt_packet_handler::recv_state packet_handler_recv_state; +    vrt_packet_handler::send_state packet_handler_send_state; + +    //state management for overflow and underflow +    size_t underflow_poll_samp_count; +    size_t overflow_poll_samp_count; + +    //wrapper around the actual send buffer interface +    //all of this to ensure only aligned lengths are committed +    //NOTE: you must commit before getting a new buffer +    //since the vrt packet handler obeys this, we are ok +    bool curr_buff_committed; +    offset_send_buffer::sptr curr_buff; +    void commit_send_buff(offset_send_buffer::sptr, offset_send_buffer::sptr, size_t); +    void flush_send_buff(void); +    bool get_send_buffs(vrt_packet_handler::managed_send_buffs_t &, double); +}; + +/*! + * Perform an actual commit on the send buffer: + * Copy the remainder of alignment to the next buffer. + * Commit the current buffer at multiples of alignment. + */ +void usrp1_impl::io_impl::commit_send_buff( +    offset_send_buffer::sptr curr, +    offset_send_buffer::sptr next, +    size_t num_bytes +){ +    //total number of bytes now in the current buffer +    size_t bytes_in_curr_buffer = curr->offset + num_bytes; + +    //calculate how many to commit and remainder +    size_t num_bytes_remaining = bytes_in_curr_buffer % alignment_padding; +    size_t num_bytes_to_commit = bytes_in_curr_buffer - num_bytes_remaining; + +    //copy the remainder into the next buffer +    std::memcpy( +        next->buff->cast<char *>() + next->offset, +        curr->buff->cast<char *>() + num_bytes_to_commit, +        num_bytes_remaining +    ); + +    //update the offset into the next buffer +    next->offset += num_bytes_remaining; + +    //commit the current buffer +    curr->buff->commit(num_bytes_to_commit); +    curr_buff_committed = true; +} + +/*! + * Flush the current buffer by padding out to alignment and committing. + */ +void usrp1_impl::io_impl::flush_send_buff(void){ +    //calculate the number of bytes to alignment +    size_t bytes_to_pad = (-1*curr_buff->offset)%alignment_padding; + +    //get the buffer, clear, and commit (really current buffer) +    vrt_packet_handler::managed_send_buffs_t buffs(1); +    if (this->get_send_buffs(buffs, 0.1)){ +        std::memset(buffs[0]->cast<void *>(), 0, bytes_to_pad); +        buffs[0]->commit(bytes_to_pad); +    } +} + +/*! + * Get a managed send buffer with the alignment padding: + * Always grab the next send buffer so we can timeout here. + */ +bool usrp1_impl::io_impl::get_send_buffs( +    vrt_packet_handler::managed_send_buffs_t &buffs, double timeout +){ +    UHD_ASSERT_THROW(curr_buff_committed and buffs.size() == 1); + +    //try to get a new managed buffer with timeout +    offset_send_buffer::sptr next_buff(offset_send_buffer::make(data_transport->get_send_buff(timeout))); +    if (not next_buff->buff.get()) return false; /* propagate timeout here */ + +    //calculate the buffer pointer and size given the offset +    //references to the buffers are held in the bound function +    buffs[0] = managed_send_buffer::make_safe( +        boost::asio::buffer( +            curr_buff->buff->cast<char *>() + curr_buff->offset, +            curr_buff->buff->size()         - curr_buff->offset +        ), +        boost::bind(&usrp1_impl::io_impl::commit_send_buff, this, curr_buff, next_buff, _1) +    ); + +    //store the next buffer for the next call +    curr_buff = next_buff; +    curr_buff_committed = false; + +    return true; +} + +/*********************************************************************** + * Initialize internals within this file + **********************************************************************/ +void usrp1_impl::io_init(void){ +    _rx_otw_type.width = 16; +    _rx_otw_type.shift = 0; +    _rx_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + +    _tx_otw_type.width = 16; +    _tx_otw_type.shift = 0; +    _tx_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + +    _io_impl = UHD_PIMPL_MAKE(io_impl, (_data_transport)); +} + +/*********************************************************************** + * Data send + helper functions + **********************************************************************/ +static void usrp1_bs_vrt_packer( +    boost::uint32_t *, +    vrt::if_packet_info_t &if_packet_info +){ +    if_packet_info.num_header_words32 = 0; +    if_packet_info.num_packet_words32 = if_packet_info.num_payload_words32; +} + +size_t usrp1_impl::get_max_send_samps_per_packet(void) const { +    return (_data_transport->get_send_frame_size() - alignment_padding) +        / _tx_otw_type.get_sample_size() +        / _tx_subdev_spec.size() +    ; +} + +size_t usrp1_impl::send( +    const std::vector<const void *> &buffs, size_t num_samps, +    const tx_metadata_t &metadata, const io_type_t &io_type, +    send_mode_t send_mode, double timeout +){ +    size_t num_samps_sent = vrt_packet_handler::send( +        _io_impl->packet_handler_send_state,       //last state of the send handler +        buffs, num_samps,                          //buffer to fill +        metadata, send_mode,                       //samples metadata +        io_type, _tx_otw_type,                     //input and output types to convert +        _clock_ctrl->get_master_clock_freq(),      //master clock tick rate +        &usrp1_bs_vrt_packer, +        boost::bind(&usrp1_impl::io_impl::get_send_buffs, _io_impl.get(), _1, timeout), +        get_max_send_samps_per_packet(), +        0,                                         //vrt header offset +        _tx_subdev_spec.size()                     //num channels +    ); + +    //Don't honor sob because it is normal to be always bursting... +    //handle eob flag (commit the buffer) +    if (metadata.end_of_burst) _io_impl->flush_send_buff(); + +    //handle the polling for underflow conditions +    _io_impl->underflow_poll_samp_count += num_samps_sent; +    if (_io_impl->underflow_poll_samp_count >= _tx_samps_per_poll_interval){ +        _io_impl->underflow_poll_samp_count = 0; //reset count +        boost::uint8_t underflow = 0; +        ssize_t ret = _ctrl_transport->usrp_control_read( +            VRQ_GET_STATUS, 0, GS_TX_UNDERRUN, +            &underflow, sizeof(underflow) +        ); +        if (ret < 0)        std::cerr << "USRP: underflow check failed" << std::endl; +        else if (underflow) std::cerr << "U" << std::flush; +    } + +    return num_samps_sent; +} + +/*********************************************************************** + * Data recv + helper functions + **********************************************************************/ +static void usrp1_bs_vrt_unpacker( +    const boost::uint32_t *, +    vrt::if_packet_info_t &if_packet_info +){ +    if_packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_DATA; +    if_packet_info.num_payload_words32 = if_packet_info.num_packet_words32; +    if_packet_info.num_header_words32 = 0; +    if_packet_info.packet_count = 0; +    if_packet_info.sob = false; +    if_packet_info.eob = false; +    if_packet_info.has_sid = false; +    if_packet_info.has_cid = false; +    if_packet_info.has_tsi = false; +    if_packet_info.has_tsf = false; +    if_packet_info.has_tlr = false; +} + +static bool get_recv_buffs( +    zero_copy_if::sptr zc_if, double timeout, +    vrt_packet_handler::managed_recv_buffs_t &buffs +){ +    UHD_ASSERT_THROW(buffs.size() == 1); +    buffs[0] = zc_if->get_recv_buff(timeout); +    return buffs[0].get() != NULL; +} + +size_t usrp1_impl::get_max_recv_samps_per_packet(void) const { +    return _data_transport->get_recv_frame_size() +        / _rx_otw_type.get_sample_size() +        / _rx_subdev_spec.size() +    ; +} + +size_t usrp1_impl::recv( +    const std::vector<void *> &buffs, size_t num_samps, +    rx_metadata_t &metadata, const io_type_t &io_type, +    recv_mode_t recv_mode, double timeout +){ +    size_t num_samps_recvd = vrt_packet_handler::recv( +        _io_impl->packet_handler_recv_state,       //last state of the recv handler +        buffs, num_samps,                          //buffer to fill +        metadata, recv_mode,                       //samples metadata +        io_type, _rx_otw_type,                     //input and output types to convert +        _clock_ctrl->get_master_clock_freq(),      //master clock tick rate +        &usrp1_bs_vrt_unpacker, +        boost::bind(&get_recv_buffs, _data_transport, timeout, _1), +        &vrt_packet_handler::handle_overflow_nop, +        0,                                         //vrt header offset +        _rx_subdev_spec.size()                     //num channels +    ); + +    //handle the polling for overflow conditions +    _io_impl->overflow_poll_samp_count += num_samps_recvd; +    if (_io_impl->overflow_poll_samp_count >= _rx_samps_per_poll_interval){ +        _io_impl->overflow_poll_samp_count = 0; //reset count +        boost::uint8_t overflow = 0; +        ssize_t ret = _ctrl_transport->usrp_control_read( +            VRQ_GET_STATUS, 0, GS_RX_OVERRUN, +            &overflow, sizeof(overflow) +        ); +        if (ret < 0)       std::cerr << "USRP: overflow check failed" << std::endl; +        else if (overflow) std::cerr << "O" << std::flush; +    } + +    return num_samps_recvd; +} diff --git a/host/lib/usrp/usrp1/mboard_impl.cpp b/host/lib/usrp/usrp1/mboard_impl.cpp new file mode 100644 index 000000000..4df5ada0a --- /dev/null +++ b/host/lib/usrp/usrp1/mboard_impl.cpp @@ -0,0 +1,389 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_impl.hpp" +#include "usrp_commands.h" +#include "fpga_regs_standard.h" +#include "fpga_regs_common.h" +#include "usrp_i2c_addr.h" +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/images.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/thread/thread.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +static const bool usrp1_mboard_verbose = false; + +/*********************************************************************** + * Calculate the RX mux value: + *    The I and Q mux values are intentionally reversed to flip I and Q + *    to account for the reversal in the type conversion routines. + **********************************************************************/ +static int calc_rx_mux_pair(int adc_for_i, int adc_for_q){ +    return (adc_for_i << 2) | (adc_for_q << 0); //shift reversal here +} + +/*! + *    3                   2                   1                   0 + *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------+-------+-------+-------+-------+-+-----+ + * |      must be zero     | Q3| I3| Q2| I2| Q1| I1| Q0| I0|Z| NCH | + * +-----------------------+-------+-------+-------+-------+-+-----+ + */ +static boost::uint32_t calc_rx_mux( +    const subdev_spec_t &subdev_spec, wax::obj mboard +){ +    //create look-up-table for mapping dboard name and connection type to ADC flags +    static const int ADC0 = 0, ADC1 = 1, ADC2 = 2, ADC3 = 3; +    static const uhd::dict<std::string, uhd::dict<subdev_conn_t, int> > name_to_conn_to_flag = boost::assign::map_list_of +        ("A", boost::assign::map_list_of +            (SUBDEV_CONN_COMPLEX_IQ, calc_rx_mux_pair(ADC0, ADC1)) //I and Q +            (SUBDEV_CONN_COMPLEX_QI, calc_rx_mux_pair(ADC1, ADC0)) //I and Q +            (SUBDEV_CONN_REAL_I,     calc_rx_mux_pair(ADC0, ADC0)) //I and Q (Q identical but ignored Z=1) +            (SUBDEV_CONN_REAL_Q,     calc_rx_mux_pair(ADC1, ADC1)) //I and Q (Q identical but ignored Z=1) +        ) +        ("B", boost::assign::map_list_of +            (SUBDEV_CONN_COMPLEX_IQ, calc_rx_mux_pair(ADC2, ADC3)) //I and Q +            (SUBDEV_CONN_COMPLEX_QI, calc_rx_mux_pair(ADC3, ADC2)) //I and Q +            (SUBDEV_CONN_REAL_I,     calc_rx_mux_pair(ADC2, ADC2)) //I and Q (Q identical but ignored Z=1) +            (SUBDEV_CONN_REAL_Q,     calc_rx_mux_pair(ADC3, ADC3)) //I and Q (Q identical but ignored Z=1) +        ) +    ; + +    //extract the number of channels +    size_t nchan = subdev_spec.size(); + +    //calculate the channel flags +    int channel_flags = 0; +    size_t num_reals = 0, num_quads = 0; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, subdev_spec){ +        wax::obj dboard = mboard[named_prop_t(MBOARD_PROP_RX_DBOARD, pair.db_name)]; +        wax::obj subdev = dboard[named_prop_t(DBOARD_PROP_SUBDEV, pair.sd_name)]; +        subdev_conn_t conn = subdev[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>(); +        switch(conn){ +        case SUBDEV_CONN_COMPLEX_IQ: +        case SUBDEV_CONN_COMPLEX_QI: num_quads++; break; +        case SUBDEV_CONN_REAL_I: +        case SUBDEV_CONN_REAL_Q:     num_reals++; break; +        } +        channel_flags = (channel_flags << 4) | name_to_conn_to_flag[pair.db_name][conn]; +    } + +    //calculate Z: +    //    for all real sources: Z = 1 +    //    for all quadrature sources: Z = 0 +    //    for mixed sources: warning + Z = 0 +    int Z = (num_quads > 0)? 0 : 1; +    if (num_quads != 0 and num_reals != 0) uhd::warning::post( +        "Mixing real and quadrature rx subdevices is not supported.\n" +        "The Q input to the real source(s) will be non-zero.\n" +    ); + +    //calculate the rx mux value +    return ((channel_flags & 0xffff) << 4) | ((Z & 0x1) << 3) | ((nchan & 0x7) << 0); +} + +/*********************************************************************** + * Calculate the TX mux value: + *    The I and Q mux values are intentionally reversed to flip I and Q + *    to account for the reversal in the type conversion routines. + **********************************************************************/ +static int calc_tx_mux_pair(int chn_for_i, int chn_for_q){ +    return (chn_for_i << 4) | (chn_for_q << 0); //shift reversal here +} + +/*! + *    3                   2                   1                   0 + *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------+-------+-------+-------+-------+-+-----+ + * |                       | DAC1Q | DAC1I | DAC0Q | DAC0I |0| NCH | + * +-----------------------------------------------+-------+-+-----+ + */ +static boost::uint32_t calc_tx_mux( +    const subdev_spec_t &subdev_spec, wax::obj mboard +){ +    //create look-up-table for mapping channel number and connection type to flags +    static const int ENB = 1 << 3, CHAN_I0 = 0, CHAN_Q0 = 1, CHAN_I1 = 2, CHAN_Q1 = 3; +    static const uhd::dict<size_t, uhd::dict<subdev_conn_t, int> > chan_to_conn_to_flag = boost::assign::map_list_of +        (0, boost::assign::map_list_of +            (SUBDEV_CONN_COMPLEX_IQ, calc_tx_mux_pair(CHAN_I0 | ENB, CHAN_Q0 | ENB)) +            (SUBDEV_CONN_COMPLEX_QI, calc_tx_mux_pair(CHAN_Q0 | ENB, CHAN_I0 | ENB)) +            (SUBDEV_CONN_REAL_I,     calc_tx_mux_pair(CHAN_I0 | ENB, 0            )) +            (SUBDEV_CONN_REAL_Q,     calc_tx_mux_pair(0,             CHAN_I0 | ENB)) +        ) +        (1, boost::assign::map_list_of +            (SUBDEV_CONN_COMPLEX_IQ, calc_tx_mux_pair(CHAN_I1 | ENB, CHAN_Q1 | ENB)) +            (SUBDEV_CONN_COMPLEX_QI, calc_tx_mux_pair(CHAN_Q1 | ENB, CHAN_I1 | ENB)) +            (SUBDEV_CONN_REAL_I,     calc_tx_mux_pair(CHAN_I1 | ENB, 0            )) +            (SUBDEV_CONN_REAL_Q,     calc_tx_mux_pair(0,             CHAN_I1 | ENB)) +        ) +    ; + +    //extract the number of channels +    size_t nchan = subdev_spec.size(); + +    //calculate the channel flags +    int channel_flags = 0, chan = 0; +    uhd::dict<std::string, int> slot_to_chan_count = boost::assign::map_list_of("A", 0)("B", 0); +    BOOST_FOREACH(const subdev_spec_pair_t &pair, subdev_spec){ +        wax::obj dboard = mboard[named_prop_t(MBOARD_PROP_TX_DBOARD, pair.db_name)]; +        wax::obj subdev = dboard[named_prop_t(DBOARD_PROP_SUBDEV, pair.sd_name)]; +        subdev_conn_t conn = subdev[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>(); + +        //combine the channel flags: shift for slot A vs B +        if (pair.db_name == "A") channel_flags |= chan_to_conn_to_flag[chan][conn] << 0; +        if (pair.db_name == "B") channel_flags |= chan_to_conn_to_flag[chan][conn] << 8; + +        //sanity check, only 1 channel per slot +        slot_to_chan_count[pair.db_name]++; +        if (slot_to_chan_count[pair.db_name] > 1){ +            throw std::runtime_error(str(boost::format( +                "dboard slot %s assigned to multiple channels in subdev spec %s" +            ) % pair.db_name % subdev_spec.to_string())); +        } + +        //increment for the next channel +        chan++; +    } + +    //calculate the tx mux value +    return ((channel_flags & 0xffff) << 4) | ((nchan & 0x7) << 0); +} + +/*! + * Capabilities Register + * + *    3                   2                   1                   0 + *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------------------------------+-+-----+-+-----+ + * |               Reserved                        |T|DUCs |R|DDCs | + * +-----------------------------------------------+-+-----+-+-----+ + */ +size_t usrp1_impl::get_num_ddcs(void){ +    boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); +    return (regval >> 0) & 0x0007; +} + +size_t usrp1_impl::get_num_ducs(void){ +    boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); +    return (regval >> 4) & 0x0007; +} + +bool usrp1_impl::has_rx_halfband(void){ +    boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); +    return (regval >> 3) & 0x0001; +} + +bool usrp1_impl::has_tx_halfband(void){ +    boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); +    return (regval >> 7) & 0x0001; +} + +/*********************************************************************** + * Mboard Initialization + **********************************************************************/ +void usrp1_impl::mboard_init(void) +{ +    _mboard_proxy = wax_obj_proxy::make( +                     boost::bind(&usrp1_impl::mboard_get, this, _1, _2), +                     boost::bind(&usrp1_impl::mboard_set, this, _1, _2)); + +    // Normal mode with no loopback or Rx counting +    _iface->poke32(FR_MODE, 0x00000000); +    _iface->poke32(FR_DEBUG_EN, 0x00000000); +    _iface->poke32(FR_RX_SAMPLE_RATE_DIV, 0x00000001); +    _iface->poke32(FR_TX_SAMPLE_RATE_DIV, 0x00000003); +    _iface->poke32(FR_DC_OFFSET_CL_EN, 0x0000000f); + +    // Reset offset correction registers +    _iface->poke32(FR_ADC_OFFSET_0, 0x00000000); +    _iface->poke32(FR_ADC_OFFSET_1, 0x00000000); +    _iface->poke32(FR_ADC_OFFSET_2, 0x00000000); +    _iface->poke32(FR_ADC_OFFSET_3, 0x00000000); + +    // Set default for RX format to 16-bit I&Q and no half-band filter bypass +    _iface->poke32(FR_RX_FORMAT, 0x00000300); + +    // Set default for TX format to 16-bit I&Q +    _iface->poke32(FR_TX_FORMAT, 0x00000000); + +    if (usrp1_mboard_verbose){ +        std::cout << "USRP1 Capabilities" << std::endl; +        std::cout << "    number of duc's: " << get_num_ddcs() << std::endl; +        std::cout << "    number of ddc's: " << get_num_ducs() << std::endl; +        std::cout << "    rx halfband:     " << has_rx_halfband() << std::endl; +        std::cout << "    tx halfband:     " << has_tx_halfband() << std::endl; +    } +} + +void usrp1_impl::issue_stream_cmd(const stream_cmd_t &stream_cmd) +{ +    switch(stream_cmd.stream_mode){ +    case stream_cmd_t::STREAM_MODE_START_CONTINUOUS: +        return _iface->write_firmware_cmd(VRQ_FPGA_SET_RX_ENABLE, true, 0, 0, 0); + +    case stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS: +        return _iface->write_firmware_cmd(VRQ_FPGA_SET_RX_ENABLE, false, 0, 0, 0); + +    default: throw std::runtime_error("unsupported stream command type for USRP1"); +    } +} + +/*********************************************************************** + * Mboard Get + **********************************************************************/ +static prop_names_t dboard_names = boost::assign::list_of("A")("B"); + +void usrp1_impl::mboard_get(const wax::obj &key_, wax::obj &val) +{ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<mboard_prop_t>()){ +    case MBOARD_PROP_NAME: +        val = std::string("usrp1 mboard - " + _iface->mb_eeprom["serial"]); +        return; + +    case MBOARD_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case MBOARD_PROP_RX_DBOARD: +        uhd::assert_has(dboard_names, key.name, "dboard name"); +        if (key.name == "A") val = _rx_dboard_proxies[DBOARD_SLOT_A]->get_link(); +        if (key.name == "B") val = _rx_dboard_proxies[DBOARD_SLOT_B]->get_link(); +        return; + +    case MBOARD_PROP_RX_DBOARD_NAMES: +        val = dboard_names; +        return; + +    case MBOARD_PROP_TX_DBOARD: +        uhd::assert_has(dboard_names, key.name, "dboard name"); +        if (key.name == "A") val = _tx_dboard_proxies[DBOARD_SLOT_A]->get_link(); +        if (key.name == "B") val = _tx_dboard_proxies[DBOARD_SLOT_B]->get_link(); +        return; + +    case MBOARD_PROP_TX_DBOARD_NAMES: +        val = dboard_names; +        return; + +    case MBOARD_PROP_RX_DSP: +        UHD_ASSERT_THROW(key.name == ""); +        val = _rx_dsp_proxy->get_link(); +        return; + +    case MBOARD_PROP_RX_DSP_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case MBOARD_PROP_TX_DSP: +        UHD_ASSERT_THROW(key.name == ""); +        val = _tx_dsp_proxy->get_link(); +        return; + +    case MBOARD_PROP_TX_DSP_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case MBOARD_PROP_CLOCK_CONFIG: +        val = _clock_config; +        return; + +    case MBOARD_PROP_RX_SUBDEV_SPEC: +        val = _rx_subdev_spec; +        return; + +    case MBOARD_PROP_TX_SUBDEV_SPEC: +        val = _tx_subdev_spec; +        return; + +    case MBOARD_PROP_EEPROM_MAP: +        val = _iface->mb_eeprom; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * Mboard Set + **********************************************************************/ +void usrp1_impl::mboard_set(const wax::obj &key, const wax::obj &val) +{ +    if(key.type() == typeid(std::string)) { +      if(key.as<std::string>() == "load_eeprom") { +        std::string usrp1_eeprom_image = val.as<std::string>(); +        std::cout << "USRP1 EEPROM image: " << usrp1_eeprom_image << std::endl; +        _ctrl_transport->usrp_load_eeprom(val.as<std::string>()); +      } +      return; +   	} + +    //handle the get request conditioned on the key +    switch(key.as<mboard_prop_t>()){ + +    case MBOARD_PROP_STREAM_CMD: +        issue_stream_cmd(val.as<stream_cmd_t>()); +        return; + +    case MBOARD_PROP_RX_SUBDEV_SPEC: +        _rx_subdev_spec = val.as<subdev_spec_t>(); +        if (_rx_subdev_spec.size() > this->get_num_ddcs()){ +            throw std::runtime_error(str(boost::format( +                "USRP1 suports up to %u RX channels.\n" +                "However, this RX subdev spec requires %u channels\n" +            ) % this->get_num_ddcs() % _rx_subdev_spec.size())); +        } +        verify_rx_subdev_spec(_rx_subdev_spec, _mboard_proxy->get_link()); +        //set the mux and set the number of rx channels +        _iface->poke32(FR_RX_MUX, calc_rx_mux(_rx_subdev_spec, _mboard_proxy->get_link())); +        return; + +    case MBOARD_PROP_TX_SUBDEV_SPEC: +        _tx_subdev_spec = val.as<subdev_spec_t>(); +        if (_tx_subdev_spec.size() > this->get_num_ducs()){ +            throw std::runtime_error(str(boost::format( +                "USRP1 suports up to %u TX channels.\n" +                "However, this TX subdev spec requires %u channels\n" +            ) % this->get_num_ducs() % _tx_subdev_spec.size())); +        } +        verify_tx_subdev_spec(_tx_subdev_spec, _mboard_proxy->get_link()); +        //set the mux and set the number of tx channels +        _iface->poke32(FR_TX_MUX, calc_tx_mux(_tx_subdev_spec, _mboard_proxy->get_link())); +        return; + +    case MBOARD_PROP_EEPROM_MAP: +        // Step1: commit the map, writing only those values set. +        // Step2: readback the entire eeprom map into the iface. +        val.as<mboard_eeprom_t>().commit(*_iface, mboard_eeprom_t::MAP_B000); +        _iface->mb_eeprom = mboard_eeprom_t(*_iface, mboard_eeprom_t::MAP_B000); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp1/usrp1_ctrl.cpp b/host/lib/usrp/usrp1/usrp1_ctrl.cpp new file mode 100644 index 000000000..5043aed7d --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_ctrl.cpp @@ -0,0 +1,453 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_ctrl.hpp" +#include "usrp_commands.h"  +#include <uhd/transport/usb_control.hpp> +#include <boost/functional/hash.hpp> +#include <boost/thread/thread.hpp> +#include <iostream> +#include <fstream> +#include <sstream> +#include <string> +#include <vector> +#include <cstring> + +using namespace uhd; + +enum firmware_code { +    USRP_FPGA_LOAD_SUCCESS, +    USRP_FPGA_ALREADY_LOADED, +    USRP_FIRMWARE_LOAD_SUCCESS, +    USRP_FIRMWARE_ALREADY_LOADED +}; + +#define FX2_FIRMWARE_LOAD 0xa0 + +static const bool load_img_msg = true; + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +/*! + * Create a file hash + * The hash will be used to identify the loaded firmware and fpga image  + * \param filename file used to generate hash value + * \return hash value in a size_t type + */ +static size_t generate_hash(const char *filename) +{ +    std::ifstream file(filename); +    if (!file) +        std::cerr << "error: cannot open input file " << filename << std::endl; + +    size_t hash = 0; + +    char ch; +    while (file.get(ch)) { +        boost::hash_combine(hash, ch); +    } + +    if (!file.eof()) +        std::cerr << "error: file error " << filename << std::endl; + +    file.close();  +    return hash;  +} + + +/*! + * Verify checksum of a Intel HEX record  + * \param record a line from an Intel HEX file + * \return true if record is valid, false otherwise  + */ +static bool checksum(std::string *record) +{ + +    size_t len = record->length(); +    unsigned int i; +    unsigned char sum = 0; +    unsigned int val; + +    for (i = 1; i < len; i += 2) { +        std::istringstream(record->substr(i, 2)) >> std::hex >> val; +        sum += val; +    } + +    if (sum == 0) +       return true; +    else +       return false; +} + + +/*! + * Parse Intel HEX record + * + * \param record a line from an Intel HEX file + * \param len output length of record + * \param addr output address + * \param type output type + * \param data output data + * \return true if record is sucessfully read, false on error + */ +bool parse_record(std::string *record, unsigned int &len, +                  unsigned int &addr, unsigned int &type, +                  unsigned char* data) +{ +    unsigned int i; +    std::string _data; +    unsigned int val; + +    if (record->substr(0, 1) != ":") +        return false; + +    std::istringstream(record->substr(1, 2)) >> std::hex >> len; +    std::istringstream(record->substr(3, 4)) >> std::hex >> addr; +    std::istringstream(record->substr(7, 2)) >> std::hex >> type; + +    for (i = 0; i < len; i++) { +        std::istringstream(record->substr(9 + 2 * i, 2)) >> std::hex >> val; +        data[i] = (unsigned char) val; +    }  + +    return true; +} + + +/*! + * USRP control implementation for device discovery and configuration + */ +class usrp_ctrl_impl : public usrp_ctrl { +public: +    usrp_ctrl_impl(uhd::transport::usb_control::sptr ctrl_transport) +    { +        _ctrl_transport = ctrl_transport; +    } + + +    ~usrp_ctrl_impl(void) +    { +        /* NOP */ +    } + + +    int usrp_load_firmware(std::string filestring, bool force) +    { +        const char *filename = filestring.c_str(); + +        size_t hash = generate_hash(filename); + +        size_t loaded_hash; +        if (usrp_get_firmware_hash(loaded_hash) < 0) { +            std::cerr << "firmware hash retrieval failed" << std::endl; +            return -1; +        } + +        if (!force && (hash == loaded_hash)) +            return USRP_FIRMWARE_ALREADY_LOADED; + +        //FIXME: verify types +        unsigned int len; +        unsigned int addr; +        unsigned int type; +        unsigned char data[512]; + +        int ret; +        std::ifstream file; +        file.open(filename, std::ifstream::in); + +        if (!file.good()) { +            std::cerr << "cannot open firmware input file" << std::endl; +            return -1;  +        } + +        unsigned char reset_y = 1; +        unsigned char reset_n = 0; + +        //hit the reset line +        if (load_img_msg) std::cout << "Loading firmware image: " << filestring << "..." << std::flush; +        usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, +                           &reset_y, 1); +  +        while (!file.eof()) { +           std::string record; +           file >> record; +       +            //check for valid record  +            if (!checksum(&record) ||  +                    !parse_record(&record, len, addr, type, data)) { +                std::cerr << "error: bad record" << std::endl; +                file.close(); +                return -1; +            } + +            //type 0x00 is data +            if (type == 0x00) { +               ret = usrp_control_write(FX2_FIRMWARE_LOAD, addr, 0, +                                        data, len); +               if (ret < 0) { +                    std::cerr << "error: usrp_control_write failed: "; +                    std::cerr << ret << std::endl; +                    file.close(); +                    return -1;  +                } +            }   +            //type 0x01 is end  +            else if (type == 0x01) { +                usrp_set_firmware_hash(hash); //set hash before reset +                usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, +                                   &reset_n, 1); +                file.close(); + +                //wait for things to settle +                boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); +                if (load_img_msg) std::cout << " done" << std::endl; +                return USRP_FIRMWARE_LOAD_SUCCESS;  +            } +            //type anything else is unhandled +            else { +                std::cerr << "error: unsupported record" << std::endl; +                file.close(); +                return -1;  +            } +        } + +        //file did not end  +        std::cerr << "error: bad record" << std::endl; +        file.close(); +        return -1; +    } + + +    int usrp_load_fpga(std::string filestring) +    { +        const char *filename = filestring.c_str(); + +        size_t hash = generate_hash(filename); + +        size_t loaded_hash; +        if (usrp_get_fpga_hash(loaded_hash) < 0) { +            std::cerr << "fpga hash retrieval failed" << std::endl; +            return -1; +        } + +        if (hash == loaded_hash) +            return USRP_FPGA_ALREADY_LOADED; +        const int ep0_size = 64; +        unsigned char buf[ep0_size]; +        int ret; + +        if (load_img_msg) std::cout << "Loading FPGA image: " << filestring << "..." << std::flush; +        std::ifstream file; +        file.open(filename, std::ios::in | std::ios::binary); +        if (not file.good()) { +            std::cerr << "cannot open fpga input file" << std::endl; +            file.close(); +            return -1; +        } + +        if (usrp_control_write_cmd(VRQ_FPGA_LOAD, 0, FL_BEGIN) < 0) { +            std::cerr << "fpga load error" << std::endl; +            file.close(); +            return -1; +        } + +        while (not file.eof()) { +            file.read((char *)buf, sizeof(buf)); +            size_t n = file.gcount(); +            ret = usrp_control_write(VRQ_FPGA_LOAD, 0, FL_XFER, +                                     buf, n); +            if (ret < 0 or size_t(ret) != n) { +                std::cerr << "fpga load error " << ret << std::endl; +                file.close(); +                return -1; +            } +        } +  +        if (usrp_control_write_cmd(VRQ_FPGA_LOAD, 0, FL_END) < 0) { +            std::cerr << "fpga load error" << std::endl; +            file.close(); +            return -1; +        } + +        usrp_set_fpga_hash(hash); +        file.close(); +        if (load_img_msg) std::cout << " done" << std::endl; +        return 0;  +    } + +    int usrp_load_eeprom(std::string filestring) +    { +        const char *filename = filestring.c_str(); +        const boost::uint16_t i2c_addr = 0x50; + +        //FIXME: verify types +        int len; +        unsigned int addr; +        unsigned char data[256]; +        unsigned char sendbuf[17]; + +        int ret; +        std::ifstream file; +        file.open(filename, std::ifstream::in); + +        if (!file.good()) { +            std::cerr << "cannot open EEPROM input file" << std::endl; +            return -1;  +        } + +        file.read((char *)data, 256); +        len = file.gcount(); + +        if(len == 256) { +          std::cerr << "error: image size too large" << std::endl; +          file.close(); +          return -1; +        } + +        const int pagesize = 16; +        addr = 0; +        while(len > 0) { +          sendbuf[0] = addr; +          memcpy(sendbuf+1, &data[addr], len > pagesize ? pagesize : len); +          ret = usrp_i2c_write(i2c_addr, sendbuf, (len > pagesize ? pagesize : len)+1); +          if (ret < 0) { +            std::cerr << "error: usrp_i2c_write failed: "; +            std::cerr << ret << std::endl; +            file.close(); +            return -1;  +          } +          addr += pagesize; +          len -= pagesize; +          boost::this_thread::sleep(boost::posix_time::milliseconds(100)); +        } +        file.close(); +        return 0; +    } + + +    int usrp_set_led(int led_num, bool on) +    { +        return usrp_control_write_cmd(VRQ_SET_LED, on, led_num); +    } + + +    int usrp_get_firmware_hash(size_t &hash) +    { +        return usrp_control_read(0xa0, USRP_HASH_SLOT_0_ADDR, 0,  +                                 (unsigned char*) &hash, sizeof(size_t)); +    } + + +    int usrp_set_firmware_hash(size_t hash) +    { +        return usrp_control_write(0xa0, USRP_HASH_SLOT_0_ADDR, 0, +                                  (unsigned char*) &hash, sizeof(size_t)); + +    } + + +    int usrp_get_fpga_hash(size_t &hash) +    { +        return usrp_control_read(0xa0, USRP_HASH_SLOT_1_ADDR, 0, +                                 (unsigned char*) &hash, sizeof(size_t)); +    } + + +    int usrp_set_fpga_hash(size_t hash) +    { +        return usrp_control_write(0xa0, USRP_HASH_SLOT_1_ADDR, 0, +                                  (unsigned char*) &hash, sizeof(size_t)); +    } + +    int usrp_tx_enable(bool on) +    { +        return usrp_control_write_cmd(VRQ_FPGA_SET_TX_ENABLE, on, 0); +    } + + +    int usrp_rx_enable(bool on) +    { +        return usrp_control_write_cmd(VRQ_FPGA_SET_RX_ENABLE, on, 0);  +    } + + +    int usrp_tx_reset(bool on) +    { +        return usrp_control_write_cmd(VRQ_FPGA_SET_TX_RESET, on, 0);  +    } + + +    int usrp_control_write(boost::uint8_t request, +                           boost::uint16_t value, +                           boost::uint16_t index, +                           unsigned char *buff, +                           boost::uint16_t length) +    { +        return _ctrl_transport->submit(VRT_VENDOR_OUT,     // bmReqeustType +                                       request,            // bRequest +                                       value,              // wValue +                                       index,              // wIndex +                                       buff,               // data +                                       length);            // wLength  +    } + + +    int usrp_control_read(boost::uint8_t request, +                          boost::uint16_t value, +                          boost::uint16_t index, +                          unsigned char *buff, +                          boost::uint16_t length) +    { +        return _ctrl_transport->submit(VRT_VENDOR_IN,      // bmReqeustType +                                       request,            // bRequest +                                       value,              // wValue +                                       index,              // wIndex +                                       buff,               // data +                                       length);            // wLength +    } + + +    int usrp_control_write_cmd(boost::uint8_t request, boost::uint16_t value, boost::uint16_t index) +    { +        return usrp_control_write(request, value, index, 0, 0); +    } + +    int usrp_i2c_write(boost::uint16_t i2c_addr, unsigned char *buf, boost::uint16_t len) +    { +        return usrp_control_write(VRQ_I2C_WRITE, i2c_addr, 0, buf, len); +    } + +    int usrp_i2c_read(boost::uint16_t i2c_addr, unsigned char *buf, boost::uint16_t len) +    { +        return usrp_control_read(VRQ_I2C_READ, i2c_addr, 0, buf, len); +    } + + + +private: +    uhd::transport::usb_control::sptr _ctrl_transport; +}; + +/*********************************************************************** + * Public make function for usrp_ctrl interface + **********************************************************************/ +usrp_ctrl::sptr usrp_ctrl::make(uhd::transport::usb_control::sptr ctrl_transport){ +    return sptr(new usrp_ctrl_impl(ctrl_transport)); +} + diff --git a/host/lib/usrp/usrp1/usrp1_ctrl.hpp b/host/lib/usrp/usrp1/usrp1_ctrl.hpp new file mode 100644 index 000000000..a02d9f96c --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_ctrl.hpp @@ -0,0 +1,163 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_CTRL_HPP +#define INCLUDED_USRP_CTRL_HPP + +#include <uhd/transport/usb_control.hpp>  +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class usrp_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp_ctrl> sptr; + +    /*! +     * Make a usrp control object from a control transport +     * \param ctrl_transport a USB control transport +     * \return a new usrp control object +     */ +    static sptr make(uhd::transport::usb_control::sptr ctrl_transport); + +    /*! +     * Load firmware in Intel HEX Format onto device  +     * \param filename name of firmware file +     * \param force reload firmware if already loaded +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_load_firmware(std::string filename, +                                   bool force = false) = 0; + +    /*! +     * Load fpga file onto usrp  +     * \param filename name of fpga image  +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_load_fpga(std::string filename) = 0; + +    /*! +     * Load USB descriptor file in Intel HEX format into EEPROM +     * \param filename name of EEPROM image  +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_load_eeprom(std::string filestring) = 0; + +    /*! +     * Set led usrp  +     * \param led_num which LED to control (0 or 1) +     * \param on turn LED on or off +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_set_led(int led_num, bool on) = 0; + +    /*! +     * Get firmware hash  +     * \param hash a size_t hash value +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_get_firmware_hash(size_t &hash) = 0; + +    /*! +     * Set firmware hash  +     * \param hash a size_t hash value +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_set_firmware_hash(size_t hash) = 0; +                               +    /*! +     * Get fpga hash  +     * \param hash a size_t hash value +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_get_fpga_hash(size_t &hash) = 0; + +    /*! +     * Set fpga hash  +     * \param hash a size_t hash value +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_set_fpga_hash(size_t hash) = 0; + +    /*! +     * Set rx enable or disable  +     * \param on enable or disable value +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_rx_enable(bool on) = 0; + +    /*! +     * Set rx enable or disable  +     * \param on enable or disable value +     * \return 0 on success, error code otherwise +     */ +    virtual int usrp_tx_enable(bool on) = 0; + +    /*! +     * Submit an IN transfer  +     * \param request device specific request  +     * \param value device specific field +     * \param index device specific field +     * \param buff buffer to place data +     * \return number of bytes read or error  +     */ +    virtual int usrp_control_read(boost::uint8_t request, +                                  boost::uint16_t value, +                                  boost::uint16_t index, +                                  unsigned char *buff, +                                  boost::uint16_t length) = 0; + +    /*! +     * Submit an OUT transfer  +     * \param request device specific request  +     * \param value device specific field +     * \param index device specific field +     * \param buff buffer of data to be sent  +     * \return number of bytes written or error  +     */ +    virtual int usrp_control_write(boost::uint8_t request, +                                   boost::uint16_t value, +                                   boost::uint16_t index, +                                   unsigned char *buff, +                                   boost::uint16_t length) = 0; + +    /*! +     * Perform an I2C write +     * \param i2c_addr I2C device address +     * \param buf data to be written  +     * \param len length of data in bytes +     * \return number of bytes written or error  +     */ + +    virtual int usrp_i2c_write(boost::uint16_t i2c_addr, +                               unsigned char *buf,  +                               boost::uint16_t len) = 0; + +    /*! +     * Perform an I2C read +     * \param i2c_addr I2C device address +     * \param buf data to be read  +     * \param len length of data in bytes +     * \return number of bytes read or error  +     */ + +    virtual int usrp_i2c_read(boost::uint16_t i2c_addr, +                               unsigned char *buf,  +                               boost::uint16_t len) = 0; + +}; + +#endif /* INCLUDED_USRP_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_iface.cpp b/host/lib/usrp/usrp1/usrp1_iface.cpp new file mode 100644 index 000000000..63fcd5777 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_iface.cpp @@ -0,0 +1,251 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_iface.hpp" +#include "usrp_commands.h" +#include <uhd/utils/assert.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/format.hpp> +#include <stdexcept> +#include <iostream> +#include <iomanip> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const bool iface_debug = false; + +class usrp1_iface_impl : public usrp1_iface{ +public: +    /******************************************************************* +     * Structors +     ******************************************************************/ +    usrp1_iface_impl(usrp_ctrl::sptr ctrl_transport) +    { +        _ctrl_transport = ctrl_transport; +        mb_eeprom = mboard_eeprom_t(*this, mboard_eeprom_t::MAP_B000); +    } + +    ~usrp1_iface_impl(void) +    { +        /* NOP */ +    } + +    /******************************************************************* +     * Peek and Poke +     ******************************************************************/ +    void poke32(boost::uint32_t addr, boost::uint32_t value) +    { +        boost::uint32_t swapped = uhd::htonx(value); + +        if (iface_debug) { +            std::cout.fill('0'); +            std::cout << "poke32("; +            std::cout << std::dec << std::setw(2) << addr << ", 0x"; +            std::cout << std::hex << std::setw(8) << value << ")" << std::endl; +        } + +        boost::uint8_t w_index_h = SPI_ENABLE_FPGA & 0xff; +        boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff; + +        int ret =_ctrl_transport->usrp_control_write( +                                          VRQ_SPI_WRITE, +                                          addr & 0x7f, +                                          (w_index_h << 8) | (w_index_l << 0), +                                          (unsigned char*) &swapped, +                                          sizeof(boost::uint32_t)); + +        if (ret < 0) +            std::cerr << "USRP: failed memory write: " << ret << std::endl; +    } + +    boost::uint32_t peek32(boost::uint32_t addr) +    { +        boost::uint32_t value_out; + +        boost::uint8_t w_index_h = SPI_ENABLE_FPGA & 0xff; +        boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff; + +        int ret = _ctrl_transport->usrp_control_read( +                                          VRQ_SPI_READ, +                                          0x80 | (addr & 0x7f), +                                          (w_index_h << 8) | (w_index_l << 0), +                                          (unsigned char*) &value_out, +                                          sizeof(boost::uint32_t)); + +        if (ret < 0) +            std::cerr << "USRP: failed memory read: " << ret << std::endl; + +        return uhd::ntohx(value_out); +    } + +    /******************************************************************* +     * I2C +     ******************************************************************/ +    static const size_t max_i2c_data_bytes = 64; + +    //TODO: make this handle EEPROM page sizes. right now you can't write over a 16-byte boundary. +    //to accomplish this you'll have to have addr offset as a separate parameter. + +    void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes) +    { +        UHD_ASSERT_THROW(bytes.size() < max_i2c_data_bytes); + +        unsigned char buff[max_i2c_data_bytes]; +        std::copy(bytes.begin(), bytes.end(), buff); + +        int ret = _ctrl_transport->usrp_i2c_write(addr & 0xff, +                                             buff, +                                             bytes.size()); + +        // TODO throw and catch i2c failures during eeprom read +        if (iface_debug && (ret < 0)) +            std::cerr << "USRP: failed i2c write: " << ret << std::endl; +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes) +    { +        UHD_ASSERT_THROW(num_bytes < max_i2c_data_bytes); + +        unsigned char buff[max_i2c_data_bytes]; +        int ret = _ctrl_transport->usrp_i2c_read(addr & 0xff, +                                            buff, +                                            num_bytes); + +        // TODO throw and catch i2c failures during eeprom read +        if (iface_debug && ((ret < 0) || (unsigned)ret < (num_bytes))) { +            std::cerr << "USRP: failed i2c read: " << ret << std::endl; +            return byte_vector_t(num_bytes, 0xff);  +        } + +        byte_vector_t out_bytes; +        for (size_t i = 0; i < num_bytes; i++) +            out_bytes.push_back(buff[i]); + +        return out_bytes;  +    } + +    /******************************************************************* +     * SPI +     * +     * For non-readback transactions use the SPI_WRITE command, which is +     * simpler and uses the USB control buffer for OUT data. No data +     * needs to be returned. +     * +     * For readback transactions use SPI_TRANSACT, which places up to +     * 4 bytes of OUT data in the device request fields and uses the +     * control buffer for IN data. +     ******************************************************************/ +    boost::uint32_t transact_spi(int which_slave, +                                 const spi_config_t &, +                                 boost::uint32_t bits, +                                 size_t num_bits, +                                 bool readback) +    { +        UHD_ASSERT_THROW((num_bits <= 32) && !(num_bits % 8)); +        size_t num_bytes = num_bits / 8; + +        // Byteswap on num_bytes +        unsigned char buff[4] = { 0 }; +        for (size_t i = 1; i <= num_bytes; i++) +            buff[num_bytes - i] = (bits >> ((i - 1) * 8)) & 0xff; + +        if (readback) { +            boost::uint8_t w_len_h = which_slave & 0xff; +            boost::uint8_t w_len_l = num_bytes & 0xff; + +            int ret = _ctrl_transport->usrp_control_read( +                                         VRQ_SPI_TRANSACT, +                                         (buff[0] << 8) | (buff[1] << 0),  +                                         (buff[2] << 8) | (buff[3] << 0), +                                         buff, +                                         (w_len_h << 8) | (w_len_l << 0)); + +            if (ret < 0) { +                std::cout << "USRP: failed SPI readback transaction: " +                          << std::dec << ret << std::endl; +            } + +            boost::uint32_t val = (((boost::uint32_t)buff[0]) <<  0) | +                                  (((boost::uint32_t)buff[1]) <<  8) | +                                  (((boost::uint32_t)buff[2]) << 16) | +                                  (((boost::uint32_t)buff[3]) << 24); +            return val;  +        } +        else { +            boost::uint8_t w_index_h = which_slave & 0xff; +            boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_0) & 0xff; + +            int ret =_ctrl_transport->usrp_control_write( +                                          VRQ_SPI_WRITE, +                                          0x00, +                                          (w_index_h << 8) | (w_index_l << 0), +                                          buff, num_bytes); + +            if (ret < 0) { +                std::cout << "USRP: failed SPI transaction: " +                          << std::dec << ret << std::endl; +            } + +            return 0; +        } +    } + +    /******************************************************************* +     * Firmware  +     * +     * This call is deprecated. +     ******************************************************************/ +    void write_firmware_cmd(boost::uint8_t request, +                            boost::uint16_t value, +                            boost::uint16_t index, +                            unsigned char *buff, +                            boost::uint16_t length) +    { +        int ret; + +        if (request & 0x80) { +            ret = _ctrl_transport->usrp_control_read(request, +                                                     value, +                                                     index, +                                                     buff, +                                                     length); +        } +        else { +            ret = _ctrl_transport->usrp_control_write(request, +                                                      value, +                                                      index, +                                                      buff, +                                                      length); +        } + +        if (ret < 0) +            std::cerr << "USRP: failed firmware command: " << ret << std::endl; +    } + +private: +    usrp_ctrl::sptr _ctrl_transport; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +usrp1_iface::sptr usrp1_iface::make(usrp_ctrl::sptr ctrl_transport) +{ +    return sptr(new usrp1_iface_impl(ctrl_transport)); +} diff --git a/host/lib/usrp/usrp1/usrp1_iface.hpp b/host/lib/usrp/usrp1/usrp1_iface.hpp new file mode 100644 index 000000000..34a2330b5 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_iface.hpp @@ -0,0 +1,89 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP1_IFACE_HPP +#define INCLUDED_USRP1_IFACE_HPP + +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include "usrp1_ctrl.hpp" + +/*! + * The usrp1 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp1_iface : boost::noncopyable, public uhd::i2c_iface{ +public: +    typedef boost::shared_ptr<usrp1_iface> sptr; + +    /*! +     * Make a new usrp1 interface with the control transport. +     * \param ctrl_transport the usrp controller object +     * \return a new usrp1 interface object +     */ +    static sptr make(usrp_ctrl::sptr ctrl_transport); + +    /*! +     * Write a register (32 bits) +     * \param addr the address +     * \param data the 32bit data +     */ +    virtual void poke32(boost::uint32_t addr, boost::uint32_t data) = 0; + +    /*! +     * Read a register (32 bits) +     * \param addr the address +     * \return the 32bit data +     */ +    virtual boost::uint32_t peek32(boost::uint32_t addr) = 0; + +    /*! +     * Perform an spi transaction. +     * \param which_slave the slave device number +     * \param config spi config args +     * \param data the bits to write +     * \param num_bits how many bits in data +     * \param readback true to readback a value +     * \return spi data if readback set +     */ +    virtual boost::uint32_t transact_spi(int which_slave, +                                         const uhd::spi_config_t &config, +                                         boost::uint32_t data, +                                         size_t num_bits, +                                         bool readback) = 0; + +    /*! +     * Perform a general USB firmware OUT operation +     * \param request  +     * \param value +     * \param index  +     * \param data  +     * \return  +     */ +    virtual void write_firmware_cmd(boost::uint8_t request, +                                   boost::uint16_t value, +                                   boost::uint16_t index, +                                   unsigned char* buff, +                                   boost::uint16_t length) = 0; + +    uhd::usrp::mboard_eeprom_t mb_eeprom; +}; + +#endif /* INCLUDED_USRP1_IFACE_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp new file mode 100644 index 000000000..6016b0979 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -0,0 +1,245 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_impl.hpp" +#include "usrp1_ctrl.hpp" +#include "fpga_regs_standard.h" +#include "usrp_spi_defs.h" +#include <uhd/transport/usb_control.hpp> +#include <uhd/usrp/device_props.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +const boost::uint16_t USRP1_VENDOR_ID  = 0xfffe; +const boost::uint16_t USRP1_PRODUCT_ID = 0x0002; +const boost::uint16_t FX2_VENDOR_ID    = 0x04b4; +const boost::uint16_t FX2_PRODUCT_ID   = 0x8613; + +const std::vector<usrp1_impl::dboard_slot_t> usrp1_impl::_dboard_slots = boost::assign::list_of +    (usrp1_impl::DBOARD_SLOT_A)(usrp1_impl::DBOARD_SLOT_B) +; + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t usrp1_find(const device_addr_t &hint) +{ +    device_addrs_t usrp1_addrs; + +    //return an empty list of addresses when type is set to non-usrp1 +    if (hint.has_key("type") and hint["type"] != "usrp1") return usrp1_addrs; + +    //Return an empty list of addresses when an address is specified, +    //since an address is intended for a different, non-USB, device. +    if (hint.has_key("addr")) return usrp1_addrs; + +    //extract the firmware path for the USRP1 +    std::string usrp1_fw_image; +    try{ +        usrp1_fw_image = find_image_path(hint.get("fw", "usrp1_fw.ihx")); +    } +    catch(...){ +        uhd::warning::post( +            "Could not locate USRP1 firmware.\n" +            "Please install the images package.\n" +        ); +        return usrp1_addrs; +    } +    //std::cout << "USRP1 firmware image: " << usrp1_fw_image << std::endl; + +    boost::uint16_t vid = hint.has_key("uninit") ? FX2_VENDOR_ID : USRP1_VENDOR_ID; +    boost::uint16_t pid = hint.has_key("uninit") ? FX2_PRODUCT_ID : USRP1_PRODUCT_ID; + +    // Important note: +    // The get device list calls are nested inside the for loop. +    // This allows the usb guts to decontruct when not in use, +    // so that re-enumeration after fw load can occur successfully. +    // This requirement is a courtesy of libusb1.0 on windows. + +    //find the usrps and load firmware +    BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { +        usrp_ctrl::make(usb_control::make(handle))->usrp_load_firmware(usrp1_fw_image); +    } + +    //get descriptors again with serial number, but using the initialized VID/PID now since we have firmware +    vid = USRP1_VENDOR_ID; +    pid = USRP1_PRODUCT_ID; + +    BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { +        usrp1_iface::sptr iface = usrp1_iface::make(usrp_ctrl::make(usb_control::make(handle))); +        device_addr_t new_addr; +        new_addr["type"] = "usrp1"; +        new_addr["name"] = iface->mb_eeprom["name"]; +        new_addr["serial"] = handle->get_serial(); +        //this is a found usrp1 when the hint serial and name match or blank +        if ( +            (not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +            (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) +        ){ +            usrp1_addrs.push_back(new_addr); +        } +    } + +    return usrp1_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp1_make(const device_addr_t &device_addr){ + +    //extract the FPGA path for the USRP1 +    std::string usrp1_fpga_image = find_image_path( +        device_addr.get("fpga", "usrp1_fpga.rbf") +    ); +    //std::cout << "USRP1 FPGA image: " << usrp1_fpga_image << std::endl; + +    //try to match the given device address with something on the USB bus +    std::vector<usb_device_handle::sptr> device_list = +        usb_device_handle::get_device_list(USRP1_VENDOR_ID, USRP1_PRODUCT_ID); + +    //locate the matching handle in the device list +    usb_device_handle::sptr handle; +    BOOST_FOREACH(usb_device_handle::sptr dev_handle, device_list) { +        if (dev_handle->get_serial() == device_addr["serial"]){ +            handle = dev_handle; +            break; +        } +    } +    UHD_ASSERT_THROW(handle.get() != NULL); //better be found + +    //create control objects and a data transport +    usb_control::sptr ctrl_transport = usb_control::make(handle); +    usrp_ctrl::sptr usrp_ctrl = usrp_ctrl::make(ctrl_transport); +    usrp_ctrl->usrp_load_fpga(usrp1_fpga_image); +    usb_zero_copy::sptr data_transport = usb_zero_copy::make( +        handle,        // identifier +        6,             // IN endpoint +        2,             // OUT endpoint +        device_addr    // param hints +    ); + +    //create the usrp1 implementation guts +    return device::sptr(new usrp1_impl(data_transport, usrp_ctrl)); +} + +UHD_STATIC_BLOCK(register_usrp1_device){ +    device::register_device(&usrp1_find, &usrp1_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp1_impl::usrp1_impl(uhd::transport::usb_zero_copy::sptr data_transport, +                       usrp_ctrl::sptr ctrl_transport) + : _data_transport(data_transport), _ctrl_transport(ctrl_transport) +{ +    _iface = usrp1_iface::make(ctrl_transport); + +    //create clock interface +    _clock_ctrl = usrp1_clock_ctrl::make(_iface); + +    //create codec interface +    _codec_ctrls[DBOARD_SLOT_A] = usrp1_codec_ctrl::make( +        _iface, _clock_ctrl, SPI_ENABLE_CODEC_A +    ); +    _codec_ctrls[DBOARD_SLOT_B] = usrp1_codec_ctrl::make( +        _iface, _clock_ctrl, SPI_ENABLE_CODEC_B +    ); + +    //initialize the codecs +    codec_init(); + +    //initialize the mboard +    mboard_init(); + +    //initialize the dboards +    dboard_init(); + +    //initialize the dsps +    rx_dsp_init(); + +    //initialize the dsps +    tx_dsp_init(); + +    //initialize the send/recv +    io_init(); + +    //turn on the transmitter +    _ctrl_transport->usrp_tx_enable(true); + +    //init the subdev specs +    this->mboard_set(MBOARD_PROP_RX_SUBDEV_SPEC, subdev_spec_t()); +    this->mboard_set(MBOARD_PROP_TX_SUBDEV_SPEC, subdev_spec_t()); +} + +usrp1_impl::~usrp1_impl(void){ +    /* NOP */ +} + +bool usrp1_impl::recv_async_msg(uhd::async_metadata_t &, double timeout){ +    //dummy fill-in for the recv_async_msg +    boost::this_thread::sleep(boost::posix_time::microseconds(long(timeout*1e6))); +    return false; +} + +/*********************************************************************** + * Device Get + **********************************************************************/ +void usrp1_impl::get(const wax::obj &key_, wax::obj &val) +{ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<device_prop_t>()){ +    case DEVICE_PROP_NAME: +        val = std::string("usrp1 device"); +        return; + +    case DEVICE_PROP_MBOARD: +        UHD_ASSERT_THROW(key.name == ""); +        val = _mboard_proxy->get_link(); +        return; + +    case DEVICE_PROP_MBOARD_NAMES: +        val = prop_names_t(1, ""); //vector of size 1 with empty string +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * Device Set + **********************************************************************/ +void usrp1_impl::set(const wax::obj &, const wax::obj &) +{ +    UHD_THROW_PROP_SET_ERROR(); +} diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp new file mode 100644 index 000000000..ff4d40762 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -0,0 +1,206 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp1_iface.hpp" +#include "usrp1_ctrl.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/device.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/transport/usb_zero_copy.hpp> + +#ifndef INCLUDED_USRP1_IMPL_HPP +#define INCLUDED_USRP1_IMPL_HPP + +/*! + * Simple wax obj proxy class: + * Provides a wax obj interface for a set and a get function. + * This allows us to create nested properties structures + * while maintaining flattened code within the implementation. + */ +class wax_obj_proxy : public wax::obj { +public: +    typedef boost::function<void(const wax::obj &, wax::obj &)>       get_t; +    typedef boost::function<void(const wax::obj &, const wax::obj &)> set_t; +    typedef boost::shared_ptr<wax_obj_proxy> sptr; + +    static sptr make(const get_t &get, const set_t &set){ +        return sptr(new wax_obj_proxy(get, set)); +    } + +private: +    get_t _get; set_t _set; +    wax_obj_proxy(const get_t &get, const set_t &set): _get(get), _set(set) {}; +    void get(const wax::obj &key, wax::obj &val) {return _get(key, val);} +    void set(const wax::obj &key, const wax::obj &val) {return _set(key, val);} +}; + +/*! + * USRP1 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class usrp1_impl : public uhd::device { +public: +    //! used everywhere to differentiate slots/sides... +    enum dboard_slot_t{ +        DBOARD_SLOT_A = 'A', +        DBOARD_SLOT_B = 'B' +    }; +    //and a way to enumerate through a list of the above... +    static const std::vector<dboard_slot_t> _dboard_slots; + +    //structors +    usrp1_impl(uhd::transport::usb_zero_copy::sptr data_transport, +               usrp_ctrl::sptr ctrl_transport); + +    ~usrp1_impl(void); + +    //the io interface +    size_t send(const std::vector<const void *> &, +                size_t, +                const uhd::tx_metadata_t &, +                const uhd::io_type_t &, +                send_mode_t, double); + +    size_t recv(const std::vector<void *> &, +                size_t, uhd::rx_metadata_t &, +                const uhd::io_type_t &, +                recv_mode_t, double); + +    size_t get_max_send_samps_per_packet(void) const; + +    size_t get_max_recv_samps_per_packet(void) const; + +    bool recv_async_msg(uhd::async_metadata_t &, double); + +private: +    /*! +     * Make a usrp1 dboard interface. +     * \param iface the usrp1 interface object +     * \param clock the clock control interface +     * \param codec the codec control interface +     * \param dboard_slot the slot identifier +     * \param rx_dboard_id the db id for the rx board (used for evil dbsrx purposes) +     * \return a sptr to a new dboard interface +     */ +    static uhd::usrp::dboard_iface::sptr make_dboard_iface( +        usrp1_iface::sptr iface, +        usrp1_clock_ctrl::sptr clock, +        usrp1_codec_ctrl::sptr codec, +        dboard_slot_t dboard_slot, +        const uhd::usrp::dboard_id_t &rx_dboard_id +    ); + +    //interface to ioctls and file descriptor +    usrp1_iface::sptr _iface; + +    //handle io stuff +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void io_init(void); +    void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd); +    void handle_overrun(size_t); + +    //underrun and overrun poll intervals +    size_t _rx_samps_per_poll_interval; +    size_t _tx_samps_per_poll_interval;  + +    //otw types +    uhd::otw_type_t _rx_otw_type; +    uhd::otw_type_t _tx_otw_type; + +    //configuration shadows +    uhd::clock_config_t _clock_config; +    uhd::usrp::subdev_spec_t _rx_subdev_spec, _tx_subdev_spec; + +    //clock control +    usrp1_clock_ctrl::sptr _clock_ctrl; + +    //ad9862 codec control interface +    uhd::dict<dboard_slot_t, usrp1_codec_ctrl::sptr> _codec_ctrls; + +    //codec properties interfaces +    void codec_init(void); +    void rx_codec_get(const wax::obj &, wax::obj &, dboard_slot_t); +    void rx_codec_set(const wax::obj &, const wax::obj &, dboard_slot_t); +    void tx_codec_get(const wax::obj &, wax::obj &, dboard_slot_t); +    void tx_codec_set(const wax::obj &, const wax::obj &, dboard_slot_t); +    uhd::dict<dboard_slot_t, wax_obj_proxy::sptr> _rx_codec_proxies, _tx_codec_proxies; + +    //device functions and settings +    void get(const wax::obj &, wax::obj &); +    void set(const wax::obj &, const wax::obj &); + +    //mboard functions and settings +    void mboard_init(void); +    void mboard_get(const wax::obj &, wax::obj &); +    void mboard_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _mboard_proxy; + +    //xx dboard functions and settings +    void dboard_init(void); +    uhd::dict<dboard_slot_t, uhd::usrp::dboard_manager::sptr> _dboard_managers; +    uhd::dict<dboard_slot_t, uhd::usrp::dboard_iface::sptr> _dboard_ifaces; + +    //rx dboard functions and settings +    uhd::dict<dboard_slot_t, uhd::usrp::dboard_eeprom_t> _rx_db_eeproms; +    void rx_dboard_get(const wax::obj &, wax::obj &, dboard_slot_t); +    void rx_dboard_set(const wax::obj &, const wax::obj &, dboard_slot_t); +    uhd::dict<dboard_slot_t, wax_obj_proxy::sptr> _rx_dboard_proxies; + +    //tx dboard functions and settings +    uhd::dict<dboard_slot_t, uhd::usrp::dboard_eeprom_t> _tx_db_eeproms; +    void tx_dboard_get(const wax::obj &, wax::obj &, dboard_slot_t); +    void tx_dboard_set(const wax::obj &, const wax::obj &, dboard_slot_t); +    uhd::dict<dboard_slot_t, wax_obj_proxy::sptr> _tx_dboard_proxies; + +    //rx dsp functions and settings +    void rx_dsp_init(void); +    void rx_dsp_get(const wax::obj &, wax::obj &); +    void rx_dsp_set(const wax::obj &, const wax::obj &); +    uhd::dict<std::string, double> _rx_dsp_freqs; +    size_t _rx_dsp_decim; +    wax_obj_proxy::sptr _rx_dsp_proxy; + +    //tx dsp functions and settings +    void tx_dsp_init(void); +    void tx_dsp_get(const wax::obj &, wax::obj &); +    void tx_dsp_set(const wax::obj &, const wax::obj &); +    uhd::dict<std::string, double> _tx_dsp_freqs; +    size_t _tx_dsp_interp; +    wax_obj_proxy::sptr _tx_dsp_proxy; + +    //transports +    uhd::transport::usb_zero_copy::sptr _data_transport; +    usrp_ctrl::sptr _ctrl_transport; + +    //capabilities +    size_t get_num_ducs(void); +    size_t get_num_ddcs(void); +    bool has_rx_halfband(void); +    bool has_tx_halfband(void); +}; + +#endif /* INCLUDED_USRP1_IMPL_HPP */ diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt new file mode 100644 index 000000000..afd69cae9 --- /dev/null +++ b/host/lib/usrp/usrp2/CMakeLists.txt @@ -0,0 +1,48 @@ +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +#This file will be included by cmake, use absolute paths! + +######################################################################## +# Conditionally configure the USRP2 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 TRUE) + +IF(ENABLE_USRP2) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/clock_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/clock_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/codec_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/codec_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/codec_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/dboard_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/dboard_iface.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/dsp_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/gps_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/gps_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/io_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/mboard_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/serdes_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/serdes_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/usrp2_iface.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/usrp2_iface.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/usrp2_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/usrp2_impl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/usrp2_regs.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp2/usrp2_regs.cpp +    ) +ENDIF(ENABLE_USRP2) diff --git a/host/lib/usrp/usrp2/clock_ctrl.cpp b/host/lib/usrp/usrp2/clock_ctrl.cpp new file mode 100644 index 000000000..428d5539b --- /dev/null +++ b/host/lib/usrp/usrp2/clock_ctrl.cpp @@ -0,0 +1,313 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9510_regs.hpp" +#include "usrp2_regs.hpp" //spi slave constants +#include "usrp2_clk_regs.hpp" +#include <uhd/utils/assert.hpp> +#include <boost/cstdint.hpp> +#include <boost/lexical_cast.hpp> +#include <iostream> + +using namespace uhd; + +/*! + * A usrp2 clock control specific to the ad9510 ic. + */ +class usrp2_clock_ctrl_impl : public usrp2_clock_ctrl{ +public: +    usrp2_clock_ctrl_impl(usrp2_iface::sptr iface){ +        _iface = iface; +        clk_regs = usrp2_clk_regs_t(_iface->get_rev()); + +        _ad9510_regs.cp_current_setting = ad9510_regs_t::CP_CURRENT_SETTING_3_0MA; +        this->write_reg(clk_regs.pll_3); + +        // Setup the clock registers to 100MHz: +        //  This was already done by the firmware (or the host couldnt communicate). +        //  We could remove this part, and just leave it to the firmware. +        //  But why not leave it in for those who want to mess with clock settings? +        //  100mhz = 10mhz/R * (P*B + A) + +        _ad9510_regs.pll_power_down = ad9510_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9510_regs.prescaler_value = ad9510_regs_t::PRESCALER_VALUE_DIV2; +        this->write_reg(clk_regs.pll_4); + +        _ad9510_regs.acounter = 0; +        this->write_reg(clk_regs.acounter); + +        _ad9510_regs.bcounter_msb = 0; +        _ad9510_regs.bcounter_lsb = 5; +        this->write_reg(clk_regs.bcounter_msb); +        this->write_reg(clk_regs.bcounter_lsb); + +        _ad9510_regs.ref_counter_msb = 0; +        _ad9510_regs.ref_counter_lsb = 1; // r divider = 1 +        this->write_reg(clk_regs.ref_counter_msb); +        this->write_reg(clk_regs.ref_counter_lsb); + +        /* regs will be updated in commands below */ + +        this->enable_external_ref(false); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); + +        /* private clock enables, must be set here */ +        this->enable_dac_clock(true); +        this->enable_adc_clock(true); + +        /* always driving the mimo reference */ +        this->enable_mimo_clock_out(true); +    } + +    ~usrp2_clock_ctrl_impl(void){ +        //power down clock outputs +        this->enable_external_ref(false); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +        this->enable_dac_clock(false); +        this->enable_adc_clock(false); +        this->enable_mimo_clock_out(false); +    } + +    void enable_mimo_clock_out(bool enb){ +        //calculate the low and high dividers +        size_t divider = size_t(this->get_master_clock_rate()/10e6); +        size_t high = divider/2; +        size_t low = divider - high; + +        switch(clk_regs.exp){ +        case 2: //U2 rev 3 +            _ad9510_regs.power_down_lvpecl_out2 = enb? +                ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_NORMAL : +                ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_SAFE_PD; +            _ad9510_regs.output_level_lvpecl_out2 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT2_810MV; +            //set the registers (divider - 1) +            _ad9510_regs.divider_low_cycles_out2 = low - 1; +            _ad9510_regs.divider_high_cycles_out2 = high - 1; +            _ad9510_regs.bypass_divider_out2 = 0; +            break; + +        case 5: //U2 rev 4 +            _ad9510_regs.power_down_lvds_cmos_out5 = enb? 0 : 1; +            _ad9510_regs.lvds_cmos_select_out5 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT5_LVDS; +            _ad9510_regs.output_level_lvds_out5 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT5_1_75MA; +            //set the registers (divider - 1) +            _ad9510_regs.divider_low_cycles_out5 = low - 1; +            _ad9510_regs.divider_high_cycles_out5 = high - 1; +            _ad9510_regs.bypass_divider_out5 = 0; +            break; +             +        case 6: //U2+ +            _ad9510_regs.power_down_lvds_cmos_out6 = enb? 0 : 1; +            _ad9510_regs.lvds_cmos_select_out6 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT6_LVDS; +            _ad9510_regs.output_level_lvds_out6 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT6_1_75MA; +            //set the registers (divider - 1) +            _ad9510_regs.divider_low_cycles_out6 = low - 1; +            _ad9510_regs.divider_high_cycles_out6 = high - 1; +            _ad9510_regs.bypass_divider_out5 = 0; +            break; + +        default: +            break; +        } +        this->write_reg(clk_regs.output(clk_regs.exp)); +        this->write_reg(clk_regs.div_lo(clk_regs.exp)); +        this->update_regs(); +    } + +    //uses output clock 7 (cmos) +    void enable_rx_dboard_clock(bool enb){ +        _ad9510_regs.power_down_lvds_cmos_out7 = enb? 0 : 1; +        _ad9510_regs.lvds_cmos_select_out7 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT7_CMOS; +        _ad9510_regs.output_level_lvds_out7 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT7_1_75MA; +        this->write_reg(clk_regs.output(clk_regs.rx_db)); +        this->update_regs(); +    } + +    void set_rate_rx_dboard_clock(double rate){ +        assert_has(get_rates_rx_dboard_clock(), rate, "rx dboard clock rate"); +        size_t divider = size_t(get_master_clock_rate()/rate); +        //bypass when the divider ratio is one +        _ad9510_regs.bypass_divider_out7 = (divider == 1)? 1 : 0; +        //calculate the low and high dividers +        size_t high = divider/2; +        size_t low = divider - high; +        //set the registers (divider - 1) +        _ad9510_regs.divider_low_cycles_out7 = low - 1; +        _ad9510_regs.divider_high_cycles_out7 = high - 1; +        //write the registers +        this->write_reg(clk_regs.div_lo(clk_regs.rx_db)); +        this->write_reg(clk_regs.div_hi(clk_regs.rx_db)); +        this->update_regs(); +    } + +    std::vector<double> get_rates_rx_dboard_clock(void){ +        std::vector<double> rates; +        for (size_t i = 1; i <= 16+16; i++) rates.push_back(get_master_clock_rate()/i); +        return rates; +    } + +    //uses output clock 6 (cmos) on USRP2 and output clock 5 (cmos) on USRP2+ +    void enable_tx_dboard_clock(bool enb){ +        switch(clk_regs.tx_db) { +        case 5: //USRP2+ +          _ad9510_regs.power_down_lvds_cmos_out5 = enb? 0 : 1; +          _ad9510_regs.lvds_cmos_select_out5 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT5_CMOS; +          _ad9510_regs.output_level_lvds_out5 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT5_1_75MA; +          break; +        case 6: //USRP2 +          _ad9510_regs.power_down_lvds_cmos_out6 = enb? 0 : 1; +          _ad9510_regs.lvds_cmos_select_out6 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT6_CMOS; +          _ad9510_regs.output_level_lvds_out6 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT6_1_75MA; +          break; +        } + +        this->write_reg(clk_regs.output(clk_regs.tx_db)); +        this->update_regs(); +    } + +    void set_rate_tx_dboard_clock(double rate){ +        assert_has(get_rates_tx_dboard_clock(), rate, "tx dboard clock rate"); +        size_t divider = size_t(get_master_clock_rate()/rate); +        //bypass when the divider ratio is one +        _ad9510_regs.bypass_divider_out6 = (divider == 1)? 1 : 0; +        //calculate the low and high dividers +        size_t high = divider/2; +        size_t low = divider - high; + +        switch(clk_regs.tx_db) { +        case 5: //USRP2+ +          _ad9510_regs.bypass_divider_out5 = (divider == 1)? 1 : 0; +          _ad9510_regs.divider_low_cycles_out5 = low - 1; +          _ad9510_regs.divider_high_cycles_out5 = high - 1; +          break; +        case 6: //USRP2 +          //bypass when the divider ratio is one +          _ad9510_regs.bypass_divider_out6 = (divider == 1)? 1 : 0; +          //set the registers (divider - 1) +          _ad9510_regs.divider_low_cycles_out6 = low - 1; +          _ad9510_regs.divider_high_cycles_out6 = high - 1; +          break; +        } + +        //write the registers +        this->write_reg(clk_regs.div_hi(clk_regs.tx_db)); +        this->write_reg(clk_regs.div_lo(clk_regs.tx_db)); +        this->update_regs(); +    } + +    std::vector<double> get_rates_tx_dboard_clock(void){ +        return get_rates_rx_dboard_clock(); //same master clock, same dividers... +    } +     +    void enable_test_clock(bool enb) { +        _ad9510_regs.power_down_lvpecl_out0 = enb? +            ad9510_regs_t::POWER_DOWN_LVPECL_OUT0_NORMAL : +            ad9510_regs_t::POWER_DOWN_LVPECL_OUT0_SAFE_PD; +        _ad9510_regs.output_level_lvpecl_out0 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT0_810MV; +        _ad9510_regs.divider_low_cycles_out0 = 0; +        _ad9510_regs.divider_high_cycles_out0 = 0; +        _ad9510_regs.bypass_divider_out0 = 1; +        this->write_reg(0x3c); +        this->write_reg(0x48); +        this->write_reg(0x49); +    } + +    /*! +     * If we are to use an external reference, enable the charge pump. +     * \param enb true to enable the CP +     */ +    void enable_external_ref(bool enb){ +        _ad9510_regs.charge_pump_mode = (enb)? +            ad9510_regs_t::CHARGE_PUMP_MODE_NORMAL : +            ad9510_regs_t::CHARGE_PUMP_MODE_3STATE ; +        _ad9510_regs.pll_mux_control = ad9510_regs_t::PLL_MUX_CONTROL_DLD_HIGH; +        _ad9510_regs.pfd_polarity = ad9510_regs_t::PFD_POLARITY_POS; +        this->write_reg(clk_regs.pll_2); +        this->update_regs(); +    } + +    double get_master_clock_rate(void){ +        return 100e6; +    } + +private: +    /*! +     * Write a single register to the spi regs. +     * \param addr the address to write +     */ +    void write_reg(boost::uint8_t addr){ +        boost::uint32_t data = _ad9510_regs.get_write_reg(addr); +        _iface->transact_spi(SPI_SS_AD9510, spi_config_t::EDGE_RISE, data, 24, false /*no rb*/); +    } + +    /*! +     * Tells the ad9510 to latch the settings into the operational registers. +     */ +    void update_regs(void){ +        _ad9510_regs.update_registers = 1; +        this->write_reg(clk_regs.update); +    } + +    //uses output clock 3 (pecl) +    //this is the same between USRP2 and USRP2+ and doesn't get a switch statement +    void enable_dac_clock(bool enb){ +        _ad9510_regs.power_down_lvpecl_out3 = (enb)? +            ad9510_regs_t::POWER_DOWN_LVPECL_OUT3_NORMAL : +            ad9510_regs_t::POWER_DOWN_LVPECL_OUT3_SAFE_PD; +        _ad9510_regs.output_level_lvpecl_out3 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT3_810MV; +        _ad9510_regs.bypass_divider_out3 = 1; +        this->write_reg(clk_regs.output(clk_regs.dac)); +        this->write_reg(clk_regs.div_hi(clk_regs.dac)); +        this->update_regs(); +    } + +    //uses output clock 4 (lvds) on USRP2 and output clock 2 (lvpecl) on USRP2+ +    void enable_adc_clock(bool enb){ +        switch(clk_regs.adc) { +        case 2: +          _ad9510_regs.power_down_lvpecl_out2 = enb? ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_NORMAL : ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_SAFE_PD; +          _ad9510_regs.output_level_lvpecl_out2 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT2_500MV; +          _ad9510_regs.bypass_divider_out2 = 1; +          break; +        case 4: +          _ad9510_regs.power_down_lvds_cmos_out4 = enb? 0 : 1; +          _ad9510_regs.lvds_cmos_select_out4 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT4_LVDS; +          _ad9510_regs.output_level_lvds_out4 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT4_1_75MA; +          _ad9510_regs.bypass_divider_out4 = 1; +          break; +        } + +        this->write_reg(clk_regs.output(clk_regs.adc)); +        this->write_reg(clk_regs.div_hi(clk_regs.adc)); +        this->update_regs(); +    } +     +    usrp2_iface::sptr _iface; + +    usrp2_clk_regs_t clk_regs; +    ad9510_regs_t _ad9510_regs; +}; + +/*********************************************************************** + * Public make function for the ad9510 clock control + **********************************************************************/ +usrp2_clock_ctrl::sptr usrp2_clock_ctrl::make(usrp2_iface::sptr iface){ +    return sptr(new usrp2_clock_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp2/clock_ctrl.hpp b/host/lib/usrp/usrp2/clock_ctrl.hpp new file mode 100644 index 000000000..db6c52c83 --- /dev/null +++ b/host/lib/usrp/usrp2/clock_ctrl.hpp @@ -0,0 +1,99 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_CLOCK_CTRL_HPP +#define INCLUDED_CLOCK_CTRL_HPP + +#include "usrp2_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +class usrp2_clock_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp2_clock_ctrl> sptr; + +    /*! +     * Make a clock config for the ad9510 ic. +     * \param _iface a pointer to the usrp2 interface object +     * \return a new clock control object +     */ +    static sptr make(usrp2_iface::sptr iface); + +    /*! +     * Get the master clock frequency for the fpga. +     * \return the clock frequency in Hz +     */ +    virtual double get_master_clock_rate(void) = 0; + +    /*! +     * Enable/disable the rx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_rx_dboard_clock(bool enb) = 0; + +    /*! +     * Set the clock rate on the rx dboard clock. +     * \param rate the new clock rate +     * \throw exception when rate invalid +     */ +    virtual void set_rate_rx_dboard_clock(double rate) = 0; + +    /*! +     * Get a list of possible rx dboard clock rates. +     * \return a list of clock rates in Hz +     */ +    virtual std::vector<double> get_rates_rx_dboard_clock(void) = 0; + +    /*! +     * Enable/disable the tx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_tx_dboard_clock(bool enb) = 0; + +    /*! +     * Set the clock rate on the tx dboard clock. +     * \param rate the new clock rate +     * \throw exception when rate invalid +     */ +    virtual void set_rate_tx_dboard_clock(double rate) = 0; + +    /*! +     * Get a list of possible tx dboard clock rates. +     * \return a list of clock rates in Hz +     */ +    virtual std::vector<double> get_rates_tx_dboard_clock(void) = 0; + +    /*! +     * Enable/disable external reference. +     * \param enb true to enable +     */ +    virtual void enable_external_ref(bool enb) = 0; +     +    /*! +     * Enable/disable test clock output. +     * \param enb true to enable +     */ +    virtual void enable_test_clock(bool enb) = 0; + +    /*! +     * TODO other clock control api here.... +     */ + +}; + +#endif /* INCLUDED_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/codec_ctrl.cpp b/host/lib/usrp/usrp2/codec_ctrl.cpp new file mode 100644 index 000000000..ad1ae1acb --- /dev/null +++ b/host/lib/usrp/usrp2/codec_ctrl.cpp @@ -0,0 +1,169 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9777_regs.hpp" +#include "ads62p44_regs.hpp" +#include "usrp2_regs.hpp" +#include <boost/cstdint.hpp> +#include <boost/foreach.hpp> +#include <iostream> +#include <uhd/utils/exception.hpp> + +static const bool codec_ctrl_debug = false; + +using namespace uhd; + +/*! + * A usrp2 codec control specific to the ad9777 ic. + */ +class usrp2_codec_ctrl_impl : public usrp2_codec_ctrl{ +public: +    usrp2_codec_ctrl_impl(usrp2_iface::sptr iface){ +        _iface = iface; + +        //setup the ad9777 dac +        _ad9777_regs.x_1r_2r_mode = ad9777_regs_t::X_1R_2R_MODE_1R; +        _ad9777_regs.filter_interp_rate = ad9777_regs_t::FILTER_INTERP_RATE_4X; +        _ad9777_regs.mix_mode = ad9777_regs_t::MIX_MODE_REAL; +        _ad9777_regs.pll_divide_ratio = ad9777_regs_t::PLL_DIVIDE_RATIO_DIV1; +        _ad9777_regs.pll_state = ad9777_regs_t::PLL_STATE_ON; +        _ad9777_regs.auto_cp_control = ad9777_regs_t::AUTO_CP_CONTROL_AUTO; +        //I dac values +        _ad9777_regs.idac_fine_gain_adjust = 0; +        _ad9777_regs.idac_coarse_gain_adjust = 0xf; +        _ad9777_regs.idac_offset_adjust_lsb = 0; +        _ad9777_regs.idac_offset_adjust_msb = 0; +        //Q dac values +        _ad9777_regs.qdac_fine_gain_adjust = 0; +        _ad9777_regs.qdac_coarse_gain_adjust = 0xf; +        _ad9777_regs.qdac_offset_adjust_lsb = 0; +        _ad9777_regs.qdac_offset_adjust_msb = 0; +        //write all regs +        for(boost::uint8_t addr = 0; addr <= 0xC; addr++){ +            this->send_ad9777_reg(addr); +        } + +        //power-up adc +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP2_REV3: +        case usrp2_iface::USRP2_REV4: +            _iface->poke32(_iface->regs.misc_ctrl_adc, U2_FLAG_MISC_CTRL_ADC_ON); +            break; + +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +            _ads62p44_regs.reset = 1; +            this->send_ads62p44_reg(0x00); //issue a reset to the ADC +            //everything else should be pretty much default, i think +            //_ads62p44_regs.decimation = DECIMATION_DECIMATE_1; +            _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_NORMAL; +            this->send_ads62p44_reg(0x14); +            break; + +        case usrp2_iface::USRP_NXXX: break; +        } +    } + +    ~usrp2_codec_ctrl_impl(void){ +        //power-down dac +        _ad9777_regs.power_down_mode = 1; +        this->send_ad9777_reg(0); + +        //power-down adc +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP2_REV3: +        case usrp2_iface::USRP2_REV4: +            _iface->poke32(_iface->regs.misc_ctrl_adc, U2_FLAG_MISC_CTRL_ADC_OFF); +            break; + +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +            //send a global power-down to the ADC here... it will get lifted on reset +            _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_GLOBAL_PD; +            this->send_ads62p44_reg(0x14); +            break; + +        case usrp2_iface::USRP_NXXX: break; +        } +    } + +    void set_rx_digital_gain(float gain) {  //fine digital gain +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +            _ads62p44_regs.fine_gain = int(gain/0.5); +            this->send_ads62p44_reg(0x17); +            break; + +        default: UHD_THROW_INVALID_CODE_PATH(); +        } +    } + +    void set_rx_digital_fine_gain(float gain) { //gain correction       +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +            _ads62p44_regs.gain_correction = int(gain / 0.05); +            this->send_ads62p44_reg(0x1A); +            break; + +        default: UHD_THROW_INVALID_CODE_PATH(); +        } +    } + +    void set_rx_analog_gain(bool gain) { //turns on/off analog 3.5dB preamp +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +            _ads62p44_regs.coarse_gain = gain ? ads62p44_regs_t::COARSE_GAIN_3_5DB : ads62p44_regs_t::COARSE_GAIN_0DB; +            this->send_ads62p44_reg(0x14); +            break; + +        default: UHD_THROW_INVALID_CODE_PATH(); +        } +    } + +private: +    ad9777_regs_t _ad9777_regs; +    ads62p44_regs_t _ads62p44_regs; +    usrp2_iface::sptr _iface; + +    void send_ad9777_reg(boost::uint8_t addr){ +        boost::uint16_t reg = _ad9777_regs.get_write_reg(addr); +        if (codec_ctrl_debug) std::cout << "send_ad9777_reg: " << std::hex << reg << std::endl; +        _iface->transact_spi( +            SPI_SS_AD9777, spi_config_t::EDGE_RISE, +            reg, 16, false /*no rb*/ +        ); +    } + +    void send_ads62p44_reg(boost::uint8_t addr) { +        boost::uint16_t reg = _ads62p44_regs.get_write_reg(addr); +        _iface->transact_spi( +            SPI_SS_ADS62P44, spi_config_t::EDGE_FALL, +            reg, 16, false /*no rb*/ +        ); +    } +}; + +/*********************************************************************** + * Public make function for the usrp2 codec control + **********************************************************************/ +usrp2_codec_ctrl::sptr usrp2_codec_ctrl::make(usrp2_iface::sptr iface){ +    return sptr(new usrp2_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp2/codec_ctrl.hpp b/host/lib/usrp/usrp2/codec_ctrl.hpp new file mode 100644 index 000000000..57a37b94b --- /dev/null +++ b/host/lib/usrp/usrp2/codec_ctrl.hpp @@ -0,0 +1,59 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_CODEC_CTRL_HPP +#define INCLUDED_CODEC_CTRL_HPP + +#include "usrp2_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class usrp2_codec_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp2_codec_ctrl> sptr; + +    /*! +     * Make a codec control for the DAC and ADC. +     * \param _iface a pointer to the usrp2 interface object +     * \return a new codec control object +     */ +    static sptr make(usrp2_iface::sptr iface); + +    /*! +     * Set the analog preamplifier on the USRP2+ ADC (ADS62P44). +     * \param gain enable or disable the 3.5dB preamp +     */ + +    virtual void set_rx_analog_gain(bool gain) = 0; + +    /*! +     * Set the digital gain on the USRP2+ ADC (ADS62P44). +     * \param gain from 0-6dB +     */ + +    virtual void set_rx_digital_gain(float gain) = 0; + +    /*! +     * Set the digital gain correction on the USRP2+ ADC (ADS62P44). +     * \param gain from 0-0.5dB +     */ + +    virtual void set_rx_digital_fine_gain(float gain) = 0; + +}; + +#endif /* INCLUDED_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/codec_impl.cpp b/host/lib/usrp/usrp2/codec_impl.cpp new file mode 100644 index 000000000..e417bc340 --- /dev/null +++ b/host/lib/usrp/usrp2/codec_impl.cpp @@ -0,0 +1,173 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_impl.hpp" +#include <uhd/usrp/codec_props.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/bind.hpp> +#include <boost/assign/list_of.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/exception.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +//this only applies to USRP2P +static const uhd::dict<std::string, gain_range_t> codec_rx_gain_ranges = map_list_of +                                  ("analog", gain_range_t(0, float(3.5), float(3.5))) +                                  ("digital", gain_range_t(0, float(6.0), float(0.5))) +                                  ("digital-fine", gain_range_t(0, float(0.5), float(0.05))); + + +/*********************************************************************** + * Helper Methods + **********************************************************************/ +void usrp2_mboard_impl::codec_init(void){ +    //make proxies +    _rx_codec_proxy = wax_obj_proxy::make( +        boost::bind(&usrp2_mboard_impl::rx_codec_get, this, _1, _2), +        boost::bind(&usrp2_mboard_impl::rx_codec_set, this, _1, _2) +    ); +    _tx_codec_proxy = wax_obj_proxy::make( +        boost::bind(&usrp2_mboard_impl::tx_codec_get, this, _1, _2), +        boost::bind(&usrp2_mboard_impl::tx_codec_set, this, _1, _2) +    ); +} + +/*********************************************************************** + * RX Codec Properties + **********************************************************************/ +void usrp2_mboard_impl::rx_codec_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<codec_prop_t>()){ +    case CODEC_PROP_NAME: +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +            val = _iface->get_cname() + " adc - ads62p44"; +            break; + +        case usrp2_iface::USRP2_REV3: +        case usrp2_iface::USRP2_REV4: +            val = _iface->get_cname() + " adc - ltc2284"; +            break; + +        case usrp2_iface::USRP_NXXX: +            val = _iface->get_cname() + " adc - ??????"; +            break; +        } +        return; + +    case CODEC_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case CODEC_PROP_GAIN_NAMES: +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +            val = prop_names_t(codec_rx_gain_ranges.keys()); +            return; + +        default: val = prop_names_t(); +        } +        return; + +    case CODEC_PROP_GAIN_I: +    case CODEC_PROP_GAIN_Q: +        assert_has(_codec_rx_gains.keys(), key.name, "codec rx gain name"); +        val = _codec_rx_gains[key.name]; +        return; + +    case CODEC_PROP_GAIN_RANGE: +      assert_has(codec_rx_gain_ranges.keys(), key.name, "codec rx gain range name"); +      val = codec_rx_gain_ranges[key.name]; +      return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp2_mboard_impl::rx_codec_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<codec_prop_t>()) { +    case CODEC_PROP_GAIN_I: +    case CODEC_PROP_GAIN_Q: +        this->rx_codec_set_gain(val.as<float>(), key.name); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +  } +} + +/*********************************************************************** + * Helper function to set RX codec gain + ***********************************************************************/ + +void usrp2_mboard_impl::rx_codec_set_gain(float gain, const std::string &name){ +  assert_has(codec_rx_gain_ranges.keys(), name, "codec rx gain name"); + +  _codec_rx_gains[name] = gain; + +  if(name == "analog") { +    _codec_ctrl->set_rx_analog_gain(gain > 0); //just turn it on or off +    return; +  } +  if(name == "digital") { +    _codec_ctrl->set_rx_digital_gain(gain); +    return; +  } +  if(name == "digital-fine") { +    _codec_ctrl->set_rx_digital_fine_gain(gain); +    return; +  } +  UHD_THROW_PROP_SET_ERROR(); +} + + +/*********************************************************************** + * TX Codec Properties + **********************************************************************/ +void usrp2_mboard_impl::tx_codec_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<codec_prop_t>()){ +    case CODEC_PROP_NAME: +        val = _iface->get_cname() + " dac - ad9777"; +        return; + +    case CODEC_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case CODEC_PROP_GAIN_NAMES: +        val = prop_names_t(); //no gain elements to be controlled +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp2_mboard_impl::tx_codec_set(const wax::obj &, const wax::obj &){ +    UHD_THROW_PROP_SET_ERROR(); +} diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp new file mode 100644 index 000000000..54c1c735c --- /dev/null +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -0,0 +1,350 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_iface.hpp" +#include "clock_ctrl.hpp" +#include "usrp2_regs.hpp" //wishbone address constants +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio.hpp> //htonl and ntohl +#include <boost/math/special_functions/round.hpp> +#include "ad7922_regs.hpp" //aux adc +#include "ad5623_regs.hpp" //aux dac + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class usrp2_dboard_iface : public dboard_iface{ +public: +    usrp2_dboard_iface(usrp2_iface::sptr iface, usrp2_clock_ctrl::sptr clock_ctrl); +    ~usrp2_dboard_iface(void); + +    special_props_t get_special_props(void){ +        special_props_t props; +        props.soft_clock_divider = false; +        props.mangle_i2c_addrs = false; +        return props; +    } + +    void write_aux_dac(unit_t, aux_dac_t, float); +    float read_aux_adc(unit_t, aux_adc_t); + +    void _set_pin_ctrl(unit_t, boost::uint16_t); +    void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); +    void _set_gpio_ddr(unit_t, boost::uint16_t); +    void _set_gpio_out(unit_t, boost::uint16_t); +    void set_gpio_debug(unit_t, int); +    boost::uint16_t read_gpio(unit_t); + +    void write_i2c(boost::uint8_t, const byte_vector_t &); +    byte_vector_t read_i2c(boost::uint8_t, size_t); + +    void set_clock_rate(unit_t, double); +    double get_clock_rate(unit_t); +    std::vector<double> get_clock_rates(unit_t); +    void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t); + +    void write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    boost::uint32_t read_write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +private: +    usrp2_iface::sptr _iface; +    usrp2_clock_ctrl::sptr _clock_ctrl; +    boost::uint32_t _ddr_shadow; +    boost::uint32_t _gpio_shadow; + +    uhd::dict<unit_t, ad5623_regs_t> _dac_regs; +    uhd::dict<unit_t, double> _clock_rates; +    void _write_aux_dac(unit_t); +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_usrp2_dboard_iface( +    usrp2_iface::sptr iface, +    usrp2_clock_ctrl::sptr clock_ctrl +){ +    return dboard_iface::sptr(new usrp2_dboard_iface(iface, clock_ctrl)); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp2_dboard_iface::usrp2_dboard_iface( +    usrp2_iface::sptr iface, +    usrp2_clock_ctrl::sptr clock_ctrl +){ +    _iface = iface; +    _clock_ctrl = clock_ctrl; +    _ddr_shadow = 0; +    _gpio_shadow = 0; + +    //reset the aux dacs +    _dac_regs[UNIT_RX] = ad5623_regs_t(); +    _dac_regs[UNIT_TX] = ad5623_regs_t(); +    BOOST_FOREACH(unit_t unit, _dac_regs.keys()){ +        _dac_regs[unit].data = 1; +        _dac_regs[unit].addr = ad5623_regs_t::ADDR_ALL; +        _dac_regs[unit].cmd  = ad5623_regs_t::CMD_RESET; +        this->_write_aux_dac(unit); +    } + +    //init the clock rate shadows with max rate clock +    this->set_clock_rate(UNIT_RX, sorted(this->get_clock_rates(UNIT_RX)).back()); +    this->set_clock_rate(UNIT_TX, sorted(this->get_clock_rates(UNIT_TX)).back()); +} + +usrp2_dboard_iface::~usrp2_dboard_iface(void){ +    /* NOP */ +} + +/*********************************************************************** + * Clocks + **********************************************************************/ +void usrp2_dboard_iface::set_clock_rate(unit_t unit, double rate){ +    _clock_rates[unit] = rate; //set to shadow +    switch(unit){ +    case UNIT_RX: _clock_ctrl->set_rate_rx_dboard_clock(rate); return; +    case UNIT_TX: _clock_ctrl->set_rate_tx_dboard_clock(rate); return; +    } +} + +double usrp2_dboard_iface::get_clock_rate(unit_t unit){ +    return _clock_rates[unit]; //get from shadow +} + +std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _clock_ctrl->get_rates_rx_dboard_clock(); +    case UNIT_TX: return _clock_ctrl->get_rates_tx_dboard_clock(); +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ +    switch(unit){ +    case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; +    case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; +    } +} + +double usrp2_dboard_iface::get_codec_rate(unit_t){ +    return _clock_ctrl->get_master_clock_rate(); +} +/*********************************************************************** + * GPIO + **********************************************************************/ +static const uhd::dict<dboard_iface::unit_t, int> unit_to_shift = map_list_of +    (dboard_iface::UNIT_RX, 0) +    (dboard_iface::UNIT_TX, 16) +; + +void usrp2_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ +    //calculate the new selection mux setting +    boost::uint32_t new_sels = 0x0; +    for(size_t i = 0; i < 16; i++){ +        bool is_bit_set = (value & (0x1 << i)) != 0; +        new_sels |= ((is_bit_set)? U2_FLAG_GPIO_SEL_ATR : U2_FLAG_GPIO_SEL_GPIO) << (i*2); +    } + +    //write the selection mux value to register +    switch(unit){ +    case UNIT_RX: _iface->poke32(_iface->regs.gpio_rx_sel, new_sels); return; +    case UNIT_TX: _iface->poke32(_iface->regs.gpio_tx_sel, new_sels); return; +    } +} + +void usrp2_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ +    _ddr_shadow = \ +        (_ddr_shadow & ~(0xffff << unit_to_shift[unit])) | +        (boost::uint32_t(value) << unit_to_shift[unit]); +    _iface->poke32(_iface->regs.gpio_ddr, _ddr_shadow); +} + +void usrp2_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ +    _gpio_shadow = \ +        (_gpio_shadow & ~(0xffff << unit_to_shift[unit])) | +        (boost::uint32_t(value) << unit_to_shift[unit]); +    _iface->poke32(_iface->regs.gpio_io, _gpio_shadow); +} + +boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){ +    return boost::uint16_t(_iface->peek32(_iface->regs.gpio_io) >> unit_to_shift[unit]); +} + +void usrp2_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ +    //define mapping of unit to atr regs to register address +    static const uhd::dict< +        unit_t, uhd::dict<atr_reg_t, boost::uint32_t> +    > unit_to_atr_to_addr = map_list_of +        (UNIT_RX, map_list_of +            (ATR_REG_IDLE,        _iface->regs.atr_idle_rxside) +            (ATR_REG_TX_ONLY,     _iface->regs.atr_intx_rxside) +            (ATR_REG_RX_ONLY,     _iface->regs.atr_inrx_rxside) +            (ATR_REG_FULL_DUPLEX, _iface->regs.atr_full_rxside) +        ) +        (UNIT_TX, map_list_of +            (ATR_REG_IDLE,        _iface->regs.atr_idle_txside) +            (ATR_REG_TX_ONLY,     _iface->regs.atr_intx_txside) +            (ATR_REG_RX_ONLY,     _iface->regs.atr_inrx_txside) +            (ATR_REG_FULL_DUPLEX, _iface->regs.atr_full_txside) +        ) +    ; +    _iface->poke16(unit_to_atr_to_addr[unit][atr], value); +} + +void usrp2_dboard_iface::set_gpio_debug(unit_t unit, int which){ +    this->set_gpio_ddr(unit, 0xffff); //all outputs + +    //calculate the new selection mux setting +    boost::uint32_t new_sels = 0x0; +    int sel = (which == 0)? +        U2_FLAG_GPIO_SEL_DEBUG_0: +        U2_FLAG_GPIO_SEL_DEBUG_1; +    for(size_t i = 0; i < 16; i++){ +        new_sels |= sel << (i*2); +    } + +    //write the selection mux value to register +    switch(unit){ +    case UNIT_RX: _iface->poke32(_iface->regs.gpio_rx_sel, new_sels); return; +    case UNIT_TX: _iface->poke32(_iface->regs.gpio_tx_sel, new_sels); return; +    } +} + +/*********************************************************************** + * SPI + **********************************************************************/ +static const uhd::dict<dboard_iface::unit_t, int> unit_to_spi_dev = map_list_of +    (dboard_iface::UNIT_TX, SPI_SS_TX_DB) +    (dboard_iface::UNIT_RX, SPI_SS_RX_DB) +; + +void usrp2_dboard_iface::write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    _iface->transact_spi(unit_to_spi_dev[unit], config, data, num_bits, false /*no rb*/); +} + +boost::uint32_t usrp2_dboard_iface::read_write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    return _iface->transact_spi(unit_to_spi_dev[unit], config, data, num_bits, true /*rb*/); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp2_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +    return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp2_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ +    return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ +    static const uhd::dict<unit_t, int> unit_to_spi_dac = map_list_of +        (UNIT_RX, SPI_SS_RX_DAC) +        (UNIT_TX, SPI_SS_TX_DAC) +    ; +    _iface->transact_spi( +        unit_to_spi_dac[unit], spi_config_t::EDGE_FALL,  +        _dac_regs[unit].get_reg(), 24, false /*no rb*/ +    ); +} + +void usrp2_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, float value){ +    _dac_regs[unit].data = boost::math::iround(4095*value/3.3); +    _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; + +    typedef uhd::dict<aux_dac_t, ad5623_regs_t::addr_t> aux_dac_to_addr; +    static const uhd::dict<unit_t, aux_dac_to_addr> unit_to_which_to_addr = map_list_of +        (UNIT_RX, map_list_of +            (AUX_DAC_A, ad5623_regs_t::ADDR_DAC_B) +            (AUX_DAC_B, ad5623_regs_t::ADDR_DAC_A) +            (AUX_DAC_C, ad5623_regs_t::ADDR_DAC_A) +            (AUX_DAC_D, ad5623_regs_t::ADDR_DAC_B) +        ) +        (UNIT_TX, map_list_of +            (AUX_DAC_A, ad5623_regs_t::ADDR_DAC_A) +            (AUX_DAC_B, ad5623_regs_t::ADDR_DAC_B) +            (AUX_DAC_C, ad5623_regs_t::ADDR_DAC_B) +            (AUX_DAC_D, ad5623_regs_t::ADDR_DAC_A) +        ) +    ; +    _dac_regs[unit].addr = unit_to_which_to_addr[unit][which]; +    this->_write_aux_dac(unit); +} + +float usrp2_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which){ +    static const uhd::dict<unit_t, int> unit_to_spi_adc = map_list_of +        (UNIT_RX, SPI_SS_RX_ADC) +        (UNIT_TX, SPI_SS_TX_ADC) +    ; + +    //setup spi config args +    spi_config_t config; +    config.mosi_edge = spi_config_t::EDGE_FALL; +    config.miso_edge = spi_config_t::EDGE_RISE; + +    //setup the spi registers +    ad7922_regs_t ad7922_regs; +    switch(which){ +    case AUX_ADC_A: ad7922_regs.mod = 0; break; +    case AUX_ADC_B: ad7922_regs.mod = 1; break; +    } ad7922_regs.chn = ad7922_regs.mod; //normal mode: mod == chn + +    //write and read spi +    _iface->transact_spi( +        unit_to_spi_adc[unit], config, +        ad7922_regs.get_reg(), 16, false /*no rb*/ +    ); +    ad7922_regs.set_reg(boost::uint16_t(_iface->transact_spi( +        unit_to_spi_adc[unit], config, +        ad7922_regs.get_reg(), 16, true /*rb*/ +    ))); + +    //convert to voltage and return +    return float(3.3*ad7922_regs.result/4095); +} diff --git a/host/lib/usrp/usrp2/dboard_impl.cpp b/host/lib/usrp/usrp2/dboard_impl.cpp new file mode 100644 index 000000000..4192c4f78 --- /dev/null +++ b/host/lib/usrp/usrp2/dboard_impl.cpp @@ -0,0 +1,170 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_impl.hpp" +#include "usrp2_regs.hpp" +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/asio.hpp> //htonl and ntohl +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Helper Methods + **********************************************************************/ +void usrp2_mboard_impl::dboard_init(void){ +    //read the dboard eeprom to extract the dboard ids +    _rx_db_eeprom = dboard_eeprom_t(_iface->read_eeprom(USRP2_I2C_ADDR_RX_DB, 0, dboard_eeprom_t::num_bytes())); +    _tx_db_eeprom = dboard_eeprom_t(_iface->read_eeprom(USRP2_I2C_ADDR_TX_DB, 0, dboard_eeprom_t::num_bytes())); + +    //create a new dboard interface and manager +    _dboard_iface = make_usrp2_dboard_iface(_iface, _clock_ctrl); +    _dboard_manager = dboard_manager::make( +        _rx_db_eeprom.id, _tx_db_eeprom.id, _dboard_iface +    ); + +    //load dboards +    _rx_dboard_proxy = wax_obj_proxy::make( +        boost::bind(&usrp2_mboard_impl::rx_dboard_get, this, _1, _2), +        boost::bind(&usrp2_mboard_impl::rx_dboard_set, this, _1, _2) +    ); +    _tx_dboard_proxy = wax_obj_proxy::make( +        boost::bind(&usrp2_mboard_impl::tx_dboard_get, this, _1, _2), +        boost::bind(&usrp2_mboard_impl::tx_dboard_set, this, _1, _2) +    ); +} + +/*********************************************************************** + * RX DBoard Properties + **********************************************************************/ +void usrp2_mboard_impl::rx_dboard_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_NAME: +        val = _iface->get_cname() + " dboard (rx unit)"; +        return; + +    case DBOARD_PROP_SUBDEV: +        val = _dboard_manager->get_rx_subdev(key.name); +        return; + +    case DBOARD_PROP_SUBDEV_NAMES: +        val = _dboard_manager->get_rx_subdev_names(); +        return; + +    case DBOARD_PROP_DBOARD_ID: +        val = _rx_db_eeprom.id; +        return; + +    case DBOARD_PROP_DBOARD_IFACE: +        val = _dboard_iface; +        return; + +    case DBOARD_PROP_CODEC: +        val = _rx_codec_proxy->get_link(); +        return; + +    case DBOARD_PROP_GAIN_GROUP: +        val = make_gain_group( +            _rx_db_eeprom.id, +            _dboard_manager->get_rx_subdev(key.name), +            _rx_codec_proxy->get_link(), +            GAIN_GROUP_POLICY_RX +        ); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp2_mboard_impl::rx_dboard_set(const wax::obj &key, const wax::obj &val){ +    switch(key.as<dboard_prop_t>()){ + +    case DBOARD_PROP_DBOARD_ID: +        _rx_db_eeprom.id = val.as<dboard_id_t>(); +        _iface->write_eeprom(USRP2_I2C_ADDR_RX_DB, 0, _rx_db_eeprom.get_eeprom_bytes()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX DBoard Properties + **********************************************************************/ +void usrp2_mboard_impl::tx_dboard_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_NAME: +        val = _iface->get_cname() + " dboard (tx unit)"; +        return; + +    case DBOARD_PROP_SUBDEV: +        val = _dboard_manager->get_tx_subdev(key.name); +        return; + +    case DBOARD_PROP_SUBDEV_NAMES: +        val = _dboard_manager->get_tx_subdev_names(); +        return; + +    case DBOARD_PROP_DBOARD_ID: +        val = _tx_db_eeprom.id; +        return; + +    case DBOARD_PROP_DBOARD_IFACE: +        val = _dboard_iface; +        return; + +    case DBOARD_PROP_CODEC: +        val = _tx_codec_proxy->get_link(); +        return; + +    case DBOARD_PROP_GAIN_GROUP: +        val = make_gain_group( +            _tx_db_eeprom.id, +            _dboard_manager->get_tx_subdev(key.name), +            _tx_codec_proxy->get_link(), +            GAIN_GROUP_POLICY_TX +        ); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp2_mboard_impl::tx_dboard_set(const wax::obj &key, const wax::obj &val){ +    switch(key.as<dboard_prop_t>()){ + +    case DBOARD_PROP_DBOARD_ID: +        _tx_db_eeprom.id = val.as<dboard_id_t>(); +        _iface->write_eeprom(USRP2_I2C_ADDR_TX_DB, 0, _tx_db_eeprom.get_eeprom_bytes()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp2/dsp_impl.cpp b/host/lib/usrp/usrp2/dsp_impl.cpp new file mode 100644 index 000000000..77ed594f5 --- /dev/null +++ b/host/lib/usrp/usrp2/dsp_impl.cpp @@ -0,0 +1,202 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_impl.hpp" +#include "usrp2_regs.hpp" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <boost/bind.hpp> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; + +static const size_t default_decim = 16; +static const size_t default_interp = 16; + +/*********************************************************************** + * DDC Helper Methods + **********************************************************************/ +template <typename rate_type> +static rate_type pick_closest_rate(double exact_rate, const std::vector<rate_type> &rates){ +    unsigned closest_match = rates.front(); +    BOOST_FOREACH(rate_type possible_rate, rates){ +        if(std::abs(exact_rate - possible_rate) < std::abs(exact_rate - closest_match)) +            closest_match = possible_rate; +    } +    return closest_match; +} + +void usrp2_mboard_impl::init_ddc_config(void){ +    //create the ddc in the rx dsp dict +    _rx_dsp_proxy = wax_obj_proxy::make( +        boost::bind(&usrp2_mboard_impl::ddc_get, this, _1, _2), +        boost::bind(&usrp2_mboard_impl::ddc_set, this, _1, _2) +    ); + +    //initial config and update +    ddc_set(DSP_PROP_FREQ_SHIFT, double(0)); +    ddc_set(DSP_PROP_HOST_RATE, double(get_master_clock_freq()/default_decim)); +} + +/*********************************************************************** + * DDC Properties + **********************************************************************/ +void usrp2_mboard_impl::ddc_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ +    case DSP_PROP_NAME: +        val = _iface->get_cname() + " ddc0"; +        return; + +    case DSP_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case DSP_PROP_FREQ_SHIFT: +        val = _ddc_freq; +        return; + +    case DSP_PROP_FREQ_SHIFT_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case DSP_PROP_CODEC_RATE: +        val = get_master_clock_freq(); +        return; + +    case DSP_PROP_HOST_RATE: +        val = get_master_clock_freq()/_ddc_decim; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp2_mboard_impl::ddc_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ + +    case DSP_PROP_FREQ_SHIFT:{ +            double new_freq = val.as<double>(); +            _iface->poke32(_iface->regs.dsp_rx_freq, +                dsp_type1::calc_cordic_word_and_update(new_freq, get_master_clock_freq()) +            ); +            _ddc_freq = new_freq; //shadow +        } +        return; + +    case DSP_PROP_HOST_RATE:{ +            double extact_rate = get_master_clock_freq()/val.as<double>(); +            _ddc_decim = pick_closest_rate(extact_rate, _allowed_decim_and_interp_rates); + +            //set the decimation +            _iface->poke32(_iface->regs.dsp_rx_decim_rate, dsp_type1::calc_cic_filter_word(_ddc_decim)); + +            //set the scaling +            static const boost::int16_t default_rx_scale_iq = 1024; +            _iface->poke32(_iface->regs.dsp_rx_scale_iq, +                dsp_type1::calc_iq_scale_word(default_rx_scale_iq, default_rx_scale_iq) +            ); +        } +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * DUC Helper Methods + **********************************************************************/ +void usrp2_mboard_impl::init_duc_config(void){ +    //create the duc in the tx dsp dict +    _tx_dsp_proxy = wax_obj_proxy::make( +        boost::bind(&usrp2_mboard_impl::duc_get, this, _1, _2), +        boost::bind(&usrp2_mboard_impl::duc_set, this, _1, _2) +    ); + +    //initial config and update +    duc_set(DSP_PROP_FREQ_SHIFT, double(0)); +    duc_set(DSP_PROP_HOST_RATE, double(get_master_clock_freq()/default_interp)); +} + +/*********************************************************************** + * DUC Properties + **********************************************************************/ +void usrp2_mboard_impl::duc_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ +    case DSP_PROP_NAME: +        val = _iface->get_cname() + " duc0"; +        return; + +    case DSP_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case DSP_PROP_FREQ_SHIFT: +        val = _duc_freq; +        return; + +    case DSP_PROP_FREQ_SHIFT_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case DSP_PROP_CODEC_RATE: +        val = get_master_clock_freq(); +        return; + +    case DSP_PROP_HOST_RATE: +        val = get_master_clock_freq()/_duc_interp; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp2_mboard_impl::duc_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ + +    case DSP_PROP_FREQ_SHIFT:{ +            double new_freq = val.as<double>(); +            _iface->poke32(_iface->regs.dsp_tx_freq, +                dsp_type1::calc_cordic_word_and_update(new_freq, get_master_clock_freq()) +            ); +            _duc_freq = new_freq; //shadow +        } +        return; + +    case DSP_PROP_HOST_RATE:{ +            double extact_rate = get_master_clock_freq()/val.as<double>(); +            _duc_interp = pick_closest_rate(extact_rate, _allowed_decim_and_interp_rates); + +            //set the interpolation +            _iface->poke32(_iface->regs.dsp_tx_interp_rate, dsp_type1::calc_cic_filter_word(_duc_interp)); + +            //set the scaling +            _iface->poke32(_iface->regs.dsp_tx_scale_iq, dsp_type1::calc_iq_scale_word(_duc_interp)); +        } +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp2/fw_common.h b/host/lib/usrp/usrp2/fw_common.h new file mode 100644 index 000000000..a9c39e650 --- /dev/null +++ b/host/lib/usrp/usrp2/fw_common.h @@ -0,0 +1,145 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_FW_COMMON_H +#define INCLUDED_USRP2_FW_COMMON_H + +/*! + * Structs and constants for usrp2 communication. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +    #include <boost/cstdint.hpp> +    #define __stdint(type) boost::type +extern "C" { +#else +    #include <stdint.h> +    #define __stdint(type) type +#endif + +//fpga and firmware compatibility numbers +#define USRP2_FPGA_COMPAT_NUM 3 +#define USRP2_FW_COMPAT_NUM 7 + +//used to differentiate control packets over data port +#define USRP2_INVALID_VRT_HEADER 0 + +// udp ports for the usrp2 communication +// Dynamic and/or private ports: 49152-65535 +#define USRP2_UDP_CTRL_PORT 49152 +#define USRP2_UDP_DATA_PORT 49153 + +//////////////////////////////////////////////////////////////////////// +// I2C addresses +//////////////////////////////////////////////////////////////////////// +#define USRP2_I2C_DEV_EEPROM  0x50 // 24LC02[45]:  7-bits 1010xxx +#define	USRP2_I2C_ADDR_MBOARD (USRP2_I2C_DEV_EEPROM | 0x0) +#define	USRP2_I2C_ADDR_TX_DB  (USRP2_I2C_DEV_EEPROM | 0x4) +#define	USRP2_I2C_ADDR_RX_DB  (USRP2_I2C_DEV_EEPROM | 0x5) + +//////////////////////////////////////////////////////////////////////// +// EEPROM Layout +//////////////////////////////////////////////////////////////////////// +#define USRP2_EE_MBOARD_REV      0x00 //2 bytes, little-endian (historic, don't blame me) +#define USRP2_EE_MBOARD_MAC_ADDR 0x02 //6 bytes +#define USRP2_EE_MBOARD_IP_ADDR  0x0C //uint32, big-endian +#define USRP2_EE_MBOARD_BOOTLOADER_FLAGS 0xF7 + +typedef enum{ +    USRP2_CTRL_ID_HUH_WHAT = ' ', +    //USRP2_CTRL_ID_FOR_SURE, //TODO error condition enums +    //USRP2_CTRL_ID_SUX_MAN, + +    USRP2_CTRL_ID_WAZZUP_BRO = 'a', +    USRP2_CTRL_ID_WAZZUP_DUDE = 'A', + +    USRP2_CTRL_ID_TRANSACT_ME_SOME_SPI_BRO = 's', +    USRP2_CTRL_ID_OMG_TRANSACTED_SPI_DUDE = 'S', + +    USRP2_CTRL_ID_DO_AN_I2C_READ_FOR_ME_BRO = 'i', +    USRP2_CTRL_ID_HERES_THE_I2C_DATA_DUDE = 'I', + +    USRP2_CTRL_ID_WRITE_THESE_I2C_VALUES_BRO = 'h', +    USRP2_CTRL_ID_COOL_IM_DONE_I2C_WRITE_DUDE = 'H', + +    USRP2_CTRL_ID_POKE_THIS_REGISTER_FOR_ME_BRO = 'p', +    USRP2_CTRL_ID_OMG_POKED_REGISTER_SO_BAD_DUDE = 'P', + +    USRP2_CTRL_ID_PEEK_AT_THIS_REGISTER_FOR_ME_BRO = 'r', +    USRP2_CTRL_ID_WOAH_I_DEFINITELY_PEEKED_IT_DUDE = 'R', + +    USRP2_CTRL_ID_HEY_WRITE_THIS_UART_FOR_ME_BRO = 'u', +    USRP2_CTRL_ID_MAN_I_TOTALLY_WROTE_THAT_UART_DUDE = 'U', + +    USRP2_CTRL_ID_SO_LIKE_CAN_YOU_READ_THIS_UART_BRO = 'v', +    USRP2_CTRL_ID_I_HELLA_READ_THAT_UART_DUDE = 'V', + +    USRP2_CTRL_ID_PEACE_OUT = '~' + +} usrp2_ctrl_id_t; + +typedef enum{ +    USRP2_DIR_RX = 'r', +    USRP2_DIR_TX = 't' +} usrp2_dir_which_t; + +typedef enum{ +    USRP2_CLK_EDGE_RISE = 'r', +    USRP2_CLK_EDGE_FALL = 'f' +} usrp2_clk_edge_t; + +typedef struct{ +    __stdint(uint32_t) proto_ver; +    __stdint(uint32_t) id; +    __stdint(uint32_t) seq; +    union{ +        __stdint(uint32_t) ip_addr; +        struct { +            __stdint(uint32_t) dev; +            __stdint(uint32_t) data; +            __stdint(uint8_t) miso_edge; +            __stdint(uint8_t) mosi_edge; +            __stdint(uint8_t) num_bits; +            __stdint(uint8_t) readback; +        } spi_args; +        struct { +            __stdint(uint8_t) addr; +            __stdint(uint8_t) bytes; +            __stdint(uint8_t) data[20]; +        } i2c_args; +        struct { +            __stdint(uint32_t) addr; +            __stdint(uint32_t) data; +            __stdint(uint32_t) addrhi; +            __stdint(uint32_t) datahi; +            __stdint(uint8_t) num_bytes; //1, 2, 4, 8 +        } poke_args; +        struct { +            __stdint(uint8_t) dev; +            __stdint(uint8_t) bytes; +            __stdint(uint8_t) data[20]; +        } uart_args; +    } data; +} usrp2_ctrl_data_t; + +#undef __stdint +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_USRP2_FW_COMMON_H */ diff --git a/host/lib/usrp/usrp2/gps_ctrl.cpp b/host/lib/usrp/usrp2/gps_ctrl.cpp new file mode 100644 index 000000000..2273b2cd9 --- /dev/null +++ b/host/lib/usrp/usrp2/gps_ctrl.cpp @@ -0,0 +1,207 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "gps_ctrl.hpp" +#include <uhd/utils/assert.hpp> +#include <boost/cstdint.hpp> +#include <string> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread.hpp> +#include <boost/algorithm/string/trim.hpp> +#include <boost/tokenizer.hpp> + +using namespace uhd; +using namespace boost::gregorian; +using namespace boost::posix_time; +using namespace boost::algorithm; + +/*! + * A usrp2 GPS control for Jackson Labs devices + */ + +//TODO: multiple baud rate support (requires mboard_impl changes for poking UART registers) +class usrp2_gps_ctrl_impl : public usrp2_gps_ctrl{ +public: +  usrp2_gps_ctrl_impl(usrp2_iface::sptr iface){ +    _iface = iface; + +    std::string reply; +    bool i_heard_some_nmea = false, i_heard_something_weird = false; + +    gps_type = GPS_TYPE_NONE; + +//    set_uart_baud_rate(GPS_UART, 115200); +    //first we look for a Jackson Labs Firefly (since that's what we sell with the USRP2+...) + +    _iface->read_uart(GPS_UART); //get whatever junk is in the rx buffer right now, and throw it away +    _iface->write_uart(GPS_UART, "HAAAY GUYYYYS\n"); //to elicit a response from the Firefly + +    //then we loop until we either timeout, or until we get a response that indicates we're a JL device +    int timeout = GPS_TIMEOUT_TRIES; +    while(timeout--) { +      reply = safe_gps_read(); +      if(trim_right_copy(reply) == "Command Error") { +        gps_type = GPS_TYPE_JACKSON_LABS; +        break; +      } +      else if(reply.substr(0, 3) == "$GP") i_heard_some_nmea = true; //but keep looking for that "Command Error" response +      else if(reply.length() != 0) i_heard_something_weird = true; //probably wrong baud rate +    } + +    if((i_heard_some_nmea) && (gps_type != GPS_TYPE_JACKSON_LABS)) gps_type = GPS_TYPE_GENERIC_NMEA; + +    //otherwise, we can try some other common baud rates looking to see if a GPS is connected (todo, later) +    if((gps_type == GPS_TYPE_NONE) && i_heard_something_weird) { +      std::cout << "Invalid reply, possible incorrect baud rate" << std::endl; +    } + +    bool found_gprmc = false; + +    switch(gps_type) { +    case GPS_TYPE_JACKSON_LABS: +      std::cout << "Found a Jackson Labs GPS" << std::endl; +      //issue some setup stuff so it spits out the appropriate data +      //none of these should issue replies so we don't bother looking for them +      //we have to sleep between commands because the JL device, despite not acking, takes considerable time to process each command. +       boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); +      _iface->write_uart(GPS_UART, "SYST:COMM:SER:ECHO OFF\n"); +       boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); +      _iface->write_uart(GPS_UART, "SYST:COMM:SER:PRO OFF\n"); +       boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); +      _iface->write_uart(GPS_UART, "GPS:GPGGA 0\n"); +       boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); +      _iface->write_uart(GPS_UART, "GPS:GGAST 0\n"); +       boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); +      _iface->write_uart(GPS_UART, "GPS:GPRMC 1\n"); +       boost::this_thread::sleep(boost::posix_time::milliseconds(FIREFLY_STUPID_DELAY_MS)); + +//      break; + +    case GPS_TYPE_GENERIC_NMEA: +      if(gps_type == GPS_TYPE_GENERIC_NMEA) std::cout << "Found a generic NMEA GPS device" << std::endl; +      found_gprmc = false; +      //here we loop around looking for a GPRMC packet. if we don't get one, we don't have a usable GPS. +      timeout = GPS_TIMEOUT_TRIES; +      while(timeout--) { +        reply = safe_gps_read(); +        if(reply.substr(0, 6) == "$GPRMC") { +          found_gprmc = true; +          break; +        } +      } +      if(!found_gprmc) { +        if(gps_type == GPS_TYPE_JACKSON_LABS) std::cout << "Firefly GPS not locked or warming up." << std::endl; +        else std::cout << "GPS does not output GPRMC packets. Cannot retrieve time." << std::endl; +        gps_type = GPS_TYPE_NONE; +      } +      break; + +    case GPS_TYPE_NONE: +    default: +      break; + +    } + + +  } + +  ~usrp2_gps_ctrl_impl(void){ + +  } + +  std::string safe_gps_read() { +    std::string reply; +    try { +        reply = _iface->read_uart(GPS_UART); +  	    //std::cerr << "Got reply from GPS: " << reply.c_str() << " with length = " << reply.length() << std::endl; +    } catch (std::runtime_error err) { +      if(err.what() != std::string("usrp2 no control response")) throw; //sorry can't cope with that +      else { //we don't actually have a GPS installed +        reply = std::string(); +      } +    } +    return reply; +  } + +  ptime get_time(void) { +    std::string reply; +    ptime now; +    boost::tokenizer<boost::escaped_list_separator<char> > tok(reply); +    std::vector<std::string> toked; +    int timeout = GPS_TIMEOUT_TRIES; +    bool found_gprmc = false; +    switch(gps_type) { +    case GPS_TYPE_JACKSON_LABS: //deprecated in favor of a single NMEA parser +    case GPS_TYPE_GENERIC_NMEA: + +      while(timeout--) { +        reply = safe_gps_read(); +        if(reply.substr(0, 6) == "$GPRMC") { +          found_gprmc = true; +          break; +        } +      } +      UHD_ASSERT_THROW(found_gprmc); + +      tok.assign(reply); +      toked.assign(tok.begin(), tok.end()); + +      UHD_ASSERT_THROW(toked.size() == 11); //if it's not we got something weird in there + +      now = ptime( date(  +                         greg_year(boost::lexical_cast<int>(toked[8].substr(4, 2)) + 2000), //just trust me on this one +                         greg_month(boost::lexical_cast<int>(toked[8].substr(2, 2))),  +                         greg_day(boost::lexical_cast<int>(toked[8].substr(0, 2)))  +                       ), +                   hours(  boost::lexical_cast<int>(toked[1].substr(0, 2))) +                 + minutes(boost::lexical_cast<int>(toked[1].substr(2, 2))) +                 + seconds(boost::lexical_cast<int>(toked[1].substr(4, 2))) +                 ); +      break; +    case GPS_TYPE_NONE: +    default: +      throw std::runtime_error("get_time(): Unsupported GPS or no GPS detected\n"); +      break; +    } +    return now; +  } + +  bool gps_detected(void) { +    return (gps_type != GPS_TYPE_NONE); +  } + +private: +  usrp2_iface::sptr _iface; + +  enum { +    GPS_TYPE_JACKSON_LABS, +    GPS_TYPE_GENERIC_NMEA, +    GPS_TYPE_NONE +  } gps_type; + +  static const int GPS_UART = 2; //TODO: this should be plucked from fw_common.h or memory_map.h or somewhere in common with the firmware +  static const int GPS_TIMEOUT_TRIES = 5; +  static const int FIREFLY_STUPID_DELAY_MS = 200; + +}; + +/*********************************************************************** + * Public make function for the GPS control + **********************************************************************/ +usrp2_gps_ctrl::sptr usrp2_gps_ctrl::make(usrp2_iface::sptr iface){ +    return sptr(new usrp2_gps_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp2/gps_ctrl.hpp b/host/lib/usrp/usrp2/gps_ctrl.hpp new file mode 100644 index 000000000..5936a6fb6 --- /dev/null +++ b/host/lib/usrp/usrp2/gps_ctrl.hpp @@ -0,0 +1,53 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_GPS_CTRL_HPP +#define INCLUDED_GPS_CTRL_HPP + +#include "usrp2_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/date_time/posix_time/posix_time_types.hpp> + +using namespace boost::posix_time; + +class usrp2_gps_ctrl : boost::noncopyable{ +public: +  typedef boost::shared_ptr<usrp2_gps_ctrl> sptr; + +  /*! +   * Make a GPS config for Jackson Labs or generic NMEA GPS devices +   */ +  static sptr make(usrp2_iface::sptr iface); + +  /*! +   * Get the current GPS time and date +   * \return current GPS time and date as boost::posix_time::ptime object +   */ +  virtual ptime get_time(void) = 0; + +  /*! +   * Tell you if there's a supported GPS connected or not +   * \return true if a supported GPS is connected +   */ +  virtual bool gps_detected(void) = 0; + +  //TODO: other fun things you can do with a GPS. + +}; + +#endif /* INCLUDED_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/io_impl.cpp b/host/lib/usrp/usrp2/io_impl.cpp new file mode 100644 index 000000000..cbc0a0817 --- /dev/null +++ b/host/lib/usrp/usrp2/io_impl.cpp @@ -0,0 +1,363 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "../../transport/vrt_packet_handler.hpp" +#include "usrp2_impl.hpp" +#include "usrp2_regs.hpp" +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/convert_types.hpp> +#include <uhd/transport/alignment_buffer.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; + +/*********************************************************************** + * constants + **********************************************************************/ +static const int underflow_flags = 0 +    | async_metadata_t::EVENT_CODE_UNDERFLOW +    | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET +; + +static const size_t vrt_send_header_offset_words32 = 1; + +/*********************************************************************** + * flow control monitor for a single tx channel + *  - the pirate thread calls update + *  - the get send buffer calls check + **********************************************************************/ +class flow_control_monitor{ +public: +    typedef boost::uint32_t seq_type; +    typedef boost::shared_ptr<flow_control_monitor> sptr; + +    /*! +     * Make a new flow control monitor. +     * \param max_seqs_out num seqs before throttling +     */ +    flow_control_monitor(seq_type max_seqs_out){ +        _last_seq_out = 0; +        _last_seq_ack = 0; +        _max_seqs_out = max_seqs_out; +    } + +    /*! +     * Check the flow control condition. +     * \param seq the sequence to go out +     * \param timeout the timeout in seconds +     * \return false on timeout +     */ +    UHD_INLINE bool check_fc_condition(seq_type seq, double timeout){ +        boost::this_thread::disable_interruption di; //disable because the wait can throw +        boost::unique_lock<boost::mutex> lock(_fc_mutex); +        _last_seq_out = seq; +        return _fc_cond.timed_wait( +            lock, +            boost::posix_time::microseconds(long(timeout*1e6)), +            boost::bind(&flow_control_monitor::ready, this) +        ); +    } + +    /*! +     * Update the flow control condition. +     * \param seq the last sequence number to be ACK'd +     */ +    UHD_INLINE void update_fc_condition(seq_type seq){ +        boost::unique_lock<boost::mutex> lock(_fc_mutex); +        _last_seq_ack = seq; +        lock.unlock(); +        _fc_cond.notify_one(); +    } + +private: +    bool ready(void){ +        return seq_type(_last_seq_out -_last_seq_ack) < _max_seqs_out; +    } + +    boost::mutex _fc_mutex; +    boost::condition _fc_cond; +    seq_type _last_seq_out, _last_seq_ack, _max_seqs_out; +}; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew + * - alignment buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct usrp2_impl::io_impl{ +    typedef alignment_buffer<managed_recv_buffer::sptr, time_spec_t> alignment_buffer_type; + +    io_impl(size_t num_recv_frames, size_t send_frame_size, size_t width): +        packet_handler_recv_state(width), +        recv_pirate_booty(alignment_buffer_type::make(num_recv_frames-3, width)), +        async_msg_fifo(bounded_buffer<async_metadata_t>::make(100/*messages deep*/)) +    { +        for (size_t i = 0; i < width; i++) fc_mons.push_back( +            flow_control_monitor::sptr(new flow_control_monitor(usrp2_impl::sram_bytes/send_frame_size)) +        ); +    } + +    ~io_impl(void){ +        recv_pirate_crew_raiding = false; +        recv_pirate_crew.interrupt_all(); +        recv_pirate_crew.join_all(); +    } + +    bool get_recv_buffs(vrt_packet_handler::managed_recv_buffs_t &buffs, double timeout){ +        boost::this_thread::disable_interruption di; //disable because the wait can throw +        return recv_pirate_booty->pop_elems_with_timed_wait(buffs, timeout); +    } + +    bool get_send_buffs( +        const std::vector<zero_copy_if::sptr> &trans, +        vrt_packet_handler::managed_send_buffs_t &buffs, +        double timeout +    ){ +        UHD_ASSERT_THROW(trans.size() == buffs.size()); + +        //calculate the flow control word +        const boost::uint32_t fc_word32 = packet_handler_send_state.next_packet_seq; + +        //grab a managed buffer for each index +        for (size_t i = 0; i < buffs.size(); i++){ +            if (not fc_mons[i]->check_fc_condition(fc_word32, timeout)) return false; +            buffs[i] = trans[i]->get_send_buff(timeout); +            if (not buffs[i].get()) return false; +            buffs[i]->cast<boost::uint32_t *>()[0] = uhd::htonx(fc_word32); +        } +        return true; +    } + +    //flow control monitors +    std::vector<flow_control_monitor::sptr> fc_mons; + +    //state management for the vrt packet handler code +    vrt_packet_handler::recv_state packet_handler_recv_state; +    vrt_packet_handler::send_state packet_handler_send_state; + +    //methods and variables for the pirate crew +    void recv_pirate_loop(zero_copy_if::sptr, usrp2_mboard_impl::sptr, size_t); +    boost::thread_group recv_pirate_crew; +    bool recv_pirate_crew_raiding; +    alignment_buffer_type::sptr recv_pirate_booty; +    bounded_buffer<async_metadata_t>::sptr async_msg_fifo; +    boost::mutex spawn_mutex; +}; + +/*********************************************************************** + * Receive Pirate Loop + * - while raiding, loot for recv buffers + * - put booty into the alignment buffer + **********************************************************************/ +void usrp2_impl::io_impl::recv_pirate_loop( +    zero_copy_if::sptr zc_if, +    usrp2_mboard_impl::sptr mboard, +    size_t index +){ +    set_thread_priority_safe(); +    recv_pirate_crew_raiding = true; +    size_t next_packet_seq = 0; + +    spawn_mutex.unlock(); + +    while(recv_pirate_crew_raiding){ +        managed_recv_buffer::sptr buff = zc_if->get_recv_buff(); +        if (not buff.get()) continue; //ignore timeout/error buffers + +        try{ +            //extract the vrt header packet info +            vrt::if_packet_info_t if_packet_info; +            if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); +            const boost::uint32_t *vrt_hdr = buff->cast<const boost::uint32_t *>(); +            vrt::if_hdr_unpack_be(vrt_hdr, if_packet_info); + +            //handle the rx data stream +            if (if_packet_info.sid == usrp2_impl::RECV_SID){ +                //handle the packet count / sequence number +                if (if_packet_info.packet_count != next_packet_seq){ +                    //std::cerr << "S" << (if_packet_info.packet_count - next_packet_seq)%16; +                    std::cerr << "O" << std::flush; //report overflow (drops in the kernel) +                } +                next_packet_seq = (if_packet_info.packet_count+1)%16; + +                //extract the timespec and round to the nearest packet +                UHD_ASSERT_THROW(if_packet_info.has_tsi and if_packet_info.has_tsf); +                time_spec_t time( +                    time_t(if_packet_info.tsi), size_t(if_packet_info.tsf), mboard->get_master_clock_freq() +                ); + +                //push the packet into the buffer with the new time +                recv_pirate_booty->push_with_pop_on_full(buff, time, index); +                continue; +            } + +            //handle a tx async report message +            if (if_packet_info.sid == usrp2_impl::ASYNC_SID and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + +                //fill in the async metadata +                async_metadata_t metadata; +                metadata.channel = index; +                metadata.has_time_spec = if_packet_info.has_tsi and if_packet_info.has_tsf; +                metadata.time_spec = time_spec_t( +                    time_t(if_packet_info.tsi), size_t(if_packet_info.tsf), mboard->get_master_clock_freq() +                ); +                metadata.event_code = vrt_packet_handler::get_context_code<async_metadata_t::event_code_t>(vrt_hdr, if_packet_info); + +                //catch the flow control packets and react +                if (metadata.event_code == 0){ +                    boost::uint32_t fc_word32 = (vrt_hdr + if_packet_info.num_header_words32)[1]; +                    this->fc_mons[index]->update_fc_condition(uhd::ntohx(fc_word32)); +                    continue; +                } + +                //print the famous U, and push the metadata into the message queue +                if (metadata.event_code & underflow_flags) std::cerr << "U" << std::flush; +                //else std::cout << "metadata.event_code " << metadata.event_code << std::endl; +                async_msg_fifo->push_with_pop_on_full(metadata); +            } +            else{ +                //TODO unknown received packet, may want to print error... +            } +        }catch(const std::exception &e){ +            std::cerr << "Error (usrp2 recv pirate loop): " << e.what() << std::endl; +        } +    } +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void usrp2_impl::io_init(void){ + +    //the assumption is that all data transports should be identical +    const size_t num_recv_frames = _data_transports.front()->get_num_recv_frames(); +    const size_t send_frame_size = _data_transports.front()->get_send_frame_size(); + +    //create new io impl +    _io_impl = UHD_PIMPL_MAKE(io_impl, (num_recv_frames, send_frame_size, _data_transports.size())); + +    //TODO temporary fix for weird power up state, remove when FPGA fixed +    { +        //send an initial packet to all transports +        tx_metadata_t md; md.end_of_burst = true; +        this->send( +            std::vector<const void *>(_data_transports.size(), NULL), 0, md, +            io_type_t::COMPLEX_FLOAT32, device::SEND_MODE_ONE_PACKET, 0 +        ); +    } + +    //create a new pirate thread for each zc if (yarr!!) +    for (size_t i = 0; i < _data_transports.size(); i++){ +        //lock the unlocked mutex (non-blocking) +        _io_impl->spawn_mutex.lock(); +        //spawn a new pirate to plunder the recv booty +        _io_impl->recv_pirate_crew.create_thread(boost::bind( +            &usrp2_impl::io_impl::recv_pirate_loop, +            _io_impl.get(), _data_transports.at(i), +            _mboards.at(i), i +        )); +        //block here until the spawned thread unlocks +        _io_impl->spawn_mutex.lock(); +        //exit loop iteration in an unlocked condition +        _io_impl->spawn_mutex.unlock(); +    } +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool usrp2_impl::recv_async_msg( +    async_metadata_t &async_metadata, double timeout +){ +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return _io_impl->async_msg_fifo->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Send Data + **********************************************************************/ +size_t usrp2_impl::get_max_send_samps_per_packet(void) const{ +    static const size_t hdr_size = 0 +        + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +        + vrt_send_header_offset_words32*sizeof(boost::uint32_t) +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +    ; +    const size_t bpp = _data_transports.front()->get_send_frame_size() - hdr_size; +    return bpp/_tx_otw_type.get_sample_size(); +} + +size_t usrp2_impl::send( +    const std::vector<const void *> &buffs, size_t num_samps, +    const tx_metadata_t &metadata, const io_type_t &io_type, +    send_mode_t send_mode, double timeout +){ +    return vrt_packet_handler::send( +        _io_impl->packet_handler_send_state,       //last state of the send handler +        buffs, num_samps,                          //buffer to fill +        metadata, send_mode,                       //samples metadata +        io_type, _tx_otw_type,                     //input and output types to convert +        _mboards.front()->get_master_clock_freq(), //master clock tick rate +        uhd::transport::vrt::if_hdr_pack_be, +        boost::bind(&usrp2_impl::io_impl::get_send_buffs, _io_impl.get(), _data_transports, _1, timeout), +        get_max_send_samps_per_packet(), +        vrt_send_header_offset_words32 +    ); +} + +/*********************************************************************** + * Receive Data + **********************************************************************/ +size_t usrp2_impl::get_max_recv_samps_per_packet(void) const{ +    static const size_t hdr_size = 0 +        + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +        + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +    ; +    const size_t bpp = _data_transports.front()->get_recv_frame_size() - hdr_size; +    return bpp/_rx_otw_type.get_sample_size(); +} + +static void handle_overflow(std::vector<usrp2_mboard_impl::sptr> &mboards, size_t chan){ +    std::cerr << "O" << std::flush; +    mboards.at(chan/mboards.size())->handle_overflow(); +} + +size_t usrp2_impl::recv( +    const std::vector<void *> &buffs, size_t num_samps, +    rx_metadata_t &metadata, const io_type_t &io_type, +    recv_mode_t recv_mode, double timeout +){ +    return vrt_packet_handler::recv( +        _io_impl->packet_handler_recv_state,       //last state of the recv handler +        buffs, num_samps,                          //buffer to fill +        metadata, recv_mode,                       //samples metadata +        io_type, _rx_otw_type,                     //input and output types to convert +        _mboards.front()->get_master_clock_freq(), //master clock tick rate +        uhd::transport::vrt::if_hdr_unpack_be, +        boost::bind(&usrp2_impl::io_impl::get_recv_buffs, _io_impl.get(), _1, timeout), +        boost::bind(&handle_overflow, _mboards, _1) +    ); +} diff --git a/host/lib/usrp/usrp2/mboard_impl.cpp b/host/lib/usrp/usrp2/mboard_impl.cpp new file mode 100644 index 000000000..766ea993c --- /dev/null +++ b/host/lib/usrp/usrp2/mboard_impl.cpp @@ -0,0 +1,363 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_impl.hpp" +#include "usrp2_regs.hpp" +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/bind.hpp> +#include <iostream> +#include <boost/date_time/posix_time/posix_time.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::posix_time; + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp2_mboard_impl::usrp2_mboard_impl( +    size_t index, +    transport::udp_simple::sptr ctrl_transport, +    transport::zero_copy_if::sptr data_transport, +    size_t recv_samps_per_packet, +    const device_addr_t &flow_control_hints +): +    _index(index), +    _iface(usrp2_iface::make(ctrl_transport)) +{ +    //Send a small data packet so the usrp2 knows the udp source port. +    //This setup must happen before further initialization occurs +    //or the async update packets will cause ICMP destination unreachable. +    transport::managed_send_buffer::sptr send_buff = data_transport->get_send_buff(); +    static const boost::uint32_t data[2] = { +        uhd::htonx(boost::uint32_t(0 /* don't care seq num */)), +        uhd::htonx(boost::uint32_t(USRP2_INVALID_VRT_HEADER)) +    }; +    std::memcpy(send_buff->cast<void*>(), &data, sizeof(data)); +    send_buff->commit(sizeof(data)); + +    //contruct the interfaces to mboard perifs +    _clock_ctrl = usrp2_clock_ctrl::make(_iface); +    _codec_ctrl = usrp2_codec_ctrl::make(_iface); +    _serdes_ctrl = usrp2_serdes_ctrl::make(_iface); +    //_gps_ctrl = usrp2_gps_ctrl::make(_iface); + +    //if(_gps_ctrl->gps_detected()) std::cout << "GPS time: " << _gps_ctrl->get_time() << std::endl; + +    //TODO move to dsp impl... +    //load the allowed decim/interp rates +    //_USRP2_RATES = range(4, 128+1, 1) + range(130, 256+1, 2) + range(260, 512+1, 4) +    _allowed_decim_and_interp_rates.clear(); +    for (size_t i = 4; i <= 128; i+=1){ +        _allowed_decim_and_interp_rates.push_back(i); +    } +    for (size_t i = 130; i <= 256; i+=2){ +        _allowed_decim_and_interp_rates.push_back(i); +    } +    for (size_t i = 260; i <= 512; i+=4){ +        _allowed_decim_and_interp_rates.push_back(i); +    } + +    //setup the vrt rx registers +    _iface->poke32(_iface->regs.rx_ctrl_clear_overrun, 1); //reset +    _iface->poke32(_iface->regs.rx_ctrl_nsamps_per_pkt, recv_samps_per_packet); +    _iface->poke32(_iface->regs.rx_ctrl_nchannels, 1); +    _iface->poke32(_iface->regs.rx_ctrl_vrt_header, 0 +        | (0x1 << 28) //if data with stream id +        | (0x1 << 26) //has trailer +        | (0x3 << 22) //integer time other +        | (0x1 << 20) //fractional time sample count +    ); +    _iface->poke32(_iface->regs.rx_ctrl_vrt_stream_id, usrp2_impl::RECV_SID); +    _iface->poke32(_iface->regs.rx_ctrl_vrt_trailer, 0); +    _iface->poke32(_iface->regs.time64_tps, size_t(get_master_clock_freq())); + +    //init the tx control registers +    _iface->poke32(_iface->regs.tx_ctrl_clear_state, 1); //reset +    _iface->poke32(_iface->regs.tx_ctrl_num_chan, 0);    //1 channel +    _iface->poke32(_iface->regs.tx_ctrl_report_sid, usrp2_impl::ASYNC_SID); +    _iface->poke32(_iface->regs.tx_ctrl_policy, U2_FLAG_TX_CTRL_POLICY_NEXT_PACKET); + +    //setting the cycles per update (disabled by default) +    const double ups_per_sec = flow_control_hints.cast<double>("ups_per_sec", 0.0); +    if (ups_per_sec > 0.0){ +        const size_t cycles_per_up = size_t(_clock_ctrl->get_master_clock_rate()/ups_per_sec); +        _iface->poke32(_iface->regs.tx_ctrl_cycles_per_up, U2_FLAG_TX_CTRL_UP_ENB | cycles_per_up); +    } + +    //setting the packets per update (enabled by default) +    const double ups_per_fifo = flow_control_hints.cast<double>("ups_per_fifo", 8.0); +    if (ups_per_fifo > 0.0){ +        const size_t packets_per_up = size_t(usrp2_impl::sram_bytes/ups_per_fifo/data_transport->get_send_frame_size()); +        _iface->poke32(_iface->regs.tx_ctrl_packets_per_up, U2_FLAG_TX_CTRL_UP_ENB | packets_per_up); +    } + +    //init the ddc +    init_ddc_config(); + +    //init the duc +    init_duc_config(); + +    //initialize the clock configuration +    init_clock_config(); + +    //init the codec before the dboard +    codec_init(); + +    //init the tx and rx dboards (do last) +    dboard_init(); + +    //set default subdev specs +    (*this)[MBOARD_PROP_RX_SUBDEV_SPEC] = subdev_spec_t(); +    (*this)[MBOARD_PROP_TX_SUBDEV_SPEC] = subdev_spec_t(); +} + +usrp2_mboard_impl::~usrp2_mboard_impl(void){ +    _iface->poke32(_iface->regs.tx_ctrl_cycles_per_up, 0); +    _iface->poke32(_iface->regs.tx_ctrl_packets_per_up, 0); +} + +/*********************************************************************** + * Helper Methods + **********************************************************************/ +void usrp2_mboard_impl::init_clock_config(void){ +    //setup the clock configuration settings +    _clock_config.ref_source = clock_config_t::REF_INT; +    _clock_config.pps_source = clock_config_t::PPS_SMA; +    _clock_config.pps_polarity = clock_config_t::PPS_NEG; + +    //update the clock config (sends a control packet) +    update_clock_config(); +} + +void usrp2_mboard_impl::update_clock_config(void){ +    boost::uint32_t pps_flags = 0; + +    //translate pps source enums +    switch(_clock_config.pps_source){ +    case clock_config_t::PPS_SMA:  pps_flags |= U2_FLAG_TIME64_PPS_SMA;  break; +    case clock_config_t::PPS_MIMO: pps_flags |= U2_FLAG_TIME64_PPS_MIMO; break; +    default: throw std::runtime_error("unhandled clock configuration pps source"); +    } + +    //translate pps polarity enums +    switch(_clock_config.pps_polarity){ +    case clock_config_t::PPS_POS: pps_flags |= U2_FLAG_TIME64_PPS_POSEDGE; break; +    case clock_config_t::PPS_NEG: pps_flags |= U2_FLAG_TIME64_PPS_NEGEDGE; break; +    default: throw std::runtime_error("unhandled clock configuration pps polarity"); +    } + +    //set the pps flags +    _iface->poke32(_iface->regs.time64_flags, pps_flags); + +    //clock source ref 10mhz +    switch(_iface->get_rev()){ +    case usrp2_iface::USRP_N200: +    case usrp2_iface::USRP_N210: +        switch(_clock_config.ref_source){ +        case clock_config_t::REF_INT : _iface->poke32(_iface->regs.misc_ctrl_clock, 0x12); break; +        case clock_config_t::REF_SMA : _iface->poke32(_iface->regs.misc_ctrl_clock, 0x1C); break; +        case clock_config_t::REF_MIMO: _iface->poke32(_iface->regs.misc_ctrl_clock, 0x15); break; +        default: throw std::runtime_error("unhandled clock configuration reference source"); +        } +        _clock_ctrl->enable_external_ref(true); //USRP2P has an internal 10MHz TCXO +        break; + +    case usrp2_iface::USRP2_REV3: +    case usrp2_iface::USRP2_REV4: +        switch(_clock_config.ref_source){ +        case clock_config_t::REF_INT : _iface->poke32(_iface->regs.misc_ctrl_clock, 0x10); break; +        case clock_config_t::REF_SMA : _iface->poke32(_iface->regs.misc_ctrl_clock, 0x1C); break; +        case clock_config_t::REF_MIMO: _iface->poke32(_iface->regs.misc_ctrl_clock, 0x15); break; +        default: throw std::runtime_error("unhandled clock configuration reference source"); +        } +        _clock_ctrl->enable_external_ref(_clock_config.ref_source != clock_config_t::REF_INT); +        break; + +    case usrp2_iface::USRP_NXXX: break; +    } +} + +void usrp2_mboard_impl::set_time_spec(const time_spec_t &time_spec, bool now){ +    //set the ticks +    _iface->poke32(_iface->regs.time64_ticks, time_spec.get_tick_count(get_master_clock_freq())); + +    //set the flags register +    boost::uint32_t imm_flags = (now)? U2_FLAG_TIME64_LATCH_NOW : U2_FLAG_TIME64_LATCH_NEXT_PPS; +    _iface->poke32(_iface->regs.time64_imm, imm_flags); + +    //set the seconds (latches in all 3 registers) +    _iface->poke32(_iface->regs.time64_secs, boost::uint32_t(time_spec.get_full_secs())); +} + +void usrp2_mboard_impl::handle_overflow(void){ +    if (_continuous_streaming){ //re-issue the stream command if already continuous +        this->issue_ddc_stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); +    } +} + +void usrp2_mboard_impl::issue_ddc_stream_cmd(const stream_cmd_t &stream_cmd){ +    _continuous_streaming = stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS; +    _iface->poke32(_iface->regs.rx_ctrl_stream_cmd, dsp_type1::calc_stream_cmd_word(stream_cmd)); +    _iface->poke32(_iface->regs.rx_ctrl_time_secs,  boost::uint32_t(stream_cmd.time_spec.get_full_secs())); +    _iface->poke32(_iface->regs.rx_ctrl_time_ticks, stream_cmd.time_spec.get_tick_count(get_master_clock_freq())); +} + +/*********************************************************************** + * MBoard Get Properties + **********************************************************************/ +static const std::string dboard_name = "0"; + +void usrp2_mboard_impl::get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); +    //handle the get request conditioned on the key +    switch(key.as<mboard_prop_t>()){ +    case MBOARD_PROP_NAME: +        val = _iface->get_cname() + " mboard"; +        return; + +    case MBOARD_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case MBOARD_PROP_RX_DBOARD: +        UHD_ASSERT_THROW(key.name == dboard_name); +        val = _rx_dboard_proxy->get_link(); +        return; + +    case MBOARD_PROP_RX_DBOARD_NAMES: +        val = prop_names_t(1, dboard_name); +        return; + +    case MBOARD_PROP_TX_DBOARD: +        UHD_ASSERT_THROW(key.name == dboard_name); +        val = _tx_dboard_proxy->get_link(); +        return; + +    case MBOARD_PROP_TX_DBOARD_NAMES: +        val = prop_names_t(1, dboard_name); +        return; + +    case MBOARD_PROP_RX_DSP: +        UHD_ASSERT_THROW(key.name == ""); +        val = _rx_dsp_proxy->get_link(); +        return; + +    case MBOARD_PROP_RX_DSP_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case MBOARD_PROP_TX_DSP: +        UHD_ASSERT_THROW(key.name == ""); +        val = _tx_dsp_proxy->get_link(); +        return; + +    case MBOARD_PROP_TX_DSP_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case MBOARD_PROP_CLOCK_CONFIG: +        val = _clock_config; +        return; + +    case MBOARD_PROP_TIME_NOW:{ +            usrp2_iface::pair64 time64( +                _iface->peek64(_iface->regs.time64_secs_rb, _iface->regs.time64_ticks_rb) +            ); +            val = time_spec_t( +                time64.first, time64.second, get_master_clock_freq() +            ); +        } +        return; + +    case MBOARD_PROP_RX_SUBDEV_SPEC: +        val = _rx_subdev_spec; +        return; + +    case MBOARD_PROP_TX_SUBDEV_SPEC: +        val = _tx_subdev_spec; +        return; + +    case MBOARD_PROP_EEPROM_MAP: +        val = _iface->mb_eeprom; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * MBoard Set Properties + **********************************************************************/ +void usrp2_mboard_impl::set(const wax::obj &key, const wax::obj &val){ +    //handle the set request conditioned on the key +    switch(key.as<mboard_prop_t>()){ + +    case MBOARD_PROP_CLOCK_CONFIG: +        _clock_config = val.as<clock_config_t>(); +        update_clock_config(); +        return; + +    case MBOARD_PROP_TIME_NOW: +        set_time_spec(val.as<time_spec_t>(), true); +        return; + +    case MBOARD_PROP_TIME_NEXT_PPS: +        set_time_spec(val.as<time_spec_t>(), false); +        return; + +    case MBOARD_PROP_STREAM_CMD: +        issue_ddc_stream_cmd(val.as<stream_cmd_t>()); +        return; + +    case MBOARD_PROP_RX_SUBDEV_SPEC: +        _rx_subdev_spec = val.as<subdev_spec_t>(); +        verify_rx_subdev_spec(_rx_subdev_spec, this->get_link()); +        //sanity check +        UHD_ASSERT_THROW(_rx_subdev_spec.size() == 1); +        //set the mux +        _iface->poke32(_iface->regs.dsp_rx_mux, dsp_type1::calc_rx_mux_word( +            _dboard_manager->get_rx_subdev(_rx_subdev_spec.front().sd_name)[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>() +        )); +        return; + +    case MBOARD_PROP_TX_SUBDEV_SPEC: +        _tx_subdev_spec = val.as<subdev_spec_t>(); +        verify_tx_subdev_spec(_tx_subdev_spec, this->get_link()); +        //sanity check +        UHD_ASSERT_THROW(_tx_subdev_spec.size() == 1); +        //set the mux +        _iface->poke32(_iface->regs.dsp_tx_mux, dsp_type1::calc_tx_mux_word( +            _dboard_manager->get_tx_subdev(_tx_subdev_spec.front().sd_name)[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>() +        )); +        return; + +    case MBOARD_PROP_EEPROM_MAP: +        // Step1: commit the map, writing only those values set. +        // Step2: readback the entire eeprom map into the iface. +        val.as<mboard_eeprom_t>().commit(*_iface, mboard_eeprom_t::MAP_N100); +        _iface->mb_eeprom = mboard_eeprom_t(*_iface, mboard_eeprom_t::MAP_N100); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp2/serdes_ctrl.cpp b/host/lib/usrp/usrp2/serdes_ctrl.cpp new file mode 100644 index 000000000..1cda22f45 --- /dev/null +++ b/host/lib/usrp/usrp2/serdes_ctrl.cpp @@ -0,0 +1,46 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "serdes_ctrl.hpp" +#include "usrp2_regs.hpp" + +using namespace uhd; + +/*! + * A usrp2 serdes control implementation + */ +class usrp2_serdes_ctrl_impl : public usrp2_serdes_ctrl{ +public: +    usrp2_serdes_ctrl_impl(usrp2_iface::sptr iface){ +        _iface = iface; +        _iface->poke32(_iface->regs.misc_ctrl_serdes, U2_FLAG_MISC_CTRL_SERDES_ENABLE | U2_FLAG_MISC_CTRL_SERDES_RXEN); +    } + +    ~usrp2_serdes_ctrl_impl(void){ +        _iface->poke32(_iface->regs.misc_ctrl_serdes, 0); //power-down +    } + +private: +    usrp2_iface::sptr _iface; +}; + +/*********************************************************************** + * Public make function for the usrp2 serdes control + **********************************************************************/ +usrp2_serdes_ctrl::sptr usrp2_serdes_ctrl::make(usrp2_iface::sptr iface){ +    return sptr(new usrp2_serdes_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp2/serdes_ctrl.hpp b/host/lib/usrp/usrp2/serdes_ctrl.hpp new file mode 100644 index 000000000..3c909c531 --- /dev/null +++ b/host/lib/usrp/usrp2/serdes_ctrl.hpp @@ -0,0 +1,40 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_SERDES_CTRL_HPP +#define INCLUDED_SERDES_CTRL_HPP + +#include "usrp2_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class usrp2_serdes_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp2_serdes_ctrl> sptr; + +    /*! +     * Make a serdes control object for the usrp2 serdes port. +     * \param _iface a pointer to the usrp2 interface object +     * \return a new serdes control object +     */ +    static sptr make(usrp2_iface::sptr iface); + +    //TODO fill me in with virtual methods + +}; + +#endif /* INCLUDED_SERDES_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_clk_regs.hpp b/host/lib/usrp/usrp2/usrp2_clk_regs.hpp new file mode 100644 index 000000000..6c46d0a35 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_clk_regs.hpp @@ -0,0 +1,85 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_CLK_REGS_HPP +#define INCLUDED_USRP2_CLK_REGS_HPP + +#include "usrp2_iface.hpp" + +class usrp2_clk_regs_t { +public: +  usrp2_clk_regs_t(void) { ; } +  usrp2_clk_regs_t(usrp2_iface::rev_type rev) { +    test = 0; +    fpga = 1; +    dac = 3; + +    switch(rev) { +    case usrp2_iface::USRP2_REV3: +        exp = 2; +        adc = 4; +        serdes = 2; +        tx_db = 6; +        break; +    case usrp2_iface::USRP2_REV4: +        exp = 5; +        adc = 4; +        serdes = 2; +        tx_db = 6; +        break; +    case usrp2_iface::USRP_N200: +    case usrp2_iface::USRP_N210: +        exp = 6; +        adc = 2; +        serdes = 4; +        tx_db = 5; +        break; +    case usrp2_iface::USRP_NXXX: +        //dont throw, it may be unitialized +        break; +    } +     +    rx_db = 7; +  } + +  static int output(int clknum) { return 0x3C + clknum; } +  static int div_lo(int clknum) { return 0x48 + 2 * clknum; } +  static int div_hi(int clknum) { return 0x49 + 2 * clknum; } + +  const static int acounter = 0x04; +  const static int bcounter_msb = 0x05; +  const static int bcounter_lsb = 0x06; +  const static int pll_1 = 0x07; +  const static int pll_2 = 0x08; +  const static int pll_3 = 0x09; +  const static int pll_4 = 0x0A; +  const static int ref_counter_msb = 0x0B; +  const static int ref_counter_lsb = 0x0C; +  const static int pll_5 = 0x0D; +  const static int update = 0x5A; + +  int test; +  int fpga; +  int adc; +  int dac; +  int serdes; +  int exp; +  int tx_db; +  int rx_db; +}; + +#endif //INCLUDED_USRP2_CLK_REGS_HPP diff --git a/host/lib/usrp/usrp2/usrp2_iface.cpp b/host/lib/usrp/usrp2/usrp2_iface.cpp new file mode 100644 index 000000000..ffbe8eedb --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.cpp @@ -0,0 +1,334 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_regs.hpp" +#include "usrp2_iface.hpp" +#include <uhd/utils/exception.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/types/dict.hpp> +#include <boost/thread.hpp> +#include <boost/foreach.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/tokenizer.hpp> +#include <stdexcept> +#include <algorithm> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const double CTRL_RECV_TIMEOUT = 1.0; + +class usrp2_iface_impl : public usrp2_iface{ +public: +/*********************************************************************** + * Structors + **********************************************************************/ +    usrp2_iface_impl(udp_simple::sptr ctrl_transport){ +        _ctrl_transport = ctrl_transport; + +        mb_eeprom = mboard_eeprom_t(*this, mboard_eeprom_t::MAP_N100); +        switch(this->get_rev()){ +        case USRP2_REV3: +        case USRP2_REV4: +            regs = usrp2_get_regs(false); +            break; + +        case USRP_N200: +        case USRP_N210: +            regs = usrp2_get_regs(true); +            break; + +        case USRP_NXXX: //fallthough case is old register map (USRP2) +            regs = usrp2_get_regs(false); +            break; +        } + +        //check the fpga compatibility number +        const boost::uint32_t fpga_compat_num = this->peek32(this->regs.compat_num_rb); +        if (fpga_compat_num != USRP2_FPGA_COMPAT_NUM){ +            throw std::runtime_error(str(boost::format( +                "Expected fpga compatibility number %d, but got %d:\n" +                "The fpga build is not compatible with the host code build." +            ) % int(USRP2_FPGA_COMPAT_NUM) % fpga_compat_num)); +        } +    } + +    ~usrp2_iface_impl(void){ +        /* NOP */ +    } + +/*********************************************************************** + * Peek and Poke + **********************************************************************/ +    void poke32(boost::uint32_t addr, boost::uint32_t data){ +        return this->poke<boost::uint32_t>(addr, data); +    } + +    boost::uint32_t peek32(boost::uint32_t addr){ +        return this->peek<boost::uint32_t>(addr); +    } + +    void poke16(boost::uint32_t addr, boost::uint16_t data){ +        return this->poke<boost::uint16_t>(addr, data); +    } + +    boost::uint16_t peek16(boost::uint32_t addr){ +        return this->peek<boost::uint16_t>(addr); +    } + +    pair64 peek64(boost::uint32_t addrlo, boost::uint32_t addrhi){ +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_PEEK_AT_THIS_REGISTER_FOR_ME_BRO); +        out_data.data.poke_args.addr = htonl(addrlo); +        out_data.data.poke_args.addrhi = htonl(addrhi); +        out_data.data.poke_args.num_bytes = sizeof(boost::uint64_t); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_WOAH_I_DEFINITELY_PEEKED_IT_DUDE); +        return pair64(ntohl(in_data.data.poke_args.data), ntohl(in_data.data.poke_args.datahi)); +    } + +/*********************************************************************** + * SPI + **********************************************************************/ +    boost::uint32_t transact_spi( +        int which_slave, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits, +        bool readback +    ){ +        static const uhd::dict<spi_config_t::edge_t, int> spi_edge_to_otw = boost::assign::map_list_of +            (spi_config_t::EDGE_RISE, USRP2_CLK_EDGE_RISE) +            (spi_config_t::EDGE_FALL, USRP2_CLK_EDGE_FALL) +        ; + +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_TRANSACT_ME_SOME_SPI_BRO); +        out_data.data.spi_args.dev = htonl(which_slave); +        out_data.data.spi_args.miso_edge = spi_edge_to_otw[config.miso_edge]; +        out_data.data.spi_args.mosi_edge = spi_edge_to_otw[config.mosi_edge]; +        out_data.data.spi_args.readback = (readback)? 1 : 0; +        out_data.data.spi_args.num_bits = num_bits; +        out_data.data.spi_args.data = htonl(data); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_OMG_TRANSACTED_SPI_DUDE); + +        return ntohl(in_data.data.spi_args.data); +    } + +/*********************************************************************** + * I2C + **********************************************************************/ +    void write_i2c(boost::uint8_t addr, const byte_vector_t &buf){ +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_WRITE_THESE_I2C_VALUES_BRO); +        out_data.data.i2c_args.addr = addr; +        out_data.data.i2c_args.bytes = buf.size(); + +        //limitation of i2c transaction size +        UHD_ASSERT_THROW(buf.size() <= sizeof(out_data.data.i2c_args.data)); + +        //copy in the data +        std::copy(buf.begin(), buf.end(), out_data.data.i2c_args.data); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_COOL_IM_DONE_I2C_WRITE_DUDE); +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_DO_AN_I2C_READ_FOR_ME_BRO); +        out_data.data.i2c_args.addr = addr; +        out_data.data.i2c_args.bytes = num_bytes; + +        //limitation of i2c transaction size +        UHD_ASSERT_THROW(num_bytes <= sizeof(out_data.data.i2c_args.data)); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_HERES_THE_I2C_DATA_DUDE); +        UHD_ASSERT_THROW(in_data.data.i2c_args.addr = num_bytes); + +        //copy out the data +        byte_vector_t result(num_bytes); +        std::copy(in_data.data.i2c_args.data, in_data.data.i2c_args.data + num_bytes, result.begin()); +        return result; +    } + +/*********************************************************************** + * UART + **********************************************************************/ +    void write_uart(boost::uint8_t dev, const std::string &buf){ +      //first tokenize the string into 20-byte substrings +      boost::offset_separator f(20, 1, true, true); +      boost::tokenizer<boost::offset_separator> tok(buf, f); +      std::vector<std::string> queue(tok.begin(), tok.end()); + +      BOOST_FOREACH(std::string item, queue) { +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_HEY_WRITE_THIS_UART_FOR_ME_BRO); +        out_data.data.uart_args.dev = dev; +        out_data.data.uart_args.bytes = item.size(); + +        //limitation of uart transaction size +        UHD_ASSERT_THROW(item.size() <= sizeof(out_data.data.uart_args.data)); + +        //copy in the data +        std::copy(item.begin(), item.end(), out_data.data.uart_args.data); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_MAN_I_TOTALLY_WROTE_THAT_UART_DUDE); +      } +    } + +    std::string read_uart(boost::uint8_t dev){ +    	int readlen = 20; +      std::string result; +    	while(readlen == 20) { //while we keep receiving full packets +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_SO_LIKE_CAN_YOU_READ_THIS_UART_BRO); +        out_data.data.uart_args.dev = dev; +        out_data.data.uart_args.bytes = 20; + +        //limitation of uart transaction size +        //UHD_ASSERT_THROW(num_bytes <= sizeof(out_data.data.uart_args.data)); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_I_HELLA_READ_THAT_UART_DUDE); +        readlen = in_data.data.uart_args.bytes; + +        //copy out the data +        result += std::string((const char *)in_data.data.uart_args.data, (size_t)readlen); +      } +      return result; +    } + +/*********************************************************************** + * Send/Recv over control + **********************************************************************/ +    usrp2_ctrl_data_t ctrl_send_and_recv(const usrp2_ctrl_data_t &out_data){ +        boost::mutex::scoped_lock lock(_ctrl_mutex); + +        //fill in the seq number and send +        usrp2_ctrl_data_t out_copy = out_data; +        out_copy.proto_ver = htonl(USRP2_FW_COMPAT_NUM); +        out_copy.seq = htonl(++_ctrl_seq_num); +        _ctrl_transport->send(boost::asio::buffer(&out_copy, sizeof(usrp2_ctrl_data_t))); + +        //loop until we get the packet or timeout +        boost::uint8_t usrp2_ctrl_data_in_mem[udp_simple::mtu]; //allocate max bytes for recv +        const usrp2_ctrl_data_t *ctrl_data_in = reinterpret_cast<const usrp2_ctrl_data_t *>(usrp2_ctrl_data_in_mem); +        while(true){ +            size_t len = _ctrl_transport->recv(boost::asio::buffer(usrp2_ctrl_data_in_mem), CTRL_RECV_TIMEOUT); +            if(len >= sizeof(boost::uint32_t) and ntohl(ctrl_data_in->proto_ver) != USRP2_FW_COMPAT_NUM){ +                throw std::runtime_error(str(boost::format( +                    "Expected protocol compatibility number %d, but got %d:\n" +                    "The firmware build is not compatible with the host code build." +                ) % int(USRP2_FW_COMPAT_NUM) % ntohl(ctrl_data_in->proto_ver))); +            } +            if (len >= sizeof(usrp2_ctrl_data_t) and ntohl(ctrl_data_in->seq) == _ctrl_seq_num){ +                return *ctrl_data_in; +            } +            if (len == 0) break; //timeout +            //didnt get seq or bad packet, continue looking... +        } +        throw std::runtime_error("no control response"); +    } + +    rev_type get_rev(void){ +        switch (boost::lexical_cast<boost::uint16_t>(mb_eeprom["rev"])){ +        case 0x0300: +        case 0x0301: return USRP2_REV3; +        case 0x0400: return USRP2_REV4; +        case 0x0A00: return USRP_N200; +        case 0x0A01: return USRP_N210; +        } +        return USRP_NXXX; //unknown type +    } + +    const std::string get_cname(void){ +        switch(this->get_rev()){ +        case USRP2_REV3: return "USRP2-REV3"; +        case USRP2_REV4: return "USRP2-REV4"; +        case USRP_N200: return "USRP-N200"; +        case USRP_N210: return "USRP-N210"; +        case USRP_NXXX: return "USRP-N???"; +        } +        UHD_THROW_INVALID_CODE_PATH(); +    } + +private: +    //this lovely lady makes it all possible +    udp_simple::sptr _ctrl_transport; + +    //used in send/recv +    boost::mutex _ctrl_mutex; +    boost::uint32_t _ctrl_seq_num; + +/*********************************************************************** + * Private Templated Peek and Poke + **********************************************************************/ +    template <class T> void poke(boost::uint32_t addr, T data){ +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_POKE_THIS_REGISTER_FOR_ME_BRO); +        out_data.data.poke_args.addr = htonl(addr); +        out_data.data.poke_args.data = htonl(boost::uint32_t(data)); +        out_data.data.poke_args.num_bytes = sizeof(T); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_OMG_POKED_REGISTER_SO_BAD_DUDE); +    } + +    template <class T> T peek(boost::uint32_t addr){ +        //setup the out data +        usrp2_ctrl_data_t out_data; +        out_data.id = htonl(USRP2_CTRL_ID_PEEK_AT_THIS_REGISTER_FOR_ME_BRO); +        out_data.data.poke_args.addr = htonl(addr); +        out_data.data.poke_args.num_bytes = sizeof(T); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_WOAH_I_DEFINITELY_PEEKED_IT_DUDE); +        return T(ntohl(in_data.data.poke_args.data)); +    } +}; + +/*********************************************************************** + * Public make function for usrp2 interface + **********************************************************************/ +usrp2_iface::sptr usrp2_iface::make(udp_simple::sptr ctrl_transport){ +    return usrp2_iface::sptr(new usrp2_iface_impl(ctrl_transport)); +} + diff --git a/host/lib/usrp/usrp2/usrp2_iface.hpp b/host/lib/usrp/usrp2/usrp2_iface.hpp new file mode 100644 index 000000000..af3ed6c9f --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.hpp @@ -0,0 +1,137 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_IFACE_HPP +#define INCLUDED_USRP2_IFACE_HPP + +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/cstdint.hpp> +#include <utility> +#include <string> +#include "fw_common.h" +#include "usrp2_regs.hpp" + +/*! + * The usrp2 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp2_iface : public uhd::i2c_iface, boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp2_iface> sptr; +    typedef std::pair<boost::uint32_t, boost::uint32_t> pair64; + +    /*! +     * Make a new usrp2 interface with the control transport. +     * \param ctrl_transport the udp transport object +     * \return a new usrp2 interface object +     */ +    static sptr make(uhd::transport::udp_simple::sptr ctrl_transport); + +    /*! +     * Perform a control transaction. +     * \param data a control data struct +     * \return the result control data +     */ +    virtual usrp2_ctrl_data_t ctrl_send_and_recv(const usrp2_ctrl_data_t &data) = 0; + +    /*! +     * Read a dual register (64 bits) +     * \param addrlo the address for the low-32 bits +     * \param addrhi the address for the high-32 bits +     * \return a pair of 32 bit integers lo, hi +     */ +    virtual pair64 peek64(boost::uint32_t addrlo, boost::uint32_t addrhi) = 0; + +    /*! +     * Write a register (32 bits) +     * \param addr the address +     * \param data the 32bit data +     */ +    virtual void poke32(boost::uint32_t addr, boost::uint32_t data) = 0; + +    /*! +     * Read a register (32 bits) +     * \param addr the address +     * \return the 32bit data +     */ +    virtual boost::uint32_t peek32(boost::uint32_t addr) = 0; + +    /*! +     * Write a register (16 bits) +     * \param addr the address +     * \param data the 16bit data +     */ +    virtual void poke16(boost::uint32_t addr, boost::uint16_t data) = 0; + +    /*! +     * Read a register (16 bits) +     * \param addr the address +     * \return the 16bit data +     */ +    virtual boost::uint16_t peek16(boost::uint32_t addr) = 0; + +    /*! +     * Perform an spi transaction. +     * \param which_slave the slave device number +     * \param config spi config args +     * \param data the bits to write +     * \param num_bits how many bits in data +     * \param readback true to readback a value +     * \return spi data if readback set +     */ +    virtual boost::uint32_t transact_spi( +        int which_slave, +        const uhd::spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits, +        bool readback +    ) = 0; + +    virtual void write_uart(boost::uint8_t dev, const std::string &buf) = 0; + +    virtual std::string read_uart(boost::uint8_t dev) = 0; + +    //! The list of possible revision types +    enum rev_type { +        USRP2_REV3 = 3, +        USRP2_REV4 = 4, +        USRP_N200 = 200, +        USRP_N210 = 210, +        USRP_NXXX = 0 +    }; + +    //! Get the revision type for this device +    virtual rev_type get_rev(void) = 0; + +    //! Get the canonical name for this device +    virtual const std::string get_cname(void) = 0; + +    /*! +     * Register map selected from USRP2/USRP2+. +     */ +    usrp2_regs_t regs; + +    //motherboard eeprom map structure +    uhd::usrp::mboard_eeprom_t mb_eeprom; +}; + +#endif /* INCLUDED_USRP2_IFACE_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp new file mode 100644 index 000000000..c3bbe4d65 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -0,0 +1,294 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_impl.hpp" +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/usrp/device_props.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/regex.hpp> +#include <boost/bind.hpp> +#include <boost/asio.hpp> //htonl and ntohl +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +template <class T> std::string num2str(T num){ +    return boost::lexical_cast<std::string>(num); +} + +//! separate indexed device addresses into a vector of device addresses +device_addrs_t sep_indexed_dev_addrs(const device_addr_t &dev_addr){ +    //------------ support old deprecated way and print warning -------- +    if (dev_addr.has_key("addr")){ +        std::vector<std::string> addrs = std::split_string(dev_addr["addr"]); +        if (addrs.size() > 1){ +            device_addr_t fixed_dev_addr = dev_addr; +            fixed_dev_addr.pop("addr"); +            for (size_t i = 0; i < addrs.size(); i++){ +                fixed_dev_addr[str(boost::format("addr%d") % i)] = addrs[i]; +            } +            uhd::warning::post( +                "addr = <space separated list of ip addresses> is deprecated.\n" +                "To address a multi-device, use multiple <key><index> = <val>.\n" +                "See the USRP-NXXX application notes. Two device example:\n" +                "    addr0 = 192.168.10.2\n" +                "    addr1 = 192.168.10.3\n" +            ); +            return sep_indexed_dev_addrs(fixed_dev_addr); +        } +    } +    //------------------------------------------------------------------ +    device_addrs_t dev_addrs; +    BOOST_FOREACH(const std::string &key, dev_addr.keys()){ +        boost::cmatch matches; +        if (not boost::regex_match(key.c_str(), matches, boost::regex("^(\\D+)(\\d*)$"))){ +            throw std::runtime_error("unknown key format: " + key); +        } +        std::string key_part(matches[1].first, matches[1].second); +        std::string num_part(matches[2].first, matches[2].second); +        size_t num = (num_part.empty())? 0 : boost::lexical_cast<size_t>(num_part); +        dev_addrs.resize(std::max(num+1, dev_addrs.size())); +        dev_addrs[num][key_part] = dev_addr[key]; +    } +    return dev_addrs; +} + +//! combine a vector in device addresses into an indexed device address +device_addr_t combine_dev_addr_vector(const device_addrs_t &dev_addrs){ +    device_addr_t dev_addr; +    for (size_t i = 0; i < dev_addrs.size(); i++){ +        BOOST_FOREACH(const std::string &key, dev_addrs[i].keys()){ +            dev_addr[str(boost::format("%s%d") % key % i)] = dev_addrs[i][key]; +        } +    } +    return dev_addr; +} + +/*********************************************************************** + * Discovery over the udp transport + **********************************************************************/ +static device_addrs_t usrp2_find(const device_addr_t &hint_){ +    //handle the multi-device discovery +    device_addrs_t hints = sep_indexed_dev_addrs(hint_); +    if (hints.size() > 1){ +        device_addrs_t found_devices; +        BOOST_FOREACH(const device_addr_t &hint_i, hints){ +            device_addrs_t found_devices_i = usrp2_find(hint_i); +            if (found_devices_i.size() != 1) throw std::runtime_error(str(boost::format( +                "Could not resolve device hint \"%s\" to a single device." +            ) % hint_i.to_string())); +            found_devices.push_back(found_devices_i[0]); +        } +        return device_addrs_t(1, combine_dev_addr_vector(found_devices)); +    } + +    //initialize the hint for a single device case +    UHD_ASSERT_THROW(hints.size() <= 1); +    hints.resize(1); //in case it was empty +    device_addr_t hint = hints[0]; +    device_addrs_t usrp2_addrs; + +    //return an empty list of addresses when type is set to non-usrp2 +    if (hint.has_key("type") and hint["type"] != "usrp2") return usrp2_addrs; + +    //if no address was specified, send a broadcast on each interface +    if (not hint.has_key("addr")){ +        BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()){ +            //avoid the loopback device +            if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + +            //create a new hint with this broadcast address +            device_addr_t new_hint = hint; +            new_hint["addr"] = if_addrs.bcast; + +            //call discover with the new hint and append results +            device_addrs_t new_usrp2_addrs = usrp2_find(new_hint); +            usrp2_addrs.insert(usrp2_addrs.begin(), +                new_usrp2_addrs.begin(), new_usrp2_addrs.end() +            ); +        } +        return usrp2_addrs; +    } + +    //create a udp transport to communicate +    std::string ctrl_port = boost::lexical_cast<std::string>(USRP2_UDP_CTRL_PORT); +    udp_simple::sptr udp_transport = udp_simple::make_broadcast( +        hint["addr"], ctrl_port +    ); + +    //send a hello control packet +    usrp2_ctrl_data_t ctrl_data_out; +    ctrl_data_out.proto_ver = htonl(USRP2_FW_COMPAT_NUM); +    ctrl_data_out.id = htonl(USRP2_CTRL_ID_WAZZUP_BRO); +    udp_transport->send(boost::asio::buffer(&ctrl_data_out, sizeof(ctrl_data_out))); + +    //loop and recieve until the timeout +    boost::uint8_t usrp2_ctrl_data_in_mem[udp_simple::mtu]; //allocate max bytes for recv +    const usrp2_ctrl_data_t *ctrl_data_in = reinterpret_cast<const usrp2_ctrl_data_t *>(usrp2_ctrl_data_in_mem); +    while(true){ +        size_t len = udp_transport->recv(asio::buffer(usrp2_ctrl_data_in_mem)); +        //std::cout << len << "\n"; +        if (len > offsetof(usrp2_ctrl_data_t, data) and ntohl(ctrl_data_in->id) == USRP2_CTRL_ID_WAZZUP_DUDE){ +            //make a boost asio ipv4 with the raw addr in host byte order +            boost::asio::ip::address_v4 ip_addr(ntohl(ctrl_data_in->data.ip_addr)); +            device_addr_t new_addr; +            new_addr["type"] = "usrp2"; +            new_addr["addr"] = ip_addr.to_string(); +            //Attempt to read the name from the EEPROM and perform filtering. +            //This operation can throw due to compatibility mismatch. +            //In this case, the discovered device will be ignored. +            try{ +                mboard_eeprom_t mb_eeprom = usrp2_iface::make( +                    udp_simple::make_connected(new_addr["addr"], num2str(USRP2_UDP_CTRL_PORT)) +                )->mb_eeprom; +                new_addr["name"] = mb_eeprom["name"]; +                new_addr["serial"] = mb_eeprom["serial"]; +                if ( +                    (not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +                    (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) +                ){ +                    usrp2_addrs.push_back(new_addr); +                } +            } +            catch(const std::exception &e){ +                uhd::warning::post( +                    std::string("Ignoring discovered device\n") +                    + e.what() +                ); +            } +            //dont break here, it will exit the while loop +            //just continue on to the next loop iteration +        } +        if (len == 0) break; //timeout +    } + +    return usrp2_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp2_make(const device_addr_t &device_addr){ +sep_indexed_dev_addrs(device_addr); +    //create a ctrl and data transport for each address +    std::vector<udp_simple::sptr> ctrl_transports; +    std::vector<zero_copy_if::sptr> data_transports; + +    BOOST_FOREACH(const device_addr_t &dev_addr_i, sep_indexed_dev_addrs(device_addr)){ +        ctrl_transports.push_back(udp_simple::make_connected( +            dev_addr_i["addr"], num2str(USRP2_UDP_CTRL_PORT) +        )); +        data_transports.push_back(udp_zero_copy::make( +            dev_addr_i["addr"], num2str(USRP2_UDP_DATA_PORT), device_addr +        )); +    } + +    //create the usrp2 implementation guts +    return device::sptr( +        new usrp2_impl(ctrl_transports, data_transports, device_addr) +    ); +} + +UHD_STATIC_BLOCK(register_usrp2_device){ +    device::register_device(&usrp2_find, &usrp2_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp2_impl::usrp2_impl( +    std::vector<udp_simple::sptr> ctrl_transports, +    std::vector<zero_copy_if::sptr> data_transports, +    const device_addr_t &flow_control_hints +): +    _data_transports(data_transports) +{ +    //setup rx otw type +    _rx_otw_type.width = 16; +    _rx_otw_type.shift = 0; +    _rx_otw_type.byteorder = uhd::otw_type_t::BO_BIG_ENDIAN; + +    //setup tx otw type +    _tx_otw_type.width = 16; +    _tx_otw_type.shift = 0; +    _tx_otw_type.byteorder = uhd::otw_type_t::BO_BIG_ENDIAN; + +    //!!!!! set the otw type here before continuing, its used below + +    //create a new mboard handler for each control transport +    for(size_t i = 0; i < ctrl_transports.size(); i++){ +        _mboards.push_back(usrp2_mboard_impl::sptr(new usrp2_mboard_impl( +            i, ctrl_transports[i], data_transports[i], +            this->get_max_recv_samps_per_packet(), +            flow_control_hints +        ))); +        //use an empty name when there is only one mboard +        std::string name = (ctrl_transports.size() > 1)? boost::lexical_cast<std::string>(i) : ""; +        _mboard_dict[name] = _mboards.back(); +    } + +    //init the send and recv io +    io_init(); + +} + +usrp2_impl::~usrp2_impl(void){ +    /* NOP */ +} + +/*********************************************************************** + * Device Properties + **********************************************************************/ +void usrp2_impl::get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<device_prop_t>()){ +    case DEVICE_PROP_NAME: +        if (_mboards.size() > 1) val = std::string("USRP2/N Series multi-device"); +        else                     val = std::string("USRP2/N Series device"); +        return; + +    case DEVICE_PROP_MBOARD: +        val = _mboard_dict[key.name]->get_link(); +        return; + +    case DEVICE_PROP_MBOARD_NAMES: +        val = prop_names_t(_mboard_dict.keys()); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp2_impl::set(const wax::obj &, const wax::obj &){ +    UHD_THROW_PROP_SET_ERROR(); +} diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp new file mode 100644 index 000000000..aa8eb0155 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -0,0 +1,231 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_IMPL_HPP +#define INCLUDED_USRP2_IMPL_HPP + +#include "usrp2_iface.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "gps_ctrl.hpp" +#include "serdes_ctrl.hpp" +#include <uhd/device.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/subdev_spec.hpp> + +/*! + * Make a usrp2 dboard interface. + * \param iface the usrp2 interface object + * \param clk_ctrl the clock control object + * \return a sptr to a new dboard interface + */ +uhd::usrp::dboard_iface::sptr make_usrp2_dboard_iface( +    usrp2_iface::sptr iface, +    usrp2_clock_ctrl::sptr clk_ctrl +); + +/*! + * Simple wax obj proxy class: + * Provides a wax obj interface for a set and a get function. + * This allows us to create nested properties structures + * while maintaining flattened code within the implementation. + */ +class wax_obj_proxy : public wax::obj{ +public: +    typedef boost::function<void(const wax::obj &, wax::obj &)>       get_t; +    typedef boost::function<void(const wax::obj &, const wax::obj &)> set_t; +    typedef boost::shared_ptr<wax_obj_proxy> sptr; + +    static sptr make(const get_t &get, const set_t &set){ +        return sptr(new wax_obj_proxy(get, set)); +    } + +private: +    get_t _get; set_t _set; +    wax_obj_proxy(const get_t &get, const set_t &set): _get(get), _set(set){}; +    void get(const wax::obj &key, wax::obj &val){return _get(key, val);} +    void set(const wax::obj &key, const wax::obj &val){return _set(key, val);} +}; + +/*! + * USRP2 mboard implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class usrp2_mboard_impl : public wax::obj{ +public: +    typedef boost::shared_ptr<usrp2_mboard_impl> sptr; + +    //structors +    usrp2_mboard_impl( +        size_t index, +        uhd::transport::udp_simple::sptr, +        uhd::transport::zero_copy_if::sptr, +        size_t recv_samps_per_packet, +        const uhd::device_addr_t &flow_control_hints +    ); +    ~usrp2_mboard_impl(void); + +    inline double get_master_clock_freq(void){ +        return _clock_ctrl->get_master_clock_rate(); +    } + +    void handle_overflow(void); + +private: +    size_t _index; +    bool _continuous_streaming; + +    //interfaces +    usrp2_iface::sptr _iface; +    usrp2_clock_ctrl::sptr _clock_ctrl; +    usrp2_codec_ctrl::sptr _codec_ctrl; +    usrp2_serdes_ctrl::sptr _serdes_ctrl; +    usrp2_gps_ctrl::sptr _gps_ctrl; + +    //properties for this mboard +    void get(const wax::obj &, wax::obj &); +    void set(const wax::obj &, const wax::obj &); +    uhd::usrp::subdev_spec_t _rx_subdev_spec, _tx_subdev_spec; + +    //rx and tx dboard methods and objects +    uhd::usrp::dboard_manager::sptr _dboard_manager; +    uhd::usrp::dboard_iface::sptr _dboard_iface; +    void dboard_init(void); + +    //methods and shadows for clock configuration +    uhd::clock_config_t _clock_config; +    void init_clock_config(void); +    void update_clock_config(void); +    void set_time_spec(const uhd::time_spec_t &time_spec, bool now); + +    //properties interface for the codec +    void codec_init(void); +    void rx_codec_get(const wax::obj &, wax::obj &); +    void rx_codec_set(const wax::obj &, const wax::obj &); +    void tx_codec_get(const wax::obj &, wax::obj &); +    void tx_codec_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _rx_codec_proxy; +    wax_obj_proxy::sptr _tx_codec_proxy; + +    void rx_codec_set_gain(float, const std::string &); +    uhd::dict<std::string, float> _codec_rx_gains; + +    //properties interface for rx dboard +    void rx_dboard_get(const wax::obj &, wax::obj &); +    void rx_dboard_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _rx_dboard_proxy; +    uhd::usrp::dboard_eeprom_t _rx_db_eeprom; + +    //properties interface for tx dboard +    void tx_dboard_get(const wax::obj &, wax::obj &); +    void tx_dboard_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _tx_dboard_proxy; +    uhd::usrp::dboard_eeprom_t _tx_db_eeprom; + +    //methods and shadows for the ddc dsp +    std::vector<size_t> _allowed_decim_and_interp_rates; +    size_t _ddc_decim; +    double _ddc_freq; +    void init_ddc_config(void); +    void issue_ddc_stream_cmd(const uhd::stream_cmd_t &stream_cmd); + +    //methods and shadows for the duc dsp +    size_t _duc_interp; +    double _duc_freq; +    void init_duc_config(void); + +    //properties interface for ddc +    void ddc_get(const wax::obj &, wax::obj &); +    void ddc_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _rx_dsp_proxy; + +    //properties interface for duc +    void duc_get(const wax::obj &, wax::obj &); +    void duc_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _tx_dsp_proxy; + +}; + +/*! + * USRP2 implementation guts: + * The implementation details are encapsulated here. + * Handles device properties and streaming... + */ +class usrp2_impl : public uhd::device{ +public: +    static const size_t sram_bytes = size_t(1 << 20); +    static const boost::uint32_t RECV_SID = 1; +    static const boost::uint32_t ASYNC_SID = 2; + +    /*! +     * Create a new usrp2 impl base. +     * \param ctrl_transports the udp transports for control +     * \param data_transports the udp transports for data +     * \param flow_control_hints optional flow control params +     */ +    usrp2_impl( +        std::vector<uhd::transport::udp_simple::sptr> ctrl_transports, +        std::vector<uhd::transport::zero_copy_if::sptr> data_transports, +        const uhd::device_addr_t &flow_control_hints +    ); + +    ~usrp2_impl(void); + +    //the io interface +    size_t send( +        const std::vector<const void *> &, size_t, +        const uhd::tx_metadata_t &, const uhd::io_type_t &, +        uhd::device::send_mode_t, double +    ); +    size_t recv( +        const std::vector<void *> &, size_t, +        uhd::rx_metadata_t &, const uhd::io_type_t &, +        uhd::device::recv_mode_t, double +    ); +    size_t get_max_send_samps_per_packet(void) const; +    size_t get_max_recv_samps_per_packet(void) const; +    bool recv_async_msg(uhd::async_metadata_t &, double); + +private: +    //device properties interface +    void get(const wax::obj &, wax::obj &); +    void set(const wax::obj &, const wax::obj &); + +    //pointers to mboards on this device (think mimo setup) +    std::vector<usrp2_mboard_impl::sptr> _mboards; +    uhd::dict<std::string, usrp2_mboard_impl::sptr> _mboard_dict; + +    //io impl methods and members +    std::vector<uhd::transport::zero_copy_if::sptr> _data_transports; +    uhd::otw_type_t _rx_otw_type, _tx_otw_type; +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void io_init(void); +}; + +#endif /* INCLUDED_USRP2_IMPL_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_regs.cpp b/host/lib/usrp/usrp2/usrp2_regs.cpp new file mode 100644 index 000000000..dd0433816 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_regs.cpp @@ -0,0 +1,102 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_regs.hpp" +#include "usrp2_iface.hpp" + +int sr_addr(int misc_output_base, int sr) { +	return misc_output_base + 4 * sr; +} + +usrp2_regs_t usrp2_get_regs(bool use_n2xx_map) { + +  //how about you just make this dependent on hw_rev instead of doing the init before main, and give up the const globals, since the application won't ever need both. +  const int misc_output_base = (use_n2xx_map) ? USRP2P_MISC_OUTPUT_BASE : USRP2_MISC_OUTPUT_BASE, +            gpio_base        = (use_n2xx_map) ? USRP2P_GPIO_BASE        : USRP2_GPIO_BASE, +            atr_base         = (use_n2xx_map) ? USRP2P_ATR_BASE         : USRP2_ATR_BASE, +            bp_base          = (use_n2xx_map) ? USRP2P_BP_STATUS_BASE   : USRP2_BP_STATUS_BASE; + +  usrp2_regs_t x; +  x.sr_misc = 0; +  x.sr_tx_prot_eng = 32; +  x.sr_rx_prot_eng = 48; +  x.sr_buffer_pool_ctrl = 64; +  x.sr_udp_sm = 96; +  x.sr_tx_dsp = 208; +  x.sr_tx_ctrl = 224; +  x.sr_rx_dsp = 160; +  x.sr_rx_ctrl = 176; +  x.sr_time64 = 192; +  x.sr_simtimer = 198; +  x.sr_last = 255; +  x.misc_ctrl_clock = sr_addr(misc_output_base, 0); +  x.misc_ctrl_serdes = sr_addr(misc_output_base, 1); +  x.misc_ctrl_adc = sr_addr(misc_output_base, 2); +  x.misc_ctrl_leds = sr_addr(misc_output_base, 3); +  x.misc_ctrl_phy = sr_addr(misc_output_base, 4); +  x.misc_ctrl_dbg_mux = sr_addr(misc_output_base, 5); +  x.misc_ctrl_ram_page = sr_addr(misc_output_base, 6); +  x.misc_ctrl_flush_icache = sr_addr(misc_output_base, 7); +  x.misc_ctrl_led_src = sr_addr(misc_output_base, 8); +  x.time64_secs = sr_addr(misc_output_base, x.sr_time64 + 0); +  x.time64_ticks = sr_addr(misc_output_base, x.sr_time64 + 1); +  x.time64_flags = sr_addr(misc_output_base, x.sr_time64 + 2); +  x.time64_imm = sr_addr(misc_output_base, x.sr_time64 + 3); +  x.time64_tps = sr_addr(misc_output_base, x.sr_time64 + 4); +  x.time64_secs_rb = bp_base + 4*10; +  x.time64_ticks_rb = bp_base + 4*11; +  x.compat_num_rb = bp_base + 4*12; +  x.dsp_tx_freq = sr_addr(misc_output_base, x.sr_tx_dsp + 0); +  x.dsp_tx_scale_iq = sr_addr(misc_output_base, x.sr_tx_dsp + 1); +  x.dsp_tx_interp_rate = sr_addr(misc_output_base, x.sr_tx_dsp + 2); +  x.dsp_tx_mux = sr_addr(misc_output_base, x.sr_tx_dsp + 4); +  x.dsp_rx_freq = sr_addr(misc_output_base, x.sr_rx_dsp + 0); +  x.dsp_rx_scale_iq = sr_addr(misc_output_base, x.sr_rx_dsp + 1); +  x.dsp_rx_decim_rate = sr_addr(misc_output_base, x.sr_rx_dsp + 2); +  x.dsp_rx_dcoffset_i = sr_addr(misc_output_base, x.sr_rx_dsp + 3); +  x.dsp_rx_dcoffset_q = sr_addr(misc_output_base, x.sr_rx_dsp + 4); +  x.dsp_rx_mux = sr_addr(misc_output_base, x.sr_rx_dsp + 5); +  x.gpio_io = gpio_base + 0; +  x.gpio_ddr = gpio_base + 4; +  x.gpio_tx_sel = gpio_base + 8; +  x.gpio_rx_sel = gpio_base + 12; +  x.atr_idle_txside = atr_base + 0; +  x.atr_idle_rxside = atr_base + 2; +  x.atr_intx_txside = atr_base + 4; +  x.atr_intx_rxside = atr_base + 6; +  x.atr_inrx_txside = atr_base + 8; +  x.atr_inrx_rxside = atr_base + 10; +  x.atr_full_txside = atr_base + 12; +  x.atr_full_rxside = atr_base + 14; +  x.rx_ctrl_stream_cmd = sr_addr(misc_output_base, x.sr_rx_ctrl + 0); +  x.rx_ctrl_time_secs = sr_addr(misc_output_base, x.sr_rx_ctrl + 1); +  x.rx_ctrl_time_ticks = sr_addr(misc_output_base, x.sr_rx_ctrl + 2); +  x.rx_ctrl_clear_overrun = sr_addr(misc_output_base, x.sr_rx_ctrl + 3); +  x.rx_ctrl_vrt_header = sr_addr(misc_output_base, x.sr_rx_ctrl + 4); +  x.rx_ctrl_vrt_stream_id = sr_addr(misc_output_base, x.sr_rx_ctrl + 5); +  x.rx_ctrl_vrt_trailer = sr_addr(misc_output_base, x.sr_rx_ctrl + 6); +  x.rx_ctrl_nsamps_per_pkt = sr_addr(misc_output_base, x.sr_rx_ctrl + 7); +  x.rx_ctrl_nchannels = sr_addr(misc_output_base, x.sr_rx_ctrl + 8); +  x.tx_ctrl_num_chan = sr_addr(misc_output_base, x.sr_tx_ctrl + 0); +  x.tx_ctrl_clear_state = sr_addr(misc_output_base, x.sr_tx_ctrl + 1); +  x.tx_ctrl_report_sid = sr_addr(misc_output_base, x.sr_tx_ctrl + 2); +  x.tx_ctrl_policy = sr_addr(misc_output_base, x.sr_tx_ctrl + 3); +  x.tx_ctrl_cycles_per_up = sr_addr(misc_output_base, x.sr_tx_ctrl + 4); +  x.tx_ctrl_packets_per_up = sr_addr(misc_output_base, x.sr_tx_ctrl + 5); + +  return x; +} diff --git a/host/lib/usrp/usrp2/usrp2_regs.hpp b/host/lib/usrp/usrp2/usrp2_regs.hpp new file mode 100644 index 000000000..9936d634a --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_regs.hpp @@ -0,0 +1,289 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_REGS_HPP +#define INCLUDED_USRP2_REGS_HPP + +#include <boost/cstdint.hpp> + +#define USRP2_MISC_OUTPUT_BASE  0xD400 +#define USRP2_GPIO_BASE         0xC800 +#define USRP2_ATR_BASE          0xE400 +#define USRP2_BP_STATUS_BASE    0xCC00 + +#define USRP2P_MISC_OUTPUT_BASE 0x2000 +#define USRP2P_GPIO_BASE        0x3200 +#define USRP2P_ATR_BASE         0x3800 +#define USRP2P_BP_STATUS_BASE   0x3300 + +typedef struct { +    int sr_misc; +    int sr_tx_prot_eng; +    int sr_rx_prot_eng; +    int sr_buffer_pool_ctrl; +    int sr_udp_sm; +    int sr_tx_dsp; +    int sr_tx_ctrl; +    int sr_rx_dsp; +    int sr_rx_ctrl; +    int sr_time64; +    int sr_simtimer; +    int sr_last; +    int misc_ctrl_clock; +    int misc_ctrl_serdes; +    int misc_ctrl_adc; +    int misc_ctrl_leds; +    int misc_ctrl_phy; +    int misc_ctrl_dbg_mux; +    int misc_ctrl_ram_page; +    int misc_ctrl_flush_icache; +    int misc_ctrl_led_src; +    int time64_secs; // value to set absolute secs to on next PPS +    int time64_ticks; // value to set absolute ticks to on next PPS +    int time64_flags; // flags -- see chart below +    int time64_imm; // set immediate (0=latch on next pps, 1=latch immediate, default=0) +    int time64_tps; // ticks per second rollover count +    int time64_secs_rb; +    int time64_ticks_rb; +    int compat_num_rb; +    int dsp_tx_freq; +    int dsp_tx_scale_iq; +    int dsp_tx_interp_rate; +    int dsp_tx_mux; +    int dsp_rx_freq; +    int dsp_rx_scale_iq; +    int dsp_rx_decim_rate; +    int dsp_rx_dcoffset_i; +    int dsp_rx_dcoffset_q; +    int dsp_rx_mux; +    int gpio_base; +    int gpio_io; +    int gpio_ddr; +    int gpio_tx_sel; +    int gpio_rx_sel; +    int atr_base; +    int atr_idle_txside; +    int atr_idle_rxside; +    int atr_intx_txside; +    int atr_intx_rxside; +    int atr_inrx_txside; +    int atr_inrx_rxside; +    int atr_full_txside; +    int atr_full_rxside; +    int rx_ctrl_stream_cmd; +    int rx_ctrl_time_secs; +    int rx_ctrl_time_ticks; +    int rx_ctrl_clear_overrun; +    int rx_ctrl_vrt_header; +    int rx_ctrl_vrt_stream_id; +    int rx_ctrl_vrt_trailer; +    int rx_ctrl_nsamps_per_pkt; +    int rx_ctrl_nchannels; +    int tx_ctrl_num_chan; +    int tx_ctrl_clear_state; +    int tx_ctrl_report_sid; +    int tx_ctrl_policy; +    int tx_ctrl_cycles_per_up; +    int tx_ctrl_packets_per_up; +} usrp2_regs_t; + +extern const usrp2_regs_t usrp2_regs; //the register definitions, set in usrp2_regs.cpp and usrp2p_regs.cpp + +usrp2_regs_t usrp2_get_regs(bool); + +//////////////////////////////////////////////////// +// Settings Bus, Slave #7, Not Byte Addressable! +// +// Output-only from processor point-of-view. +// 1KB of address space (== 256 32-bit write-only regs) + + +//#define MISC_OUTPUT_BASE        0xD400 +//#define TX_PROTOCOL_ENGINE_BASE 0xD480 +//#define RX_PROTOCOL_ENGINE_BASE 0xD4C0 +//#define BUFFER_POOL_CTRL_BASE   0xD500 +//#define LAST_SETTING_REG        0xD7FC  // last valid setting register + +///////////////////////////////////////////////// +// SPI Slave Constants +//////////////////////////////////////////////// +// Masks for controlling different peripherals +#define SPI_SS_AD9510    1 +#define SPI_SS_AD9777    2 +#define SPI_SS_RX_DAC    4 +#define SPI_SS_RX_ADC    8 +#define SPI_SS_RX_DB    16 +#define SPI_SS_TX_DAC   32 +#define SPI_SS_TX_ADC   64 +#define SPI_SS_TX_DB   128 +#define SPI_SS_ADS62P44 256 //for usrp2p + +///////////////////////////////////////////////// +// Misc Control +//////////////////////////////////////////////// +#define U2_FLAG_MISC_CTRL_SERDES_ENABLE 8 +#define U2_FLAG_MISC_CTRL_SERDES_PRBSEN 4 +#define U2_FLAG_MISC_CTRL_SERDES_LOOPEN 2 +#define U2_FLAG_MISC_CTRL_SERDES_RXEN   1 + +#define U2_FLAG_MISC_CTRL_ADC_ON  0x0F +#define U2_FLAG_MISC_CTRL_ADC_OFF 0x00 + +///////////////////////////////////////////////// +// VITA49 64 bit time (write only) +//////////////////////////////////////////////// +  /*! +   * \brief Time 64 flags +   * +   * <pre> +   * +   *    3                   2                   1                        +   *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +   * +-----------------------------------------------------------+-+-+ +   * |                                                           |S|P| +   * +-----------------------------------------------------------+-+-+ +   * +   * P - PPS edge selection (0=negedge, 1=posedge, default=0) +   * S - Source (0=sma, 1=mimo, 0=default) +   * +   * </pre> +   */ + +//pps flags (see above) +#define U2_FLAG_TIME64_PPS_NEGEDGE (0 << 0) +#define U2_FLAG_TIME64_PPS_POSEDGE (1 << 0) +#define U2_FLAG_TIME64_PPS_SMA     (0 << 1) +#define U2_FLAG_TIME64_PPS_MIMO    (1 << 1) + +#define U2_FLAG_TIME64_LATCH_NOW 1 +#define U2_FLAG_TIME64_LATCH_NEXT_PPS 0 + +///////////////////////////////////////////////// +// DSP TX Regs +//////////////////////////////////////////////// + +  /*! +   * \brief output mux configuration. +   * +   * <pre> +   *     3                   2                   1                        +   *   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +   *  +-------------------------------+-------+-------+-------+-------+ +   *  |                                               | DAC1  |  DAC0 | +   *  +-------------------------------+-------+-------+-------+-------+ +   *  +   *  There are N DUCs (1 now) with complex inputs and outputs. +   *  There are two DACs. +   *  +   *  Each 4-bit DACx field specifies the source for the DAC +   *  Each subfield is coded like this:  +   *  +   *     3 2 1 0 +   *    +-------+ +   *    |   N   | +   *    +-------+ +   *  +   *  N specifies which DUC output is connected to this DAC. +   *  +   *   N   which interp output +   *  ---  ------------------- +   *   0   DUC 0 I +   *   1   DUC 0 Q +   *   2   DUC 1 I +   *   3   DUC 1 Q +   *   F   All Zeros +   *    +   * The default value is 0x10 +   * </pre> +   */ + + +///////////////////////////////////////////////// +// DSP RX Regs +//////////////////////////////////////////////// + +  /*! +   * \brief input mux configuration. +   * +   * This determines which ADC (or constant zero) is connected to  +   * each DDC input.  There are N DDCs (1 now).  Each has two inputs. +   * +   * <pre> +   * Mux value: +   * +   *    3                   2                   1                        +   *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +   * +-------+-------+-------+-------+-------+-------+-------+-------+ +   * |                                                       |Q0 |I0 | +   * +-------+-------+-------+-------+-------+-------+-------+-------+ +   * +   * Each 2-bit I field is either 00 (A/D A), 01 (A/D B) or 1X (const zero) +   * Each 2-bit Q field is either 00 (A/D A), 01 (A/D B) or 1X (const zero) +   * +   * The default value is 0x4 +   * </pre> +   */ + +//////////////////////////////////////////////// +// GPIO, Slave 4 +//////////////////////////////////////////////// + +// each 2-bit sel field is layed out this way +#define U2_FLAG_GPIO_SEL_GPIO      0 // if pin is an output, set by GPIO register +#define U2_FLAG_GPIO_SEL_ATR       1 // if pin is an output, set by ATR logic +#define U2_FLAG_GPIO_SEL_DEBUG_0   2 // if pin is an output, debug lines from FPGA fabric +#define U2_FLAG_GPIO_SEL_DEBUG_1   3 // if pin is an output, debug lines from FPGA fabric + +/////////////////////////////////////////////////// +// ATR Controller, Slave 11 +//////////////////////////////////////////////// + + +/////////////////////////////////////////////////// +// RX CTRL regs +/////////////////////////////////////////////////// +// The following 3 are logically a single command register. +// They are clocked into the underlying fifo when time_ticks is written. +//#define U2_REG_RX_CTRL_STREAM_CMD        _SR_ADDR(SR_RX_CTRL + 0) // {now, chain, num_samples(30) +//#define U2_REG_RX_CTRL_TIME_SECS         _SR_ADDR(SR_RX_CTRL + 1) +//#define U2_REG_RX_CTRL_TIME_TICKS        _SR_ADDR(SR_RX_CTRL + 2) + +//#define U2_REG_RX_CTRL_CLEAR_STATE       _SR_ADDR(SR_RX_CTRL + 3) +//#define U2_REG_RX_CTRL_VRT_HEADER        _SR_ADDR(SR_RX_CTRL + 4) // word 0 of packet.  FPGA fills in packet counter +//#define U2_REG_RX_CTRL_VRT_STREAM_ID     _SR_ADDR(SR_RX_CTRL + 5) // word 1 of packet. +//#define U2_REG_RX_CTRL_VRT_TRAILER       _SR_ADDR(SR_RX_CTRL + 6) +//#define U2_REG_RX_CTRL_NSAMPS_PER_PKT    _SR_ADDR(SR_RX_CTRL + 7) +//#define U2_REG_RX_CTRL_NCHANNELS         _SR_ADDR(SR_RX_CTRL + 8) // 1 in basic case, up to 4 for vector sources + +/////////////////////////////////////////////////// +// TX CTRL regs +/////////////////////////////////////////////////// +//#define U2_REG_TX_CTRL_NUM_CHAN          _SR_ADDR(SR_TX_CTRL + 0) +//#define U2_REG_TX_CTRL_CLEAR_STATE       _SR_ADDR(SR_TX_CTRL + 1) +//#define U2_REG_TX_CTRL_REPORT_SID        _SR_ADDR(SR_TX_CTRL + 2) +//#define U2_REG_TX_CTRL_POLICY            _SR_ADDR(SR_TX_CTRL + 3) +//#define U2_REG_TX_CTRL_CYCLES_PER_UP     _SR_ADDR(SR_TX_CTRL + 4) +//#define U2_REG_TX_CTRL_PACKETS_PER_UP    _SR_ADDR(SR_TX_CTRL + 5) + +#define U2_FLAG_TX_CTRL_POLICY_WAIT          (0x1 << 0) +#define U2_FLAG_TX_CTRL_POLICY_NEXT_PACKET   (0x1 << 1) +#define U2_FLAG_TX_CTRL_POLICY_NEXT_BURST    (0x1 << 2) + +//enable flag for registers: cycles and packets per update packet +#define U2_FLAG_TX_CTRL_UP_ENB              (1ul << 31) + +#endif /* INCLUDED_USRP2_REGS_HPP */ diff --git a/host/lib/usrp/usrp_e100/CMakeLists.txt b/host/lib/usrp/usrp_e100/CMakeLists.txt new file mode 100644 index 000000000..3c5c58ee0 --- /dev/null +++ b/host/lib/usrp/usrp_e100/CMakeLists.txt @@ -0,0 +1,47 @@ +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +#This file will be included by cmake, use absolute paths! + +######################################################################## +# Conditionally configure the USRP-E100 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP-E100" ENABLE_USRP_E100 FALSE) + +IF(ENABLE_USRP_E100) +    INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/include) + +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/clock_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/clock_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/codec_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/codec_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/codec_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/dboard_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/dboard_iface.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/dsp_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/fpga-downloader.cc +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/io_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/mboard_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_impl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_iface.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_iface.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_mmap_zero_copy.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_regs.hpp +    ) +ENDIF(ENABLE_USRP_E100) diff --git a/host/lib/usrp/usrp_e100/clock_ctrl.cpp b/host/lib/usrp/usrp_e100/clock_ctrl.cpp new file mode 100644 index 000000000..1fb1a7125 --- /dev/null +++ b/host/lib/usrp/usrp_e100/clock_ctrl.cpp @@ -0,0 +1,263 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9522_regs.hpp" +#include <uhd/utils/assert.hpp> +#include <boost/cstdint.hpp> +#include "usrp_e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <utility> +#include <iostream> + +using namespace uhd; + +template <typename div_type, typename bypass_type> static void set_clock_divider( +    size_t divider, div_type &low, div_type &high, bypass_type &bypass +){ +    high = divider/2 - 1; +    low = divider - high - 2; +    bypass = (divider == 1)? 1 : 0; +} + +/*********************************************************************** + * Constants + **********************************************************************/ +static const bool enable_test_clock = false; +static const size_t ref_clock_doubler = 2; //enabled below +static const double ref_clock_rate = 10e6 * ref_clock_doubler; + +static const size_t r_counter = 1; +static const size_t a_counter = 0; +static const size_t b_counter = 20 / ref_clock_doubler; +static const size_t prescaler = 8; //set below with enum, set to 8 when input is under 2400 MHz +static const size_t vco_divider = 5; //set below with enum + +static const size_t n_counter = prescaler * b_counter + a_counter; +static const size_t vco_clock_rate = ref_clock_rate/r_counter * n_counter; //between 1400 and 1800 MHz +static const double master_clock_rate = vco_clock_rate/vco_divider; + +static const size_t fpga_clock_divider = size_t(master_clock_rate/64e6); +static const size_t codec_clock_divider = size_t(master_clock_rate/64e6); + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class usrp_e100_clock_ctrl_impl : public usrp_e100_clock_ctrl{ +public: +    usrp_e100_clock_ctrl_impl(usrp_e100_iface::sptr iface){ +        _iface = iface; + +        //init the clock gen registers +        //Note: out0 should already be clocking the FPGA or this isnt going to work +        _ad9522_regs.sdo_active = ad9522_regs_t::SDO_ACTIVE_SDO_SDIO; +        _ad9522_regs.enable_clock_doubler = 1; //enable ref clock doubler +        _ad9522_regs.enb_stat_eeprom_at_stat_pin = 0; //use status pin +        _ad9522_regs.status_pin_control = 0x1; //n divider +        _ad9522_regs.ld_pin_control = 0x00; //dld +        _ad9522_regs.refmon_pin_control = 0x12; //show ref2 + +        _ad9522_regs.enable_ref2 = 1; +        _ad9522_regs.enable_ref1 = 0; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF2; + +        _ad9522_regs.set_r_counter(r_counter); +        _ad9522_regs.a_counter = a_counter; +        _ad9522_regs.set_b_counter(b_counter); +        _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV8_9; + +        _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + +        _ad9522_regs.vco_calibration_now = 1; //calibrate it! +        _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV5; +        _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_VCO; + +        //setup fpga master clock +        _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; +        set_clock_divider(fpga_clock_divider, +            _ad9522_regs.divider0_low_cycles, +            _ad9522_regs.divider0_high_cycles, +            _ad9522_regs.divider0_bypass +        ); + +        //setup codec clock +        _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; +        set_clock_divider(codec_clock_divider, +            _ad9522_regs.divider1_low_cycles, +            _ad9522_regs.divider1_high_cycles, +            _ad9522_regs.divider1_bypass +        ); + +        //setup test clock (same divider as codec clock) +        _ad9522_regs.out4_format = ad9522_regs_t::OUT4_FORMAT_CMOS; +        _ad9522_regs.out4_cmos_configuration = (enable_test_clock)? +            ad9522_regs_t::OUT4_CMOS_CONFIGURATION_A_ON : +            ad9522_regs_t::OUT4_CMOS_CONFIGURATION_OFF; + +        //setup a list of register ranges to write +        typedef std::pair<boost::uint16_t, boost::uint16_t> range_t; +        static const std::vector<range_t> ranges = boost::assign::list_of +            (range_t(0x000, 0x000)) (range_t(0x010, 0x01F)) +            (range_t(0x0F0, 0x0FD)) (range_t(0x190, 0x19B)) +            (range_t(0x1E0, 0x1E1)) (range_t(0x230, 0x230)) +        ; + +        //write initial register values and latch/update +        BOOST_FOREACH(const range_t &range, ranges){ +            for(boost::uint16_t addr = range.first; addr <= range.second; addr++){ +                this->send_reg(addr); +            } +        } +        this->latch_regs(); +        //test read: +        //boost::uint32_t reg = _ad9522_regs.get_read_reg(0x01b); +        //boost::uint32_t result = _iface->transact_spi( +        //    UE_SPI_SS_AD9522, +        //    spi_config_t::EDGE_RISE, +        //    reg, 24, true /*no*/ +        //); +        //std::cout << "result " << std::hex << result << std::endl; +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +    } + +    ~usrp_e100_clock_ctrl_impl(void){ +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +    } + +    double get_fpga_clock_rate(void){ +        return master_clock_rate/fpga_clock_divider; +    } + +    /*********************************************************************** +     * RX Dboard Clock Control (output 9, divider 3) +     **********************************************************************/ +    void enable_rx_dboard_clock(bool enb){ +        _ad9522_regs.out9_format = ad9522_regs_t::OUT9_FORMAT_CMOS; +        _ad9522_regs.out9_cmos_configuration = (enb)? +            ad9522_regs_t::OUT9_CMOS_CONFIGURATION_B_ON : +            ad9522_regs_t::OUT9_CMOS_CONFIGURATION_OFF; +        this->send_reg(0x0F9); +        this->latch_regs(); +    } + +    std::vector<double> get_rx_dboard_clock_rates(void){ +        std::vector<double> rates; +        for(size_t div = 1; div <= 16+16; div++) +            rates.push_back(master_clock_rate/div); +        return rates; +    } + +    void set_rx_dboard_clock_rate(double rate){ +        assert_has(get_rx_dboard_clock_rates(), rate, "rx dboard clock rate"); +        size_t divider = size_t(master_clock_rate/rate); +        //set the divider registers +        set_clock_divider(divider, +            _ad9522_regs.divider3_low_cycles, +            _ad9522_regs.divider3_high_cycles, +            _ad9522_regs.divider3_bypass +        ); +        this->send_reg(0x199); +        this->send_reg(0x19a); +        this->latch_regs(); +    } + +    /*********************************************************************** +     * TX Dboard Clock Control (output 6, divider 2) +     **********************************************************************/ +    void enable_tx_dboard_clock(bool enb){ +        _ad9522_regs.out6_format = ad9522_regs_t::OUT6_FORMAT_CMOS; +        _ad9522_regs.out6_cmos_configuration = (enb)? +            ad9522_regs_t::OUT6_CMOS_CONFIGURATION_B_ON : +            ad9522_regs_t::OUT6_CMOS_CONFIGURATION_OFF; +        this->send_reg(0x0F6); +        this->latch_regs(); +    } + +    std::vector<double> get_tx_dboard_clock_rates(void){ +        return get_rx_dboard_clock_rates(); //same master clock, same dividers... +    } + +    void set_tx_dboard_clock_rate(double rate){ +        assert_has(get_tx_dboard_clock_rates(), rate, "tx dboard clock rate"); +        size_t divider = size_t(master_clock_rate/rate); +        //set the divider registers +        set_clock_divider(divider, +            _ad9522_regs.divider2_low_cycles, +            _ad9522_regs.divider2_high_cycles, +            _ad9522_regs.divider2_bypass +        ); +        this->send_reg(0x196); +        this->send_reg(0x197); +        this->latch_regs(); +    } +     +    /*********************************************************************** +     * Clock reference control +     **********************************************************************/ +    void use_internal_ref(void) { +        _ad9522_regs.enable_ref2 = 1; +        _ad9522_regs.enable_ref1 = 0; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF2; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; +        this->send_reg(0x01C); +    } +     +    void use_external_ref(void) { +        _ad9522_regs.enable_ref2 = 0; +        _ad9522_regs.enable_ref1 = 1; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; +        this->send_reg(0x01C); +    } +     +    void use_auto_ref(void) { +        _ad9522_regs.enable_ref2 = 1; +        _ad9522_regs.enable_ref1 = 1; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_AUTO; +    } + +private: +    usrp_e100_iface::sptr _iface; +    ad9522_regs_t _ad9522_regs; + +    void latch_regs(void){ +        _ad9522_regs.io_update = 1; +        this->send_reg(0x232); +    } + +    void send_reg(boost::uint16_t addr){ +        boost::uint32_t reg = _ad9522_regs.get_write_reg(addr); +        //std::cout << "clock control write reg: " << std::hex << reg << std::endl; +        _iface->transact_spi( +            UE_SPI_SS_AD9522, +            spi_config_t::EDGE_RISE, +            reg, 24, false /*no rb*/ +        ); +    } +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +usrp_e100_clock_ctrl::sptr usrp_e100_clock_ctrl::make(usrp_e100_iface::sptr iface){ +    return sptr(new usrp_e100_clock_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp_e100/clock_ctrl.hpp b/host/lib/usrp/usrp_e100/clock_ctrl.hpp new file mode 100644 index 000000000..d613d1473 --- /dev/null +++ b/host/lib/usrp/usrp_e100/clock_ctrl.hpp @@ -0,0 +1,103 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_CLOCK_CTRL_HPP +#define INCLUDED_USRP_E100_CLOCK_CTRL_HPP + +#include "usrp_e100_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp-e clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class usrp_e100_clock_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp_e100_clock_ctrl> sptr; + +    /*! +     * Make a new clock control object. +     * \param iface the usrp_e100 iface object +     * \return the clock control object +     */ +    static sptr make(usrp_e100_iface::sptr iface); + +    /*! +     * Get the rate of the fpga clock line. +     * \return the fpga clock rate in Hz +     */ +    virtual double get_fpga_clock_rate(void) = 0; + +    /*! +     * Get the possible rates of the rx dboard clock. +     * \return a vector of clock rates in Hz +     */ +    virtual std::vector<double> get_rx_dboard_clock_rates(void) = 0; + +    /*! +     * Get the possible rates of the tx dboard clock. +     * \return a vector of clock rates in Hz +     */ +    virtual std::vector<double> get_tx_dboard_clock_rates(void) = 0; + +    /*! +     * Set the rx dboard clock rate to a possible rate. +     * \param rate the new clock rate in Hz +     * \throw exception when rate cannot be achieved +     */ +    virtual void set_rx_dboard_clock_rate(double rate) = 0; + +    /*! +     * Set the tx dboard clock rate to a possible rate. +     * \param rate the new clock rate in Hz +     * \throw exception when rate cannot be achieved +     */ +    virtual void set_tx_dboard_clock_rate(double rate) = 0; + +    /*! +     * Enable/disable the rx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_rx_dboard_clock(bool enb) = 0; + +    /*! +     * Enable/disable the tx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_tx_dboard_clock(bool enb) = 0; +     +    /*! +     * Use the internal TCXO reference +     */ +    virtual void use_internal_ref(void) = 0; +     +    /*! +     * Use the external SMA reference +     */ +    virtual void use_external_ref(void) = 0; +     +    /*! +     * Use external if available, internal otherwise +     */ +    virtual void use_auto_ref(void) = 0; + +}; + +#endif /* INCLUDED_USRP_E100_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp_e100/codec_ctrl.cpp b/host/lib/usrp/usrp_e100/codec_ctrl.cpp new file mode 100644 index 000000000..18d9daca0 --- /dev/null +++ b/host/lib/usrp/usrp_e100/codec_ctrl.cpp @@ -0,0 +1,296 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/cstdint.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include "usrp_e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <iostream> + +using namespace uhd; + +static const bool codec_debug = false; + +const gain_range_t usrp_e100_codec_ctrl::tx_pga_gain_range(-20, 0, float(0.1)); +const gain_range_t usrp_e100_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class usrp_e100_codec_ctrl_impl : public usrp_e100_codec_ctrl{ +public: +    //structors +    usrp_e100_codec_ctrl_impl(usrp_e100_iface::sptr iface); +    ~usrp_e100_codec_ctrl_impl(void); + +    //aux adc and dac control +    float read_aux_adc(aux_adc_t which); +    void write_aux_dac(aux_dac_t which, float volts); + +    //pga gain control +    void set_tx_pga_gain(float); +    float get_tx_pga_gain(void); +    void set_rx_pga_gain(float, char); +    float get_rx_pga_gain(char); + +private: +    usrp_e100_iface::sptr _iface; +    ad9862_regs_t _ad9862_regs; +    aux_adc_t _last_aux_adc_a, _last_aux_adc_b; +    void send_reg(boost::uint8_t addr); +    void recv_reg(boost::uint8_t addr); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +usrp_e100_codec_ctrl_impl::usrp_e100_codec_ctrl_impl(usrp_e100_iface::sptr iface){ +    _iface = iface; + +    //soft reset +    _ad9862_regs.soft_reset = 1; +    this->send_reg(0); + +    //initialize the codec register settings +    _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; +    _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; +    _ad9862_regs.soft_reset = 0; + +    //setup rx side of codec +    _ad9862_regs.byp_buffer_a = 1; +    _ad9862_regs.byp_buffer_b = 1; +    _ad9862_regs.buffer_a_pd = 1; +    _ad9862_regs.buffer_b_pd = 1; +    _ad9862_regs.rx_pga_a = 0;//0x1f;  //TODO bring under api control +    _ad9862_regs.rx_pga_b = 0;//0x1f;  //TODO bring under api control +    _ad9862_regs.rx_twos_comp = 1; +    _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; + +    //setup tx side of codec +    _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; +    _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; +    _ad9862_regs.tx_retime = ad9862_regs_t::TX_RETIME_CLKOUT2; +    _ad9862_regs.tx_pga_gain = 199; //TODO bring under api control +    _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; +    _ad9862_regs.interp = ad9862_regs_t::INTERP_2; +    _ad9862_regs.tx_twos_comp = 1; +    _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_BYPASS; +    _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; +    _ad9862_regs.dac_a_coarse_gain = 0x3; +    _ad9862_regs.dac_b_coarse_gain = 0x3; +    _ad9862_regs.edges = ad9862_regs_t::EDGES_NORMAL; + +    //setup the dll +    _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; +    _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; +    _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; + +    //write the register settings to the codec +    for (uint8_t addr = 0; addr <= 25; addr++){ +        this->send_reg(addr); +    } + +    //aux adc clock +    _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; +    this->send_reg(34); +} + +usrp_e100_codec_ctrl_impl::~usrp_e100_codec_ctrl_impl(void){ +    //set aux dacs to zero +    this->write_aux_dac(AUX_DAC_A, 0); +    this->write_aux_dac(AUX_DAC_B, 0); +    this->write_aux_dac(AUX_DAC_C, 0); +    this->write_aux_dac(AUX_DAC_D, 0); + +    //power down +    _ad9862_regs.all_rx_pd = 1; +    this->send_reg(1); +    _ad9862_regs.tx_digital_pd = 1; +    _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; +    this->send_reg(8); +} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void usrp_e100_codec_ctrl_impl::set_tx_pga_gain(float gain){ +    int gain_word = int(mtpgw*(gain - tx_pga_gain_range.start())/(tx_pga_gain_range.stop() - tx_pga_gain_range.start())); +    _ad9862_regs.tx_pga_gain = std::clip(gain_word, 0, mtpgw); +    this->send_reg(16); +} + +float usrp_e100_codec_ctrl_impl::get_tx_pga_gain(void){ +    return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.stop() - tx_pga_gain_range.start())/mtpgw) + tx_pga_gain_range.start(); +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void usrp_e100_codec_ctrl_impl::set_rx_pga_gain(float gain, char which){ +    int gain_word = int(mrpgw*(gain - rx_pga_gain_range.start())/(rx_pga_gain_range.stop() - rx_pga_gain_range.start())); +    gain_word = std::clip(gain_word, 0, mrpgw); +    switch(which){ +    case 'A': +        _ad9862_regs.rx_pga_a = gain_word; +        this->send_reg(2); +        return; +    case 'B': +        _ad9862_regs.rx_pga_b = gain_word; +        this->send_reg(3); +        return; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +float usrp_e100_codec_ctrl_impl::get_rx_pga_gain(char which){ +    int gain_word; +    switch(which){ +    case 'A': gain_word = _ad9862_regs.rx_pga_a; break; +    case 'B': gain_word = _ad9862_regs.rx_pga_b; break; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +    return (gain_word*(rx_pga_gain_range.stop() - rx_pga_gain_range.start())/mrpgw) + rx_pga_gain_range.start(); +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static float aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low){ +    return float((boost::uint16_t(high) << 2) | low)*3.3/0x3ff; +} + +float usrp_e100_codec_ctrl_impl::read_aux_adc(aux_adc_t which){ +    //check to see if the switch needs to be set +    bool write_switch = false; +    switch(which){ + +    case AUX_ADC_A1: +    case AUX_ADC_A2: +        if (which != _last_aux_adc_a){ +            _ad9862_regs.select_a = (which == AUX_ADC_A1)? +                ad9862_regs_t::SELECT_A_AUX_ADC1: ad9862_regs_t::SELECT_A_AUX_ADC2; +            _last_aux_adc_a = which; +            write_switch = true; +        } +        break; + +    case AUX_ADC_B1: +    case AUX_ADC_B2: +        if (which != _last_aux_adc_b){ +            _ad9862_regs.select_b = (which == AUX_ADC_B1)? +                ad9862_regs_t::SELECT_B_AUX_ADC1: ad9862_regs_t::SELECT_B_AUX_ADC2; +            _last_aux_adc_b = which; +            write_switch = true; +        } +        break; + +    } + +    //write the switch if it changed +    if(write_switch) this->send_reg(34); + +    //map aux adcs to register values to read +    static const uhd::dict<aux_adc_t, boost::uint8_t> aux_dac_to_addr = boost::assign::map_list_of +        (AUX_ADC_A2, 26) (AUX_ADC_A1, 28) +        (AUX_ADC_B2, 30) (AUX_ADC_B1, 32) +    ; + +    //read the value +    this->recv_reg(aux_dac_to_addr[which]+0); +    this->recv_reg(aux_dac_to_addr[which]+1); + +    //return the value scaled to volts +    switch(which){ +    case AUX_ADC_A1: return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); +    case AUX_ADC_A2: return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); +    case AUX_ADC_B1: return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); +    case AUX_ADC_B2: return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); +    } +    UHD_ASSERT_THROW(false); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void usrp_e100_codec_ctrl_impl::write_aux_dac(aux_dac_t which, float volts){ +    //special case for aux dac d (aka sigma delta word) +    if (which == AUX_DAC_D){ +        boost::uint16_t dac_word = std::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); +        _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); +        _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); +        this->send_reg(42); +        this->send_reg(43); +        return; +    } + +    //calculate the dac word for aux dac a, b, c +    boost::uint8_t dac_word = std::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + +    //setup a lookup table for the aux dac params (reg ref, reg addr) +    typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; +    uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of +        (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) +        (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) +        (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) +    ; + +    //set the aux dac register +    UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); +    boost::uint8_t *reg_ref, reg_addr; +    boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; +    *reg_ref = dac_word; +    this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void usrp_e100_codec_ctrl_impl::send_reg(boost::uint8_t addr){ +    boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); +    if (codec_debug) std::cout << "codec control write reg: " << std::hex << reg << std::endl; +    _iface->transact_spi( +        UE_SPI_SS_AD9862, +        spi_config_t::EDGE_RISE, +        reg, 16, false /*no rb*/ +    ); +} + +void usrp_e100_codec_ctrl_impl::recv_reg(boost::uint8_t addr){ +    boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); +    if (codec_debug) std::cout << "codec control read reg: " << std::hex << reg << std::endl; +    boost::uint32_t ret = _iface->transact_spi( +        UE_SPI_SS_AD9862, +        spi_config_t::EDGE_RISE, +        reg, 16, true /*rb*/ +    ); +    if (codec_debug) std::cout << "codec control read ret: " << std::hex << ret << std::endl; +    _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +usrp_e100_codec_ctrl::sptr usrp_e100_codec_ctrl::make(usrp_e100_iface::sptr iface){ +    return sptr(new usrp_e100_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp_e100/codec_ctrl.hpp b/host/lib/usrp/usrp_e100/codec_ctrl.hpp new file mode 100644 index 000000000..74ce9bd9a --- /dev/null +++ b/host/lib/usrp/usrp_e100/codec_ctrl.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_CODEC_CTRL_HPP +#define INCLUDED_USRP_E100_CODEC_CTRL_HPP + +#include "usrp_e100_iface.hpp" +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp-e codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class usrp_e100_codec_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp_e100_codec_ctrl> sptr; + +    static const uhd::gain_range_t tx_pga_gain_range; +    static const uhd::gain_range_t rx_pga_gain_range; + +    /*! +     * Make a new codec control object. +     * \param iface the usrp_e100 iface object +     * \return the codec control object +     */ +    static sptr make(usrp_e100_iface::sptr iface); + +    //! aux adc identifier constants +    enum aux_adc_t{ +        AUX_ADC_A2 = 0xA2, +        AUX_ADC_A1 = 0xA1, +        AUX_ADC_B2 = 0xB2, +        AUX_ADC_B1 = 0xB1 +    }; + +    /*! +     * Read an auxiliary adc: +     * The internals remember which aux adc was read last. +     * Therefore, the aux adc switch is only changed as needed. +     * \param which which of the 4 adcs +     * \return a value in volts +     */ +    virtual float read_aux_adc(aux_adc_t which) = 0; + +    //! aux dac identifier constants +    enum aux_dac_t{ +        AUX_DAC_A = 0xA, +        AUX_DAC_B = 0xB, +        AUX_DAC_C = 0xC, +        AUX_DAC_D = 0xD //really the sigma delta output +    }; + +    /*! +     * Write an auxiliary dac. +     * \param which which of the 4 dacs +     * \param volts the level in in volts +     */ +    virtual void write_aux_dac(aux_dac_t which, float volts) = 0; + +    //! Set the TX PGA gain +    virtual void set_tx_pga_gain(float gain) = 0; + +    //! Get the TX PGA gain +    virtual float get_tx_pga_gain(void) = 0; + +    //! Set the RX PGA gain ('A' or 'B') +    virtual void set_rx_pga_gain(float gain, char which) = 0; + +    //! Get the RX PGA gain ('A' or 'B') +    virtual float get_rx_pga_gain(char which) = 0; +}; + +#endif /* INCLUDED_USRP_E100_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp_e100/codec_impl.cpp b/host/lib/usrp/usrp_e100/codec_impl.cpp new file mode 100644 index 000000000..6fd44bad3 --- /dev/null +++ b/host/lib/usrp/usrp_e100/codec_impl.cpp @@ -0,0 +1,149 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/codec_props.hpp> +#include <boost/bind.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Helper Methods + **********************************************************************/ +void usrp_e100_impl::codec_init(void){ +    //make proxies +    _rx_codec_proxy = wax_obj_proxy::make( +        boost::bind(&usrp_e100_impl::rx_codec_get, this, _1, _2), +        boost::bind(&usrp_e100_impl::rx_codec_set, this, _1, _2) +    ); +    _tx_codec_proxy = wax_obj_proxy::make( +        boost::bind(&usrp_e100_impl::tx_codec_get, this, _1, _2), +        boost::bind(&usrp_e100_impl::tx_codec_set, this, _1, _2) +    ); +} + +/*********************************************************************** + * RX Codec Properties + **********************************************************************/ +static const std::string ad9862_pga_gain_name = "ad9862 pga"; + +void usrp_e100_impl::rx_codec_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<codec_prop_t>()){ +    case CODEC_PROP_NAME: +        val = std::string("usrp-e adc - ad9522"); +        return; + +    case CODEC_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case CODEC_PROP_GAIN_NAMES: +        val = prop_names_t(1, ad9862_pga_gain_name); +        return; + +    case CODEC_PROP_GAIN_RANGE: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        val = usrp_e100_codec_ctrl::rx_pga_gain_range; +        return; + +    case CODEC_PROP_GAIN_I: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        val = _codec_ctrl->get_rx_pga_gain('A'); +        return; + +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        val = _codec_ctrl->get_rx_pga_gain('B'); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp_e100_impl::rx_codec_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the set request conditioned on the key +    switch(key.as<codec_prop_t>()){ +    case CODEC_PROP_GAIN_I: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        _codec_ctrl->set_rx_pga_gain(val.as<float>(), 'A'); +        return; + +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        _codec_ctrl->set_rx_pga_gain(val.as<float>(), 'B'); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Codec Properties + **********************************************************************/ +void usrp_e100_impl::tx_codec_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<codec_prop_t>()){ +    case CODEC_PROP_NAME: +        val = std::string("usrp-e dac - ad9522"); +        return; + +    case CODEC_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case CODEC_PROP_GAIN_NAMES: +        val = prop_names_t(1, ad9862_pga_gain_name); +        return; + +    case CODEC_PROP_GAIN_RANGE: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        val = usrp_e100_codec_ctrl::tx_pga_gain_range; +        return; + +    case CODEC_PROP_GAIN_I: //only one gain for I and Q +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        val = _codec_ctrl->get_tx_pga_gain(); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void usrp_e100_impl::tx_codec_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the set request conditioned on the key +    switch(key.as<codec_prop_t>()){ +    case CODEC_PROP_GAIN_I: //only one gain for I and Q +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        _codec_ctrl->set_tx_pga_gain(val.as<float>()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp_e100/dboard_iface.cpp b/host/lib/usrp/usrp_e100/dboard_iface.cpp new file mode 100644 index 000000000..a5032f86f --- /dev/null +++ b/host/lib/usrp/usrp_e100/dboard_iface.cpp @@ -0,0 +1,298 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_iface.hpp" +#include "usrp_e100_regs.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/assign/list_of.hpp> +#include <linux/usrp_e.h> //i2c and spi constants + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class usrp_e100_dboard_iface : public dboard_iface{ +public: + +    usrp_e100_dboard_iface( +        usrp_e100_iface::sptr iface, +        usrp_e100_clock_ctrl::sptr clock, +        usrp_e100_codec_ctrl::sptr codec +    ){ +        _iface = iface; +        _clock = clock; +        _codec = codec; + +        //init the clock rate shadows +        this->set_clock_rate(UNIT_RX, _clock->get_fpga_clock_rate()); +        this->set_clock_rate(UNIT_TX, _clock->get_fpga_clock_rate()); + +        _iface->poke16(UE_REG_GPIO_RX_DBG, 0); +        _iface->poke16(UE_REG_GPIO_TX_DBG, 0); +    } + +    ~usrp_e100_dboard_iface(void){ +        /* NOP */ +    } + +    special_props_t get_special_props(void){ +        special_props_t props; +        props.soft_clock_divider = false; +        props.mangle_i2c_addrs = false; +        return props; +    } + +    void write_aux_dac(unit_t, aux_dac_t, float); +    float read_aux_adc(unit_t, aux_adc_t); + +    void _set_pin_ctrl(unit_t, boost::uint16_t); +    void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); +    void _set_gpio_ddr(unit_t, boost::uint16_t); +    void _set_gpio_out(unit_t, boost::uint16_t); +    void set_gpio_debug(unit_t, int); +    boost::uint16_t read_gpio(unit_t); + +    void write_i2c(boost::uint8_t, const byte_vector_t &); +    byte_vector_t read_i2c(boost::uint8_t, size_t); + +    void write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    boost::uint32_t read_write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    void set_clock_rate(unit_t, double); +    std::vector<double> get_clock_rates(unit_t); +    double get_clock_rate(unit_t); +    void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t); + +private: +    usrp_e100_iface::sptr _iface; +    usrp_e100_clock_ctrl::sptr _clock; +    usrp_e100_codec_ctrl::sptr _codec; +    uhd::dict<unit_t, double> _clock_rates; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_usrp_e100_dboard_iface( +    usrp_e100_iface::sptr iface, +    usrp_e100_clock_ctrl::sptr clock, +    usrp_e100_codec_ctrl::sptr codec +){ +    return dboard_iface::sptr(new usrp_e100_dboard_iface(iface, clock, codec)); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +void usrp_e100_dboard_iface::set_clock_rate(unit_t unit, double rate){ +    _clock_rates[unit] = rate; +    switch(unit){ +    case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); +    case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); +    } +} + +std::vector<double> usrp_e100_dboard_iface::get_clock_rates(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _clock->get_rx_dboard_clock_rates(); +    case UNIT_TX: return _clock->get_tx_dboard_clock_rates(); +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +double usrp_e100_dboard_iface::get_clock_rate(unit_t unit){ +    return _clock_rates[unit]; +} + +void usrp_e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ +    switch(unit){ +    case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); +    case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); +    } +} + +double usrp_e100_dboard_iface::get_codec_rate(unit_t){ +    return _clock->get_fpga_clock_rate(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void usrp_e100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ +    UHD_ASSERT_THROW(GPIO_SEL_ATR == 1); //make this assumption +    switch(unit){ +    case UNIT_RX: _iface->poke16(UE_REG_GPIO_RX_SEL, value); return; +    case UNIT_TX: _iface->poke16(UE_REG_GPIO_TX_SEL, value); return; +    } +} + +void usrp_e100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ +    switch(unit){ +    case UNIT_RX: _iface->poke16(UE_REG_GPIO_RX_DDR, value); return; +    case UNIT_TX: _iface->poke16(UE_REG_GPIO_TX_DDR, value); return; +    } +} + +void usrp_e100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ +    switch(unit){ +    case UNIT_RX: _iface->poke16(UE_REG_GPIO_RX_IO, value); return; +    case UNIT_TX: _iface->poke16(UE_REG_GPIO_TX_IO, value); return; +    } +} + +boost::uint16_t usrp_e100_dboard_iface::read_gpio(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _iface->peek16(UE_REG_GPIO_RX_IO); +    case UNIT_TX: return _iface->peek16(UE_REG_GPIO_TX_IO); +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +void usrp_e100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ +    //define mapping of unit to atr regs to register address +    static const uhd::dict< +        unit_t, uhd::dict<atr_reg_t, boost::uint32_t> +    > unit_to_atr_to_addr = map_list_of +        (UNIT_RX, map_list_of +            (ATR_REG_IDLE,        UE_REG_ATR_IDLE_RXSIDE) +            (ATR_REG_TX_ONLY,     UE_REG_ATR_INTX_RXSIDE) +            (ATR_REG_RX_ONLY,     UE_REG_ATR_INRX_RXSIDE) +            (ATR_REG_FULL_DUPLEX, UE_REG_ATR_FULL_RXSIDE) +        ) +        (UNIT_TX, map_list_of +            (ATR_REG_IDLE,        UE_REG_ATR_IDLE_TXSIDE) +            (ATR_REG_TX_ONLY,     UE_REG_ATR_INTX_TXSIDE) +            (ATR_REG_RX_ONLY,     UE_REG_ATR_INRX_TXSIDE) +            (ATR_REG_FULL_DUPLEX, UE_REG_ATR_FULL_TXSIDE) +        ) +    ; +    _iface->poke16(unit_to_atr_to_addr[unit][atr], value); +} + +void usrp_e100_dboard_iface::set_gpio_debug(unit_t unit, int which){ +    //set this unit to all outputs +    this->set_gpio_ddr(unit, 0xffff); + +    //calculate the debug selections +    boost::uint32_t dbg_sels = 0x0; +    int sel = (which == 0)? GPIO_SEL_DEBUG_0 : GPIO_SEL_DEBUG_1; +    for(size_t i = 0; i < 16; i++) dbg_sels |= sel << i; + +    //set the debug on and which debug selection +    switch(unit){ +    case UNIT_RX: +        _iface->poke16(UE_REG_GPIO_RX_DBG, 0xffff); +        _iface->poke16(UE_REG_GPIO_RX_SEL, dbg_sels); +        return; + +    case UNIT_TX: +        _iface->poke16(UE_REG_GPIO_TX_DBG, 0xffff); +        _iface->poke16(UE_REG_GPIO_TX_SEL, dbg_sels); +        return; +    } +} + +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ +    switch(unit){ +    case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB; +    case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB; +    } +    throw std::invalid_argument("unknown unit type"); +} + +void usrp_e100_dboard_iface::write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    _iface->transact_spi(unit_to_otw_spi_dev(unit), config, data, num_bits, false /*no rb*/); +} + +boost::uint32_t usrp_e100_dboard_iface::read_write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    return _iface->transact_spi(unit_to_otw_spi_dev(unit), config, data, num_bits, true /*rb*/); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp_e100_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +    return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp_e100_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ +    return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp_e100_dboard_iface::write_aux_dac(dboard_iface::unit_t, aux_dac_t which, float value){ +    //same aux dacs for each unit +    static const uhd::dict<aux_dac_t, usrp_e100_codec_ctrl::aux_dac_t> which_to_aux_dac = map_list_of +        (AUX_DAC_A, usrp_e100_codec_ctrl::AUX_DAC_A) +        (AUX_DAC_B, usrp_e100_codec_ctrl::AUX_DAC_B) +        (AUX_DAC_C, usrp_e100_codec_ctrl::AUX_DAC_C) +        (AUX_DAC_D, usrp_e100_codec_ctrl::AUX_DAC_D) +    ; +    _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +float usrp_e100_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, aux_adc_t which){ +    static const uhd::dict< +        unit_t, uhd::dict<aux_adc_t, usrp_e100_codec_ctrl::aux_adc_t> +    > unit_to_which_to_aux_adc = map_list_of +        (UNIT_RX, map_list_of +            (AUX_ADC_A, usrp_e100_codec_ctrl::AUX_ADC_A1) +            (AUX_ADC_B, usrp_e100_codec_ctrl::AUX_ADC_B1) +        ) +        (UNIT_TX, map_list_of +            (AUX_ADC_A, usrp_e100_codec_ctrl::AUX_ADC_A2) +            (AUX_ADC_B, usrp_e100_codec_ctrl::AUX_ADC_B2) +        ) +    ; +    return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/usrp_e100/dboard_impl.cpp b/host/lib/usrp/usrp_e100/dboard_impl.cpp new file mode 100644 index 000000000..9f2bfb8ae --- /dev/null +++ b/host/lib/usrp/usrp_e100/dboard_impl.cpp @@ -0,0 +1,172 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/misc_utils.hpp> +#include <boost/bind.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Dboard Initialization + **********************************************************************/ +void usrp_e100_impl::dboard_init(void){ +    _rx_db_eeprom = dboard_eeprom_t(_iface->read_eeprom(I2C_ADDR_RX_DB, 0, dboard_eeprom_t::num_bytes())); +    _tx_db_eeprom = dboard_eeprom_t(_iface->read_eeprom(I2C_ADDR_TX_DB, 0, dboard_eeprom_t::num_bytes())); + +    //create a new dboard interface and manager +    _dboard_iface = make_usrp_e100_dboard_iface( +        _iface, _clock_ctrl, _codec_ctrl +    ); +    _dboard_manager = dboard_manager::make( +        _rx_db_eeprom.id, _tx_db_eeprom.id, _dboard_iface +    ); + +    //setup the dboard proxies +    _rx_dboard_proxy = wax_obj_proxy::make( +        boost::bind(&usrp_e100_impl::rx_dboard_get, this, _1, _2), +        boost::bind(&usrp_e100_impl::rx_dboard_set, this, _1, _2) +    ); +    _tx_dboard_proxy = wax_obj_proxy::make( +        boost::bind(&usrp_e100_impl::tx_dboard_get, this, _1, _2), +        boost::bind(&usrp_e100_impl::tx_dboard_set, this, _1, _2) +    ); +} + +/*********************************************************************** + * RX Dboard Get + **********************************************************************/ +void usrp_e100_impl::rx_dboard_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_NAME: +        val = std::string("usrp-e dboard (rx unit)"); +        return; + +    case DBOARD_PROP_SUBDEV: +        val = _dboard_manager->get_rx_subdev(key.name); +        return; + +    case DBOARD_PROP_SUBDEV_NAMES: +        val = _dboard_manager->get_rx_subdev_names(); +        return; + +    case DBOARD_PROP_DBOARD_ID: +        val = _rx_db_eeprom.id; +        return; + +    case DBOARD_PROP_DBOARD_IFACE: +        val = _dboard_iface; +        return; + +    case DBOARD_PROP_CODEC: +        val = _rx_codec_proxy->get_link(); +        return; + +    case DBOARD_PROP_GAIN_GROUP: +        val = make_gain_group( +            _rx_db_eeprom.id, +            _dboard_manager->get_rx_subdev(key.name), +            _rx_codec_proxy->get_link(), +            GAIN_GROUP_POLICY_RX +        ); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * RX Dboard Set + **********************************************************************/ +void usrp_e100_impl::rx_dboard_set(const wax::obj &key, const wax::obj &val){ +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_DBOARD_ID: +        _rx_db_eeprom.id = val.as<dboard_id_t>(); +        _iface->write_eeprom(I2C_ADDR_RX_DB, 0, _rx_db_eeprom.get_eeprom_bytes()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Dboard Get + **********************************************************************/ +void usrp_e100_impl::tx_dboard_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_NAME: +        val = std::string("usrp-e dboard (tx unit)"); +        return; + +    case DBOARD_PROP_SUBDEV: +        val = _dboard_manager->get_tx_subdev(key.name); +        return; + +    case DBOARD_PROP_SUBDEV_NAMES: +        val = _dboard_manager->get_tx_subdev_names(); +        return; + +    case DBOARD_PROP_DBOARD_ID: +        val = _tx_db_eeprom.id; +        return; + +    case DBOARD_PROP_DBOARD_IFACE: +        val = _dboard_iface; +        return; + +    case DBOARD_PROP_CODEC: +        val = _tx_codec_proxy->get_link(); +        return; + +    case DBOARD_PROP_GAIN_GROUP: +        val = make_gain_group( +            _tx_db_eeprom.id, +            _dboard_manager->get_tx_subdev(key.name), +            _tx_codec_proxy->get_link(), +            GAIN_GROUP_POLICY_TX +        ); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * TX Dboard Set + **********************************************************************/ +void usrp_e100_impl::tx_dboard_set(const wax::obj &key, const wax::obj &val){ +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_DBOARD_ID: +        _tx_db_eeprom.id = val.as<dboard_id_t>(); +        _iface->write_eeprom(I2C_ADDR_TX_DB, 0, _tx_db_eeprom.get_eeprom_bytes()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp_e100/dsp_impl.cpp b/host/lib/usrp/usrp_e100/dsp_impl.cpp new file mode 100644 index 000000000..43a3bd3be --- /dev/null +++ b/host/lib/usrp/usrp_e100/dsp_impl.cpp @@ -0,0 +1,192 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/bind.hpp> + +#define rint boost::math::iround + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * RX DDC Initialization + **********************************************************************/ +void usrp_e100_impl::rx_ddc_init(void){ +    _rx_ddc_proxy = wax_obj_proxy::make( +        boost::bind(&usrp_e100_impl::rx_ddc_get, this, _1, _2), +        boost::bind(&usrp_e100_impl::rx_ddc_set, this, _1, _2) +    ); + +    //initial config and update +    rx_ddc_set(DSP_PROP_FREQ_SHIFT, double(0)); +    rx_ddc_set(DSP_PROP_HOST_RATE, double(64e6/10)); +} + +/*********************************************************************** + * RX DDC Get + **********************************************************************/ +void usrp_e100_impl::rx_ddc_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ +    case DSP_PROP_NAME: +        val = std::string("usrp-e ddc0"); +        return; + +    case DSP_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case DSP_PROP_FREQ_SHIFT: +        val = _ddc_freq; +        return; + +    case DSP_PROP_FREQ_SHIFT_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case DSP_PROP_CODEC_RATE: +        val = _clock_ctrl->get_fpga_clock_rate(); +        return; + +    case DSP_PROP_HOST_RATE: +        val = _clock_ctrl->get_fpga_clock_rate()/_ddc_decim; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * RX DDC Set + **********************************************************************/ +void usrp_e100_impl::rx_ddc_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ + +    case DSP_PROP_FREQ_SHIFT:{ +            double new_freq = val.as<double>(); +            _iface->poke32(UE_REG_DSP_RX_FREQ, +                dsp_type1::calc_cordic_word_and_update(new_freq, _clock_ctrl->get_fpga_clock_rate()) +            ); +            _ddc_freq = new_freq; //shadow +        } +        return; + +    case DSP_PROP_HOST_RATE:{ +            //set the decimation +            _ddc_decim = rint(_clock_ctrl->get_fpga_clock_rate()/val.as<double>()); +            _iface->poke32(UE_REG_DSP_RX_DECIM_RATE, dsp_type1::calc_cic_filter_word(_ddc_decim)); + +            //set the scaling +            static const boost::int16_t default_rx_scale_iq = 1024; +            _iface->poke32(UE_REG_DSP_RX_SCALE_IQ, +                dsp_type1::calc_iq_scale_word(default_rx_scale_iq, default_rx_scale_iq) +            ); +        } +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX DUC Initialization + **********************************************************************/ +void usrp_e100_impl::tx_duc_init(void){ +    _tx_duc_proxy = wax_obj_proxy::make( +        boost::bind(&usrp_e100_impl::tx_duc_get, this, _1, _2), +        boost::bind(&usrp_e100_impl::tx_duc_set, this, _1, _2) +    ); + +    //initial config and update +    tx_duc_set(DSP_PROP_FREQ_SHIFT, double(0)); +    tx_duc_set(DSP_PROP_HOST_RATE, double(64e6/10)); +} + +/*********************************************************************** + * TX DUC Get + **********************************************************************/ +void usrp_e100_impl::tx_duc_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ +    case DSP_PROP_NAME: +        val = std::string("usrp-e duc0"); +        return; + +    case DSP_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case DSP_PROP_FREQ_SHIFT: +        val = _duc_freq; +        return; + +    case DSP_PROP_FREQ_SHIFT_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case DSP_PROP_CODEC_RATE: +        val = _clock_ctrl->get_fpga_clock_rate(); +        return; + +    case DSP_PROP_HOST_RATE: +        val = _clock_ctrl->get_fpga_clock_rate()/_duc_interp; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * TX DUC Set + **********************************************************************/ +void usrp_e100_impl::tx_duc_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    switch(key.as<dsp_prop_t>()){ + +    case DSP_PROP_FREQ_SHIFT:{ +            double new_freq = val.as<double>(); +            _iface->poke32(UE_REG_DSP_TX_FREQ, +                dsp_type1::calc_cordic_word_and_update(new_freq, _clock_ctrl->get_fpga_clock_rate()) +            ); +            _duc_freq = new_freq; //shadow +        } +        return; + +    case DSP_PROP_HOST_RATE:{ +            _duc_interp = rint(_clock_ctrl->get_fpga_clock_rate()/val.as<double>()); + +            //set the interpolation +            _iface->poke32(UE_REG_DSP_TX_INTERP_RATE, dsp_type1::calc_cic_filter_word(_duc_interp)); + +            //set the scaling +            _iface->poke32(UE_REG_DSP_TX_SCALE_IQ, dsp_type1::calc_iq_scale_word(_duc_interp)); +        } +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp_e100/fpga-downloader.cc b/host/lib/usrp/usrp_e100/fpga-downloader.cc new file mode 100644 index 000000000..4a3d3b9af --- /dev/null +++ b/host/lib/usrp/usrp_e100/fpga-downloader.cc @@ -0,0 +1,274 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#include <uhd/utils/assert.hpp> + +#include <iostream> +#include <sstream> +#include <fstream> +#include <string> +#include <cstdlib> +#include <stdexcept> + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <linux/spi/spidev.h> + +/* + * Configuration connections + * + * CCK    - MCSPI1_CLK + * DIN    - MCSPI1_MOSI + * PROG_B - GPIO_175     - output (change mux) + * DONE   - GPIO_173     - input  (change mux) + * INIT_B - GPIO_114     - input  (change mux) + * +*/ + +const unsigned int PROG_B = 175; +const unsigned int DONE   = 173; +const unsigned int INIT_B = 114; + +//static std::string bit_file = "safe_u1e.bin"; + +const int BUF_SIZE = 4096; + +enum gpio_direction {IN, OUT}; + +class gpio { +	public: + +	gpio(unsigned int gpio_num, gpio_direction pin_direction); + +	bool get_value(); +	void set_value(bool state); + +	private: + +	std::stringstream base_path; +	std::fstream value_file;	 +}; + +class spidev { +	public: + +	spidev(std::string dev_name); +	~spidev(); + +	void send(char *wbuf, char *rbuf, unsigned int nbytes); + +	private: + +	int fd; + +}; + +gpio::gpio(unsigned int gpio_num, gpio_direction pin_direction) +{ +	std::fstream export_file; + +	export_file.open("/sys/class/gpio/export", std::ios::out); +	if (not export_file.is_open()) throw std::runtime_error( +		"Failed to open gpio export file." +	); + +	export_file << gpio_num << std::endl; + +	base_path << "/sys/class/gpio/gpio" << gpio_num << std::flush; + +	std::fstream direction_file; +	std::string direction_file_name; + +	if (gpio_num != 114) { +		direction_file_name = base_path.str() + "/direction"; + +		direction_file.open(direction_file_name.c_str()); +		if (!direction_file.is_open()) +			std::cout << "Failed to open direction file." << std::endl; +		if (pin_direction == OUT) +			direction_file << "out" << std::endl; +		else +			direction_file << "in" << std::endl; +	} + +	std::string value_file_name; + +	value_file_name = base_path.str() + "/value"; + +	value_file.open(value_file_name.c_str(), std::ios_base::in | std::ios_base::out); +	if (!value_file.is_open()) +		std::cout << "Failed to open value file." << std::endl; +} + +bool gpio::get_value() +{ + +	std::string val; + +	std::getline(value_file, val); +	value_file.seekg(0); + +	if (val == "0") +		return false; +	else if (val == "1") +		return true; +	else +		std::cout << "Data read from value file|" << val << "|" << std::endl; + +	return false; +} + +void gpio::set_value(bool state) +{ + +	if (state) +		value_file << "1" << std::endl; +	else +		value_file << "0" << std::endl; +} + +static void prepare_fpga_for_configuration(gpio &prog, gpio &)//init) +{ + +	prog.set_value(true); +	prog.set_value(false); +	prog.set_value(true); + +#if 0 +	bool ready_to_program(false); +	unsigned int count(0); +	do { +		ready_to_program = init.get_value(); +		count++; + +		sleep(1); +	} while (count < 10 && !ready_to_program); + +	if (count == 10) { +		std::cout << "FPGA not ready for programming." << std::endl; +		exit(-1); +	} +#endif +} + +spidev::spidev(std::string fname) +{ +	int ret; +	int mode = 0; +	int speed = 12000000; +	int bits = 8; + +	fd = open(fname.c_str(), O_RDWR); + +	ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); +	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); +	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); +} +	 + +spidev::~spidev() +{ +	close(fd); +} + +void spidev::send(char *buf, char *rbuf, unsigned int nbytes) +{ +	int ret; + +	struct spi_ioc_transfer tr; +	tr.tx_buf = (unsigned long) buf; +	tr.rx_buf = (unsigned long) rbuf; +	tr.len = nbytes; +	tr.delay_usecs = 0; +	tr.speed_hz = 48000000; +	tr.bits_per_word = 8; + +	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);	 + +} + +static void send_file_to_fpga(const std::string &file_name, gpio &error, gpio &done) +{ +	std::ifstream bitstream; + +	std::cout << "File name - " << file_name.c_str() << std::endl; + +	bitstream.open(file_name.c_str(), std::ios::binary); +	if (!bitstream.is_open()) +		std::cout << "File " << file_name << " not opened succesfully." << std::endl; + +	spidev spi("/dev/spidev1.0"); +	char buf[BUF_SIZE]; +	char rbuf[BUF_SIZE]; + +	do { +		bitstream.read(buf, BUF_SIZE); +		spi.send(buf, rbuf, bitstream.gcount()); + +		if (error.get_value()) +			std::cout << "INIT_B went high, error occured." << std::endl; + +		if (!done.get_value()) +			std::cout << "Configuration complete." << std::endl; + +	} while (bitstream.gcount() == BUF_SIZE); +} + +/* +int main(int argc, char *argv[]) +{ + +	gpio gpio_prog_b(PROG_B, OUT); +	gpio gpio_init_b(INIT_B, IN); +	gpio gpio_done  (DONE,   IN); + +	if (argc == 2) +		bit_file = argv[1]; + +	std::cout << "FPGA config file: " << bit_file << std::endl; + +	prepare_fpga_for_configuration(gpio_prog_b, gpio_init_b); + +	std::cout << "Done = " << gpio_done.get_value() << std::endl; + +	send_file_to_fpga(bit_file, gpio_init_b, gpio_done); +} +*/ + +void usrp_e100_load_fpga(const std::string &bin_file){ +	gpio gpio_prog_b(PROG_B, OUT); +	gpio gpio_init_b(INIT_B, IN); +	gpio gpio_done  (DONE,   IN); + +	std::cout << "Loading FPGA image: " << bin_file << "... " << std::flush; + +	UHD_ASSERT_THROW(std::system("/sbin/rmmod usrp_e") == 0); + +	prepare_fpga_for_configuration(gpio_prog_b, gpio_init_b); + +	std::cout << "done = " << gpio_done.get_value() << std::endl; + +	send_file_to_fpga(bin_file, gpio_init_b, gpio_done); + +	UHD_ASSERT_THROW(std::system("/sbin/modprobe usrp_e") == 0); + +} + diff --git a/host/lib/usrp/usrp_e100/include/linux/usrp_e.h b/host/lib/usrp/usrp_e100/include/linux/usrp_e.h new file mode 100644 index 000000000..4c6a5dd89 --- /dev/null +++ b/host/lib/usrp/usrp_e100/include/linux/usrp_e.h @@ -0,0 +1,91 @@ + +/* + *  Copyright (C) 2010 Ettus Research, LLC + * + *  Written by Philip Balister <philip@opensdr.com> + * + *  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 2 of the License, or + *  (at your option) any later version. + */ + +#ifndef __USRP_E_H +#define __USRP_E_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +struct usrp_e_ctl16 { +	__u32 offset; +	__u32 count; +	__u16 buf[20]; +}; + +struct usrp_e_ctl32 { +	__u32 offset; +	__u32 count; +	__u32 buf[10]; +}; + +/* SPI interface */ + +#define UE_SPI_TXONLY	0 +#define UE_SPI_TXRX	1 + +/* Defines for spi ctrl register */ +#define UE_SPI_CTRL_TXNEG	(1<<10) +#define UE_SPI_CTRL_RXNEG	(1<<9) + +#define UE_SPI_PUSH_RISE	0 +#define UE_SPI_PUSH_FALL	UE_SPI_CTRL_TXNEG +#define UE_SPI_LATCH_RISE	0 +#define UE_SPI_LATCH_FALL	UE_SPI_CTRL_RXNEG + +struct usrp_e_spi { +	__u8 readback; +	__u32 slave; +	__u32 data; +	__u32 length; +	__u32 flags; +}; + +struct usrp_e_i2c { +	__u8 addr; +	__u32 len; +	__u8 data[]; +}; + +#define USRP_E_IOC_MAGIC	'u' +#define USRP_E_WRITE_CTL16	_IOW(USRP_E_IOC_MAGIC, 0x20, struct usrp_e_ctl16) +#define USRP_E_READ_CTL16	_IOWR(USRP_E_IOC_MAGIC, 0x21, struct usrp_e_ctl16) +#define USRP_E_WRITE_CTL32	_IOW(USRP_E_IOC_MAGIC, 0x22, struct usrp_e_ctl32) +#define USRP_E_READ_CTL32	_IOWR(USRP_E_IOC_MAGIC, 0x23, struct usrp_e_ctl32) +#define USRP_E_SPI		_IOWR(USRP_E_IOC_MAGIC, 0x24, struct usrp_e_spi) +#define USRP_E_I2C_READ		_IOWR(USRP_E_IOC_MAGIC, 0x25, struct usrp_e_i2c) +#define USRP_E_I2C_WRITE	_IOW(USRP_E_IOC_MAGIC, 0x26, struct usrp_e_i2c) +#define USRP_E_GET_RB_INFO      _IOR(USRP_E_IOC_MAGIC, 0x27, struct usrp_e_ring_buffer_size_t) +#define USRP_E_GET_COMPAT_NUMBER _IO(USRP_E_IOC_MAGIC, 0x28) + +#define USRP_E_COMPAT_NUMBER 1 + +/* Flag defines */ +#define RB_USER (1<<0) +#define RB_KERNEL (1<<1) +#define RB_OVERRUN (1<<2) +#define RB_DMA_ACTIVE (1<<3) +#define RB_USER_PROCESS (1<<4) + +struct ring_buffer_info { +	int flags; +	int len; +}; + +struct usrp_e_ring_buffer_size_t { +	int num_pages_rx_flags; +	int num_rx_frames; +	int num_pages_tx_flags; +	int num_tx_frames; +}; + +#endif diff --git a/host/lib/usrp/usrp_e100/io_impl.cpp b/host/lib/usrp/usrp_e100/io_impl.cpp new file mode 100644 index 000000000..2388482c7 --- /dev/null +++ b/host/lib/usrp/usrp_e100/io_impl.cpp @@ -0,0 +1,270 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include "../../transport/vrt_packet_handler.hpp" +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +zero_copy_if::sptr usrp_e100_make_mmap_zero_copy(usrp_e100_iface::sptr iface); + +/*********************************************************************** + * Constants + **********************************************************************/ +static const size_t tx_async_report_sid = 1; +static const int underflow_flags = async_metadata_t::EVENT_CODE_UNDERFLOW | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET; +static const bool recv_debug = false; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew of 1 + * - bounded buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct usrp_e100_impl::io_impl{ +    //state management for the vrt packet handler code +    vrt_packet_handler::recv_state packet_handler_recv_state; +    vrt_packet_handler::send_state packet_handler_send_state; +    zero_copy_if::sptr data_xport; +    bool continuous_streaming; +    io_impl(usrp_e100_iface::sptr iface): +        data_xport(usrp_e100_make_mmap_zero_copy(iface)), +        recv_pirate_booty(recv_booty_type::make(data_xport->get_num_recv_frames())), +        async_msg_fifo(bounded_buffer<async_metadata_t>::make(100/*messages deep*/)) +    { +        /* NOP */ +    } + +    ~io_impl(void){ +        recv_pirate_crew_raiding = false; +        recv_pirate_crew.interrupt_all(); +        recv_pirate_crew.join_all(); +    } + +    bool get_recv_buffs(vrt_packet_handler::managed_recv_buffs_t &buffs, double timeout){ +        UHD_ASSERT_THROW(buffs.size() == 1); +        boost::this_thread::disable_interruption di; //disable because the wait can throw +        return recv_pirate_booty->pop_with_timed_wait(buffs.front(), timeout); +    } + +    //a pirate's life is the life for me! +    void recv_pirate_loop(usrp_e100_clock_ctrl::sptr); +    typedef bounded_buffer<managed_recv_buffer::sptr> recv_booty_type; +    recv_booty_type::sptr recv_pirate_booty; +    bounded_buffer<async_metadata_t>::sptr async_msg_fifo; +    boost::thread_group recv_pirate_crew; +    bool recv_pirate_crew_raiding; +}; + +/*********************************************************************** + * Receive Pirate Loop + * - while raiding, loot for recv buffers + * - put booty into the alignment buffer + **********************************************************************/ +void usrp_e100_impl::io_impl::recv_pirate_loop(usrp_e100_clock_ctrl::sptr clock_ctrl) +{ +    set_thread_priority_safe(); +    recv_pirate_crew_raiding = true; + +    while(recv_pirate_crew_raiding){ +        managed_recv_buffer::sptr buff = this->data_xport->get_recv_buff(); +        if (not buff.get()) continue; //ignore timeout/error buffers + +        if (recv_debug){ +            std::cout << "len " << buff->size() << std::endl; +            for (size_t i = 0; i < 9; i++){ +                std::cout << boost::format("    0x%08x") % buff->cast<const boost::uint32_t *>()[i] << std::endl; +            } +            std::cout << std::endl << std::endl; +        } + +        try{ +            //extract the vrt header packet info +            vrt::if_packet_info_t if_packet_info; +            if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); +            const boost::uint32_t *vrt_hdr = buff->cast<const boost::uint32_t *>(); +            vrt::if_hdr_unpack_le(vrt_hdr, if_packet_info); + +            //handle a tx async report message +            if (if_packet_info.sid == tx_async_report_sid and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + +                //fill in the async metadata +                async_metadata_t metadata; +                metadata.channel = 0; +                metadata.has_time_spec = if_packet_info.has_tsi and if_packet_info.has_tsf; +                metadata.time_spec = time_spec_t( +                    time_t(if_packet_info.tsi), size_t(if_packet_info.tsf), clock_ctrl->get_fpga_clock_rate() +                ); +                metadata.event_code = vrt_packet_handler::get_context_code<async_metadata_t::event_code_t>(vrt_hdr, if_packet_info); + +                //print the famous U, and push the metadata into the message queue +                if (metadata.event_code & underflow_flags) std::cerr << "U" << std::flush; +                async_msg_fifo->push_with_pop_on_full(metadata); +                continue; +            } + +            //same number of frames as the data transport -> always immediate +            recv_pirate_booty->push_with_wait(buff); + +        }catch(const std::exception &e){ +            std::cerr << "Error (usrp-e recv pirate loop): " << e.what() << std::endl; +        } +    } +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void usrp_e100_impl::io_init(void){ +    //setup otw types +    _send_otw_type.width = 16; +    _send_otw_type.shift = 0; +    _send_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + +    _recv_otw_type.width = 16; +    _recv_otw_type.shift = 0; +    _recv_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + +    //setup before the registers (transport called to calculate max spp) +    _io_impl = UHD_PIMPL_MAKE(io_impl, (_iface)); + +    //setup rx data path +    _iface->poke32(UE_REG_CTRL_RX_NSAMPS_PER_PKT, get_max_recv_samps_per_packet()); +    _iface->poke32(UE_REG_CTRL_RX_NCHANNELS, 1); +    _iface->poke32(UE_REG_CTRL_RX_CLEAR_OVERRUN, 1); //reset +    _iface->poke32(UE_REG_CTRL_RX_VRT_HEADER, 0 +        | (0x1 << 28) //if data with stream id +        | (0x1 << 26) //has trailer +        | (0x3 << 22) //integer time other +        | (0x1 << 20) //fractional time sample count +    ); +    _iface->poke32(UE_REG_CTRL_RX_VRT_STREAM_ID, 0); +    _iface->poke32(UE_REG_CTRL_RX_VRT_TRAILER, 0); + +    //setup the tx policy +    _iface->poke32(UE_REG_CTRL_TX_REPORT_SID, tx_async_report_sid); +    _iface->poke32(UE_REG_CTRL_TX_POLICY, UE_FLAG_CTRL_TX_POLICY_NEXT_PACKET); + +    //spawn a pirate, yarrr! +    _io_impl->recv_pirate_crew.create_thread(boost::bind( +        &usrp_e100_impl::io_impl::recv_pirate_loop, _io_impl.get(), _clock_ctrl +    )); +} + +void usrp_e100_impl::issue_stream_cmd(const stream_cmd_t &stream_cmd){ +    _io_impl->continuous_streaming = (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS); +    _iface->poke32(UE_REG_CTRL_RX_STREAM_CMD, dsp_type1::calc_stream_cmd_word(stream_cmd)); +    _iface->poke32(UE_REG_CTRL_RX_TIME_SECS,  boost::uint32_t(stream_cmd.time_spec.get_full_secs())); +    _iface->poke32(UE_REG_CTRL_RX_TIME_TICKS, stream_cmd.time_spec.get_tick_count(_clock_ctrl->get_fpga_clock_rate())); +} + +void usrp_e100_impl::handle_overrun(size_t){ +    std::cerr << "O"; //the famous OOOOOOOOOOO +    _iface->poke32(UE_REG_CTRL_RX_CLEAR_OVERRUN, 0); +    if (_io_impl->continuous_streaming){ +        this->issue_stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); +    } +} + +/*********************************************************************** + * Data Send + **********************************************************************/ +bool get_send_buffs( +    zero_copy_if::sptr trans, double timeout, +    vrt_packet_handler::managed_send_buffs_t &buffs +){ +    UHD_ASSERT_THROW(buffs.size() == 1); +    buffs[0] = trans->get_send_buff(timeout); +    return buffs[0].get() != NULL; +} + +size_t usrp_e100_impl::get_max_send_samps_per_packet(void) const{ +    static const size_t hdr_size = 0 +        + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +    ; +    size_t bpp = _io_impl->data_xport->get_send_frame_size() - hdr_size; +    return bpp/_send_otw_type.get_sample_size(); +} + +size_t usrp_e100_impl::send( +    const std::vector<const void *> &buffs, size_t num_samps, +    const tx_metadata_t &metadata, const io_type_t &io_type, +    send_mode_t send_mode, double timeout +){ +    return vrt_packet_handler::send( +        _io_impl->packet_handler_send_state,       //last state of the send handler +        buffs, num_samps,                          //buffer to fill +        metadata, send_mode,                       //samples metadata +        io_type, _send_otw_type,                   //input and output types to convert +        _clock_ctrl->get_fpga_clock_rate(),        //master clock tick rate +        uhd::transport::vrt::if_hdr_pack_le, +        boost::bind(&get_send_buffs, _io_impl->data_xport, timeout, _1), +        get_max_send_samps_per_packet() +    ); +} + +/*********************************************************************** + * Data Recv + **********************************************************************/ +size_t usrp_e100_impl::get_max_recv_samps_per_packet(void) const{ +    static const size_t hdr_size = 0 +        + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +        + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +    ; +    size_t bpp = _io_impl->data_xport->get_recv_frame_size() - hdr_size; +    return bpp/_recv_otw_type.get_sample_size(); +} + +size_t usrp_e100_impl::recv( +    const std::vector<void *> &buffs, size_t num_samps, +    rx_metadata_t &metadata, const io_type_t &io_type, +    recv_mode_t recv_mode, double timeout +){ +    return vrt_packet_handler::recv( +        _io_impl->packet_handler_recv_state,       //last state of the recv handler +        buffs, num_samps,                          //buffer to fill +        metadata, recv_mode,                       //samples metadata +        io_type, _recv_otw_type,                   //input and output types to convert +        _clock_ctrl->get_fpga_clock_rate(),        //master clock tick rate +        uhd::transport::vrt::if_hdr_unpack_le, +        boost::bind(&usrp_e100_impl::io_impl::get_recv_buffs, _io_impl.get(), _1, timeout), +        boost::bind(&usrp_e100_impl::handle_overrun, this, _1) +    ); +} + +/*********************************************************************** + * Async Recv + **********************************************************************/ +bool usrp_e100_impl::recv_async_msg( +    async_metadata_t &async_metadata, double timeout +){ +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return _io_impl->async_msg_fifo->pop_with_timed_wait(async_metadata, timeout); +} diff --git a/host/lib/usrp/usrp_e100/mboard_impl.cpp b/host/lib/usrp/usrp_e100/mboard_impl.cpp new file mode 100644 index 000000000..fe26cd63d --- /dev/null +++ b/host/lib/usrp/usrp_e100/mboard_impl.cpp @@ -0,0 +1,198 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <boost/bind.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Mboard Initialization + **********************************************************************/ +void usrp_e100_impl::mboard_init(void){ +    _mboard_proxy = wax_obj_proxy::make( +        boost::bind(&usrp_e100_impl::mboard_get, this, _1, _2), +        boost::bind(&usrp_e100_impl::mboard_set, this, _1, _2) +    ); + +    //init the clock config +    _clock_config.ref_source = clock_config_t::REF_AUTO; +    _clock_config.pps_source = clock_config_t::PPS_SMA; +    _clock_config.pps_polarity = clock_config_t::PPS_NEG; + +    update_clock_config(); +} + +void usrp_e100_impl::update_clock_config(void){ +    boost::uint32_t pps_flags = 0; + +    //translate pps polarity enums +    switch(_clock_config.pps_polarity){ +    case clock_config_t::PPS_POS: pps_flags |= UE_FLAG_TIME64_PPS_POSEDGE; break; +    case clock_config_t::PPS_NEG: pps_flags |= UE_FLAG_TIME64_PPS_NEGEDGE; break; +    default: throw std::runtime_error("unhandled clock configuration pps polarity"); +    } + +    //set the pps flags +    _iface->poke32(UE_REG_TIME64_FLAGS, pps_flags); + +    //clock source ref 10mhz +    switch(_clock_config.ref_source){ +    case clock_config_t::REF_AUTO: _clock_ctrl->use_auto_ref(); break; +    case clock_config_t::REF_INT: _clock_ctrl->use_internal_ref(); break; +    case clock_config_t::REF_SMA: _clock_ctrl->use_auto_ref(); break; +    default: throw std::runtime_error("unhandled clock configuration ref source"); +    } +} + +/*********************************************************************** + * Mboard Get + **********************************************************************/ +void usrp_e100_impl::mboard_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<mboard_prop_t>()){ +    case MBOARD_PROP_NAME: +        val = std::string("usrp-e mboard"); +        return; + +    case MBOARD_PROP_OTHERS: +        val = prop_names_t(); +        return; + +    case MBOARD_PROP_RX_DBOARD: +        UHD_ASSERT_THROW(key.name == ""); +        val = _rx_dboard_proxy->get_link(); +        return; + +    case MBOARD_PROP_RX_DBOARD_NAMES: +        val = prop_names_t(1, ""); //vector of size 1 with empty string +        return; + +    case MBOARD_PROP_TX_DBOARD: +        UHD_ASSERT_THROW(key.name == ""); +        val = _tx_dboard_proxy->get_link(); +        return; + +    case MBOARD_PROP_TX_DBOARD_NAMES: +        val = prop_names_t(1, ""); //vector of size 1 with empty string +        return; + +    case MBOARD_PROP_RX_DSP: +        UHD_ASSERT_THROW(key.name == ""); +        val = _rx_ddc_proxy->get_link(); +        return; + +    case MBOARD_PROP_RX_DSP_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case MBOARD_PROP_TX_DSP: +        UHD_ASSERT_THROW(key.name == ""); +        val = _tx_duc_proxy->get_link(); +        return; + +    case MBOARD_PROP_TX_DSP_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case MBOARD_PROP_CLOCK_CONFIG: +        val = _clock_config; +        return; + +    case MBOARD_PROP_RX_SUBDEV_SPEC: +        val = _rx_subdev_spec; +        return; + +    case MBOARD_PROP_TX_SUBDEV_SPEC: +        val = _tx_subdev_spec; +        return; + +    case MBOARD_PROP_EEPROM_MAP: +        val = _iface->mb_eeprom; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * Mboard Set + **********************************************************************/ +void usrp_e100_impl::mboard_set(const wax::obj &key, const wax::obj &val){ +    //handle the get request conditioned on the key +    switch(key.as<mboard_prop_t>()){ + +    case MBOARD_PROP_STREAM_CMD: +        issue_stream_cmd(val.as<stream_cmd_t>()); +        return; + +    case MBOARD_PROP_TIME_NOW: +    case MBOARD_PROP_TIME_NEXT_PPS:{ +            time_spec_t time_spec = val.as<time_spec_t>(); +            _iface->poke32(UE_REG_TIME64_TICKS, time_spec.get_tick_count(_clock_ctrl->get_fpga_clock_rate())); +            boost::uint32_t imm_flags = (key.as<mboard_prop_t>() == MBOARD_PROP_TIME_NOW)? 1 : 0; +            _iface->poke32(UE_REG_TIME64_IMM, imm_flags); +            _iface->poke32(UE_REG_TIME64_SECS, time_spec.get_full_secs()); +        } +        return; + +    case MBOARD_PROP_RX_SUBDEV_SPEC: +        _rx_subdev_spec = val.as<subdev_spec_t>(); +        verify_rx_subdev_spec(_rx_subdev_spec, _mboard_proxy->get_link()); +        //sanity check +        UHD_ASSERT_THROW(_rx_subdev_spec.size() == 1); +        //set the mux +        _iface->poke32(UE_REG_DSP_RX_MUX, dsp_type1::calc_rx_mux_word( +            _dboard_manager->get_rx_subdev(_rx_subdev_spec.front().sd_name)[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>() +        )); +        return; + +    case MBOARD_PROP_TX_SUBDEV_SPEC: +        _tx_subdev_spec = val.as<subdev_spec_t>(); +        verify_tx_subdev_spec(_tx_subdev_spec, _mboard_proxy->get_link()); +        //sanity check +        UHD_ASSERT_THROW(_tx_subdev_spec.size() == 1); +        //set the mux +        _iface->poke32(UE_REG_DSP_TX_MUX, dsp_type1::calc_tx_mux_word( +            _dboard_manager->get_tx_subdev(_tx_subdev_spec.front().sd_name)[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>() +        )); +        return; + +    case MBOARD_PROP_EEPROM_MAP: +        // Step1: commit the map, writing only those values set. +        // Step2: readback the entire eeprom map into the iface. +        val.as<mboard_eeprom_t>().commit(_iface->get_i2c_dev_iface(), mboard_eeprom_t::MAP_E100); +        _iface->mb_eeprom = mboard_eeprom_t(_iface->get_i2c_dev_iface(), mboard_eeprom_t::MAP_E100); +        return; +         +    case MBOARD_PROP_CLOCK_CONFIG: +        _clock_config = val.as<clock_config_t>(); +        update_clock_config(); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_iface.cpp b/host/lib/usrp/usrp_e100/usrp_e100_iface.cpp new file mode 100644 index 000000000..40c7afabb --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_iface.cpp @@ -0,0 +1,268 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_iface.hpp" +#include <uhd/utils/assert.hpp> +#include <sys/ioctl.h> //ioctl +#include <fcntl.h> //open, close +#include <linux/usrp_e.h> //ioctl structures and constants +#include <boost/format.hpp> +#include <boost/thread.hpp> //mutex +#include <linux/i2c-dev.h> +#include <linux/i2c.h> +#include <stdexcept> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * I2C device node implementation wrapper + **********************************************************************/ +class i2c_dev_iface : public i2c_iface{ +public: +    i2c_dev_iface(const std::string &node){ +        if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ +            throw std::runtime_error("Failed to open " + node); +        } +    } + +    ~i2c_dev_iface(void){ +        ::close(_node_fd); +    } + +    void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +        byte_vector_t rw_bytes(bytes); + +        //setup the message +        i2c_msg msg; +        msg.addr = addr; +        msg.flags = 0; +        msg.len = bytes.size(); +        msg.buf = &rw_bytes.front(); + +        //setup the data +        i2c_rdwr_ioctl_data data; +        data.msgs = &msg; +        data.nmsgs = 1; + +        //call the ioctl +        UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ +        byte_vector_t bytes(num_bytes); + +        //setup the message +        i2c_msg msg; +        msg.addr = addr; +        msg.flags = I2C_M_RD; +        msg.len = bytes.size(); +        msg.buf = &bytes.front(); + +        //setup the data +        i2c_rdwr_ioctl_data data; +        data.msgs = &msg; +        data.nmsgs = 1; + +        //call the ioctl +        UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); + +        return bytes; +    } + +private: int _node_fd; +}; + +/*********************************************************************** + * USRP-E100 interface implementation + **********************************************************************/ +class usrp_e100_iface_impl : public usrp_e100_iface{ +public: + +    int get_file_descriptor(void){ +        return _node_fd; +    } + +    /******************************************************************* +     * Structors +     ******************************************************************/ +    usrp_e100_iface_impl(const std::string &node): +        _i2c_dev_iface(i2c_dev_iface("/dev/i2c-3")) +    { +        //open the device node and check file descriptor +        if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ +            throw std::runtime_error("Failed to open " + node); +        } + +        mb_eeprom = mboard_eeprom_t(get_i2c_dev_iface(), mboard_eeprom_t::MAP_E100); +    } + +    ~usrp_e100_iface_impl(void){ +        //close the device node file descriptor +        ::close(_node_fd); +    } + +    /******************************************************************* +     * IOCTL: provides the communication base for all other calls +     ******************************************************************/ +    void ioctl(int request, void *mem){ +        boost::mutex::scoped_lock lock(_ctrl_mutex); + +        if (::ioctl(_node_fd, request, mem) < 0){ +            throw std::runtime_error(str( +                boost::format("ioctl failed with request %d") % request +            )); +        } +    } + +    /******************************************************************* +     * I2C device node interface +     ******************************************************************/ +    i2c_iface &get_i2c_dev_iface(void){ +        return _i2c_dev_iface; +    } + +    /******************************************************************* +     * Peek and Poke +     ******************************************************************/ +    void poke32(boost::uint32_t addr, boost::uint32_t value){ +        //load the data struct +        usrp_e_ctl32 data; +        data.offset = addr; +        data.count = 1; +        data.buf[0] = value; + +        //call the ioctl +        this->ioctl(USRP_E_WRITE_CTL32, &data); +    } + +    void poke16(boost::uint32_t addr, boost::uint16_t value){ +        //load the data struct +        usrp_e_ctl16 data; +        data.offset = addr; +        data.count = 1; +        data.buf[0] = value; + +        //call the ioctl +        this->ioctl(USRP_E_WRITE_CTL16, &data); +    } + +    boost::uint32_t peek32(boost::uint32_t addr){ +        //load the data struct +        usrp_e_ctl32 data; +        data.offset = addr; +        data.count = 1; + +        //call the ioctl +        this->ioctl(USRP_E_READ_CTL32, &data); + +        return data.buf[0]; +    } + +    boost::uint16_t peek16(boost::uint32_t addr){ +        //load the data struct +        usrp_e_ctl16 data; +        data.offset = addr; +        data.count = 1; + +        //call the ioctl +        this->ioctl(USRP_E_READ_CTL16, &data); + +        return data.buf[0]; +    } + +    /******************************************************************* +     * I2C +     ******************************************************************/ +    static const size_t max_i2c_data_bytes = 10; + +    void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +        //allocate some memory for this transaction +        UHD_ASSERT_THROW(bytes.size() <= max_i2c_data_bytes); +        boost::uint8_t mem[sizeof(usrp_e_i2c) + max_i2c_data_bytes]; + +        //load the data struct +        usrp_e_i2c *data = reinterpret_cast<usrp_e_i2c*>(mem); +        data->addr = addr; +        data->len = bytes.size(); +        std::copy(bytes.begin(), bytes.end(), data->data); + +        //call the spi ioctl +        this->ioctl(USRP_E_I2C_WRITE, data); +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ +        //allocate some memory for this transaction +        UHD_ASSERT_THROW(num_bytes <= max_i2c_data_bytes); +        boost::uint8_t mem[sizeof(usrp_e_i2c) + max_i2c_data_bytes]; + +        //load the data struct +        usrp_e_i2c *data = reinterpret_cast<usrp_e_i2c*>(mem); +        data->addr = addr; +        data->len = num_bytes; + +        //call the spi ioctl +        this->ioctl(USRP_E_I2C_READ, data); + +        //unload the data +        byte_vector_t bytes(data->len); +        UHD_ASSERT_THROW(bytes.size() == num_bytes); +        std::copy(data->data, data->data+bytes.size(), bytes.begin()); +        return bytes; +    } + +    /******************************************************************* +     * SPI +     ******************************************************************/ +    boost::uint32_t transact_spi( +        int which_slave, +        const spi_config_t &config, +        boost::uint32_t bits, +        size_t num_bits, +        bool readback +    ){ +        //load data struct +        usrp_e_spi data; +        data.readback = (readback)? UE_SPI_TXRX : UE_SPI_TXONLY; +        data.slave = which_slave; +        data.length = num_bits; +        data.data = bits; + +        //load the flags +        data.flags = 0; +        data.flags |= (config.miso_edge == spi_config_t::EDGE_RISE)? UE_SPI_LATCH_RISE : UE_SPI_LATCH_FALL; +        data.flags |= (config.mosi_edge == spi_config_t::EDGE_RISE)? UE_SPI_PUSH_FALL  : UE_SPI_PUSH_RISE; + +        //call the spi ioctl +        this->ioctl(USRP_E_SPI, &data); + +        //unload the data +        return data.data; +    } + +private: +    int _node_fd; +    i2c_dev_iface _i2c_dev_iface; +    boost::mutex _ctrl_mutex; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +usrp_e100_iface::sptr usrp_e100_iface::make(const std::string &node){ +    return sptr(new usrp_e100_iface_impl(node)); +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_iface.hpp b/host/lib/usrp/usrp_e100/usrp_e100_iface.hpp new file mode 100644 index 000000000..12283fb52 --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_iface.hpp @@ -0,0 +1,119 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_IFACE_HPP +#define INCLUDED_USRP_E100_IFACE_HPP + +#include <uhd/transport/udp_simple.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/cstdint.hpp> + +//////////////////////////////////////////////////////////////////////// +// I2C addresses +//////////////////////////////////////////////////////////////////////// +#define I2C_DEV_EEPROM  0x50 // 24LC02[45]:  7-bits 1010xxx +#define	I2C_ADDR_MBOARD (I2C_DEV_EEPROM | 0x0) +#define	I2C_ADDR_TX_DB  (I2C_DEV_EEPROM | 0x4) +#define	I2C_ADDR_RX_DB  (I2C_DEV_EEPROM | 0x5) +//////////////////////////////////////////////////////////////////////// + +/*! + * The usrp-e interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp_e100_iface : boost::noncopyable, public uhd::i2c_iface{ +public: +    typedef boost::shared_ptr<usrp_e100_iface> sptr; + +    /*! +     * Make a new usrp-e interface with the control transport. +     * \param node the device node name +     * \return a new usrp-e interface object +     */ +    static sptr make(const std::string &node); + +    /*! +     * Get the underlying file descriptor. +     * \return the file descriptor +     */ +    virtual int get_file_descriptor(void) = 0; + +    /*! +     * Perform an ioctl call on the device node file descriptor. +     * This will throw when the internal ioctl call fails. +     * \param request the control word +     * \param mem pointer to some memory +     */ +    virtual void ioctl(int request, void *mem) = 0; + +    //! Get the I2C interface for the I2C device node +    virtual uhd::i2c_iface &get_i2c_dev_iface(void) = 0; + +    /*! +     * Write a register (32 bits) +     * \param addr the address +     * \param data the 32bit data +     */ +    virtual void poke32(boost::uint32_t addr, boost::uint32_t data) = 0; + +    /*! +     * Read a register (32 bits) +     * \param addr the address +     * \return the 32bit data +     */ +    virtual boost::uint32_t peek32(boost::uint32_t addr) = 0; + +    /*! +     * Write a register (16 bits) +     * \param addr the address +     * \param data the 16bit data +     */ +    virtual void poke16(boost::uint32_t addr, boost::uint16_t data) = 0; + +    /*! +     * Read a register (16 bits) +     * \param addr the address +     * \return the 16bit data +     */ +    virtual boost::uint16_t peek16(boost::uint32_t addr) = 0; + +    /*! +     * Perform an spi transaction. +     * \param which_slave the slave device number +     * \param config spi config args +     * \param data the bits to write +     * \param num_bits how many bits in data +     * \param readback true to readback a value +     * \return spi data if readback set +     */ +    virtual boost::uint32_t transact_spi( +        int which_slave, +        const uhd::spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits, +        bool readback +    ) = 0; + +    //motherboard eeprom map structure +    uhd::usrp::mboard_eeprom_t mb_eeprom; +}; + +#endif /* INCLUDED_USRP_E100_IFACE_HPP */ diff --git a/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp b/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp new file mode 100644 index 000000000..40ea56466 --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp @@ -0,0 +1,212 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/usrp/device_props.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/warning.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/functional/hash.hpp> +#include <iostream> +#include <fstream> + +using namespace uhd; +using namespace uhd::usrp; +namespace fs = boost::filesystem; + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t usrp_e100_find(const device_addr_t &hint){ +    device_addrs_t usrp_e100_addrs; + +    //return an empty list of addresses when type is set to non-usrp-e +    if (hint.has_key("type") and hint["type"] != "usrp-e") return usrp_e100_addrs; + +    //device node not provided, assume its 0 +    if (not hint.has_key("node")){ +        device_addr_t new_addr = hint; +        new_addr["node"] = "/dev/usrp_e0"; +        return usrp_e100_find(new_addr); +    } + +    //use the given device node name +    if (fs::exists(hint["node"])){ +        device_addr_t new_addr; +        new_addr["type"] = "usrp-e"; +        new_addr["node"] = fs::system_complete(fs::path(hint["node"])).file_string(); +        try{ +            usrp_e100_iface::sptr iface = usrp_e100_iface::make(new_addr["node"]); +            new_addr["name"] = iface->mb_eeprom["name"]; +            new_addr["serial"] = iface->mb_eeprom["serial"]; +            if ( +                (not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +                (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) +            ){ +                usrp_e100_addrs.push_back(new_addr); +            } +        } +        catch(const std::exception &e){ +            uhd::warning::post( +                std::string("Ignoring discovered device\n") +                + e.what() +            ); +        } +    } + +    return usrp_e100_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp_e100_make(const device_addr_t &device_addr){ + +    //setup the main interface into fpga +    std::string node = device_addr["node"]; +    std::cout << boost::format("Opening USRP-E on %s") % node << std::endl; +    usrp_e100_iface::sptr iface = usrp_e100_iface::make(node); + +    //------------------------------------------------------------------ +    //-- Handle the FPGA loading... +    //-- The image can be confimed as already loaded when: +    //--   1) The compatibility number matches. +    //--   2) The hash in the hash-file matches. +    //------------------------------------------------------------------ +    static const char *hash_file_path = "/tmp/usrp_e100_hash"; + +    //extract the fpga path for usrp-e +    std::string usrp_e100_fpga_image = find_image_path( +        device_addr.has_key("fpga")? device_addr["fpga"] : "usrp_e100_fpga.bin" +    ); + +    //calculate a hash of the fpga file +    size_t fpga_hash = 0; +    { +        std::ifstream file(usrp_e100_fpga_image.c_str()); +        if (not file.good()) throw std::runtime_error( +            "cannot open fpga file for read: " + usrp_e100_fpga_image +        ); +        do{ +            boost::hash_combine(fpga_hash, file.get()); +        } while (file.good()); +        file.close(); +    } + +    //read the compatibility number +    boost::uint16_t fpga_compat_num = iface->peek16(UE_REG_MISC_COMPAT); + +    //read the hash in the hash-file +    size_t loaded_hash = 0; +    try{std::ifstream(hash_file_path) >> loaded_hash;}catch(...){} + +    //if not loaded: load the fpga image and write the hash-file +    if (fpga_compat_num != USRP_E_COMPAT_NUM or loaded_hash != fpga_hash){ +        iface.reset(); +        usrp_e100_load_fpga(usrp_e100_fpga_image); +	sleep(1); ///\todo do this better one day. +        std::cout << boost::format("re-Opening USRP-E on %s") % node << std::endl; +        iface = usrp_e100_iface::make(node); +        try{std::ofstream(hash_file_path) << fpga_hash;}catch(...){} +    } + +    //check that the compatibility is correct +    fpga_compat_num = iface->peek16(UE_REG_MISC_COMPAT); +    if (fpga_compat_num != USRP_E_COMPAT_NUM){ +        throw std::runtime_error(str(boost::format( +            "Expected fpga compatibility number 0x%x, but got 0x%x:\n" +            "The fpga build is not compatible with the host code build." +        ) % USRP_E_COMPAT_NUM % fpga_compat_num)); +    } + +    return device::sptr(new usrp_e100_impl(iface)); +} + +UHD_STATIC_BLOCK(register_usrp_e100_device){ +    device::register_device(&usrp_e100_find, &usrp_e100_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp_e100_impl::usrp_e100_impl(usrp_e100_iface::sptr iface): _iface(iface){ + +    //setup interfaces into hardware +    _clock_ctrl = usrp_e100_clock_ctrl::make(_iface); +    _codec_ctrl = usrp_e100_codec_ctrl::make(_iface); + +    //initialize the mboard +    mboard_init(); + +    //initialize the dboards +    dboard_init(); + +    //initialize the dsps +    rx_ddc_init(); +    tx_duc_init(); + +    //init the codec properties +    codec_init(); + +    //init the io send/recv +    io_init(); + +    //set default subdev specs +    this->mboard_set(MBOARD_PROP_RX_SUBDEV_SPEC, subdev_spec_t()); +    this->mboard_set(MBOARD_PROP_TX_SUBDEV_SPEC, subdev_spec_t()); +} + +usrp_e100_impl::~usrp_e100_impl(void){ +    /* NOP */ +} + +/*********************************************************************** + * Device Get + **********************************************************************/ +void usrp_e100_impl::get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<device_prop_t>()){ +    case DEVICE_PROP_NAME: +        val = std::string("usrp-e device"); +        return; + +    case DEVICE_PROP_MBOARD: +        UHD_ASSERT_THROW(key.name == ""); +        val = _mboard_proxy->get_link(); +        return; + +    case DEVICE_PROP_MBOARD_NAMES: +        val = prop_names_t(1, ""); //vector of size 1 with empty string +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * Device Set + **********************************************************************/ +void usrp_e100_impl::set(const wax::obj &, const wax::obj &){ +    UHD_THROW_PROP_SET_ERROR(); +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_impl.hpp b/host/lib/usrp/usrp_e100/usrp_e100_impl.hpp new file mode 100644 index 000000000..de158ea5e --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_impl.hpp @@ -0,0 +1,167 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_iface.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/device.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_manager.hpp> + +#ifndef INCLUDED_USRP_E100_IMPL_HPP +#define INCLUDED_USRP_E100_IMPL_HPP + +static const boost::uint16_t USRP_E_COMPAT_NUM = 0x02; + +//! load an fpga image from a bin file into the usrp-e fpga +extern void usrp_e100_load_fpga(const std::string &bin_file); + +/*! + * Make a usrp-e dboard interface. + * \param iface the usrp-e interface object + * \param clock the clock control interface + * \param codec the codec control interface + * \return a sptr to a new dboard interface + */ +uhd::usrp::dboard_iface::sptr make_usrp_e100_dboard_iface( +    usrp_e100_iface::sptr iface, +    usrp_e100_clock_ctrl::sptr clock, +    usrp_e100_codec_ctrl::sptr codec +); + +/*! + * Simple wax obj proxy class: + * Provides a wax obj interface for a set and a get function. + * This allows us to create nested properties structures + * while maintaining flattened code within the implementation. + */ +class wax_obj_proxy : public wax::obj{ +public: +    typedef boost::function<void(const wax::obj &, wax::obj &)>       get_t; +    typedef boost::function<void(const wax::obj &, const wax::obj &)> set_t; +    typedef boost::shared_ptr<wax_obj_proxy> sptr; + +    static sptr make(const get_t &get, const set_t &set){ +        return sptr(new wax_obj_proxy(get, set)); +    } + +private: +    get_t _get; set_t _set; +    wax_obj_proxy(const get_t &get, const set_t &set): _get(get), _set(set){}; +    void get(const wax::obj &key, wax::obj &val){return _get(key, val);} +    void set(const wax::obj &key, const wax::obj &val){return _set(key, val);} +}; + +/*! + * USRP-E100 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class usrp_e100_impl : public uhd::device{ +public: +    //structors +    usrp_e100_impl(usrp_e100_iface::sptr); +    ~usrp_e100_impl(void); + +    //the io interface +    size_t send(const std::vector<const void *> &, size_t, const uhd::tx_metadata_t &, const uhd::io_type_t &, send_mode_t, double); +    size_t recv(const std::vector<void *> &, size_t, uhd::rx_metadata_t &, const uhd::io_type_t &, recv_mode_t, double); +    bool recv_async_msg(uhd::async_metadata_t &, double); +    size_t get_max_send_samps_per_packet(void) const; +    size_t get_max_recv_samps_per_packet(void) const; + +private: +    //interface to ioctls and file descriptor +    usrp_e100_iface::sptr _iface; + +    //handle io stuff +    UHD_PIMPL_DECL(io_impl) _io_impl; +    uhd::otw_type_t _send_otw_type, _recv_otw_type; +    void io_init(void); +    void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd); +    void handle_overrun(size_t); + +    //configuration shadows +    uhd::clock_config_t _clock_config; + +    //ad9522 clock control +    usrp_e100_clock_ctrl::sptr _clock_ctrl; + +    //ad9862 codec control +    usrp_e100_codec_ctrl::sptr _codec_ctrl; + +    //device functions and settings +    void get(const wax::obj &, wax::obj &); +    void set(const wax::obj &, const wax::obj &); + +    //mboard functions and settings +    void mboard_init(void); +    void mboard_get(const wax::obj &, wax::obj &); +    void mboard_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _mboard_proxy; +    uhd::usrp::subdev_spec_t _rx_subdev_spec, _tx_subdev_spec; + +    //xx dboard functions and settings +    void dboard_init(void); +    uhd::usrp::dboard_manager::sptr _dboard_manager; +    uhd::usrp::dboard_iface::sptr _dboard_iface; + +    //rx dboard functions and settings +    uhd::usrp::dboard_eeprom_t _rx_db_eeprom; +    void rx_dboard_get(const wax::obj &, wax::obj &); +    void rx_dboard_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _rx_dboard_proxy; + +    //tx dboard functions and settings +    uhd::usrp::dboard_eeprom_t _tx_db_eeprom; +    void tx_dboard_get(const wax::obj &, wax::obj &); +    void tx_dboard_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _tx_dboard_proxy; + +    //rx ddc functions and settings +    void rx_ddc_init(void); +    void rx_ddc_get(const wax::obj &, wax::obj &); +    void rx_ddc_set(const wax::obj &, const wax::obj &); +    double _ddc_freq; size_t _ddc_decim; +    wax_obj_proxy::sptr _rx_ddc_proxy; + +    //tx duc functions and settings +    void tx_duc_init(void); +    void tx_duc_get(const wax::obj &, wax::obj &); +    void tx_duc_set(const wax::obj &, const wax::obj &); +    double _duc_freq; size_t _duc_interp; +    wax_obj_proxy::sptr _tx_duc_proxy; + +    //codec functions and settings +    void codec_init(void); +    void rx_codec_get(const wax::obj &, wax::obj &); +    void rx_codec_set(const wax::obj &, const wax::obj &); +    void tx_codec_get(const wax::obj &, wax::obj &); +    void tx_codec_set(const wax::obj &, const wax::obj &); +    wax_obj_proxy::sptr _rx_codec_proxy, _tx_codec_proxy; +     +    //clock control functions and settings +    void init_clock_config(void); +    void update_clock_config(void); +}; + +#endif /* INCLUDED_USRP_E100_IMPL_HPP */ diff --git a/host/lib/usrp/usrp_e100/usrp_e100_mmap_zero_copy.cpp b/host/lib/usrp/usrp_e100/usrp_e100_mmap_zero_copy.cpp new file mode 100644 index 000000000..bf378a9b1 --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_mmap_zero_copy.cpp @@ -0,0 +1,215 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_iface.hpp" +#include <uhd/transport/zero_copy.hpp> +#include <uhd/utils/assert.hpp> +#include <linux/usrp_e.h> +#include <sys/mman.h> //mmap +#include <unistd.h> //getpagesize +#include <poll.h> //poll +#include <boost/bind.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::transport; + +static const bool fp_verbose = false; //fast-path verbose +static const bool sp_verbose = false; //slow-path verbose +static const size_t poll_breakout = 10; //how many poll timeouts constitute a full timeout + +/*********************************************************************** + * The zero copy interface implementation + **********************************************************************/ +class usrp_e100_mmap_zero_copy_impl : public zero_copy_if, public boost::enable_shared_from_this<usrp_e100_mmap_zero_copy_impl> { +public: +    usrp_e100_mmap_zero_copy_impl(usrp_e100_iface::sptr iface): +        _fd(iface->get_file_descriptor()), _recv_index(0), _send_index(0) +    { +        //get system sizes +        iface->ioctl(USRP_E_GET_RB_INFO, &_rb_size); +        size_t page_size = getpagesize(); +        _frame_size = page_size/2; + +        //calculate the memory size +        _map_size = +            (_rb_size.num_pages_rx_flags + _rb_size.num_pages_tx_flags) * page_size + +            (_rb_size.num_rx_frames + _rb_size.num_tx_frames) * _frame_size; + +        //print sizes summary +        if (sp_verbose){ +            std::cout << "page_size:          " << page_size                   << std::endl; +            std::cout << "frame_size:         " << _frame_size                 << std::endl; +            std::cout << "num_pages_rx_flags: " << _rb_size.num_pages_rx_flags << std::endl; +            std::cout << "num_rx_frames:      " << _rb_size.num_rx_frames      << std::endl; +            std::cout << "num_pages_tx_flags: " << _rb_size.num_pages_tx_flags << std::endl; +            std::cout << "num_tx_frames:      " << _rb_size.num_tx_frames      << std::endl; +            std::cout << "map_size:           " << _map_size                   << std::endl; +        } + +        //call mmap to get the memory +        _mapped_mem = ::mmap( +            NULL, _map_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0 +        ); +        UHD_ASSERT_THROW(_mapped_mem != MAP_FAILED); + +        //calculate the memory offsets for info and buffers +        size_t recv_info_off = 0; +        size_t recv_buff_off = recv_info_off + (_rb_size.num_pages_rx_flags * page_size); +        size_t send_info_off = recv_buff_off + (_rb_size.num_rx_frames * _frame_size); +        size_t send_buff_off = send_info_off + (_rb_size.num_pages_tx_flags * page_size); + +        //print offset summary +        if (sp_verbose){ +            std::cout << "recv_info_off: " << recv_info_off << std::endl; +            std::cout << "recv_buff_off: " << recv_buff_off << std::endl; +            std::cout << "send_info_off: " << send_info_off << std::endl; +            std::cout << "send_buff_off: " << send_buff_off << std::endl; +        } + +        //set the internal pointers for info and buffers +        typedef ring_buffer_info (*rbi_pta)[]; +        char *rb_ptr = reinterpret_cast<char *>(_mapped_mem); +        _recv_info = reinterpret_cast<rbi_pta>(rb_ptr + recv_info_off); +        _recv_buff = rb_ptr + recv_buff_off; +        _send_info = reinterpret_cast<rbi_pta>(rb_ptr + send_info_off); +        _send_buff = rb_ptr + send_buff_off; +    } + +    ~usrp_e100_mmap_zero_copy_impl(void){ +        if (sp_verbose) std::cout << "cleanup: munmap" << std::endl; +        ::munmap(_mapped_mem, _map_size); +    } + +    managed_recv_buffer::sptr get_recv_buff(double timeout){ +        if (fp_verbose) std::cout << "get_recv_buff: " << _recv_index << std::endl; + +        //grab pointers to the info and buffer +        ring_buffer_info *info = (*_recv_info) + _recv_index; +        void *mem = _recv_buff + _frame_size*_recv_index; + +        //poll/wait for a ready frame +        if (not (info->flags & RB_USER)){ +            for (size_t i = 0; i < poll_breakout; i++){ +                pollfd pfd; +                pfd.fd = _fd; +                pfd.events = POLLIN; +                ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3/poll_breakout)); +                if (fp_verbose) std::cout << "  POLLIN: " << poll_ret << std::endl; +                if (poll_ret > 0) goto found_user_frame; //good poll, continue on +            } +            return managed_recv_buffer::sptr(); //timed-out for real +        } found_user_frame: + +        //the process has claimed the frame +        info->flags = RB_USER_PROCESS; + +        //increment the index for the next call +        if (++_recv_index == size_t(_rb_size.num_rx_frames)) _recv_index = 0; + +        //return the managed buffer for this frame +        if (fp_verbose) std::cout << "  make_recv_buff: " << info->len << std::endl; +        return managed_recv_buffer::make_safe( +            boost::asio::const_buffer(mem, info->len), +            boost::bind(&usrp_e100_mmap_zero_copy_impl::release, shared_from_this(), info) +        ); +    } + +    size_t get_num_recv_frames(void) const{ +        return _rb_size.num_rx_frames; +    } + +    size_t get_recv_frame_size(void) const{ +        return _frame_size; +    } + +    managed_send_buffer::sptr get_send_buff(double timeout){ +        if (fp_verbose) std::cout << "get_send_buff: " << _send_index << std::endl; + +        //grab pointers to the info and buffer +        ring_buffer_info *info = (*_send_info) + _send_index; +        void *mem = _send_buff + _frame_size*_send_index; + +        //poll/wait for a ready frame +        if (not (info->flags & RB_KERNEL)){ +            pollfd pfd; +            pfd.fd = _fd; +            pfd.events = POLLOUT; +            ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3)); +            if (fp_verbose) std::cout << "  POLLOUT: " << poll_ret << std::endl; +            if (poll_ret <= 0) return managed_send_buffer::sptr(); +        } + +        //increment the index for the next call +        if (++_send_index == size_t(_rb_size.num_tx_frames)) _send_index = 0; + +        //return the managed buffer for this frame +        if (fp_verbose) std::cout << "  make_send_buff: " << _frame_size << std::endl; +        return managed_send_buffer::make_safe( +            boost::asio::mutable_buffer(mem, _frame_size), +            boost::bind(&usrp_e100_mmap_zero_copy_impl::commit, shared_from_this(), info, _1) +        ); +    } + +    size_t get_num_send_frames(void) const{ +        return _rb_size.num_tx_frames; +    } + +    size_t get_send_frame_size(void) const{ +        return _frame_size; +    } + +private: + +    void release(ring_buffer_info *info){ +        if (fp_verbose) std::cout << "recv buff: release" << std::endl; +        info->flags = RB_KERNEL; +    } + +    void commit(ring_buffer_info *info, size_t len){ +        if (fp_verbose) std::cout << "send buff: commit " << len << std::endl; +        info->len = len; +        info->flags = RB_USER; +        if (::write(_fd, NULL, 0) < 0){ +            std::cerr << UHD_THROW_SITE_INFO("write error") << std::endl; +        } +    } + +    int _fd; + +    //the mapped memory itself +    void *_mapped_mem; + +    //mapped memory sizes +    usrp_e_ring_buffer_size_t _rb_size; +    size_t _frame_size, _map_size; + +    //pointers to sections in the mapped memory +    ring_buffer_info (*_recv_info)[], (*_send_info)[]; +    char *_recv_buff, *_send_buff; + +    //indexes into sub-sections of mapped memory +    size_t _recv_index, _send_index; +}; + +/*********************************************************************** + * The zero copy interface make function + **********************************************************************/ +zero_copy_if::sptr usrp_e100_make_mmap_zero_copy(usrp_e100_iface::sptr iface){ +    return zero_copy_if::sptr(new usrp_e100_mmap_zero_copy_impl(iface)); +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_regs.hpp b/host/lib/usrp/usrp_e100/usrp_e100_regs.hpp new file mode 100644 index 000000000..625fb2c35 --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_regs.hpp @@ -0,0 +1,198 @@ + + +//////////////////////////////////////////////////////////////// +// +//         Memory map for embedded wishbone bus +// +//////////////////////////////////////////////////////////////// + +// All addresses are byte addresses.  All accesses are word (16-bit) accesses. +//  This means that address bit 0 is usually 0. +//  There are 11 bits of address for the control. + +#ifndef INCLUDED_USRP_E100_REGS_HPP +#define INCLUDED_USRP_E100_REGS_HPP + +///////////////////////////////////////////////////// +// Slave pointers + +#define UE_REG_SLAVE(n) ((n)<<7) +#define UE_REG_SR_ADDR(n) ((UE_REG_SLAVE(5)) + (4*(n))) + +///////////////////////////////////////////////////// +// Slave 0 -- Misc Regs + +#define UE_REG_MISC_BASE UE_REG_SLAVE(0) + +#define UE_REG_MISC_LED        UE_REG_MISC_BASE + 0 +#define UE_REG_MISC_SW         UE_REG_MISC_BASE + 2 +#define UE_REG_MISC_CGEN_CTRL  UE_REG_MISC_BASE + 4 +#define UE_REG_MISC_CGEN_ST    UE_REG_MISC_BASE + 6 +#define UE_REG_MISC_TEST       UE_REG_MISC_BASE + 8 +#define UE_REG_MISC_RX_LEN     UE_REG_MISC_BASE + 10 +#define UE_REG_MISC_TX_LEN     UE_REG_MISC_BASE + 12 +#define UE_REG_MISC_XFER_RATE  UE_REG_MISC_BASE + 14 +#define UE_REG_MISC_COMPAT     UE_REG_MISC_BASE + 16 + +///////////////////////////////////////////////////// +// Slave 1 -- UART +//   CLKDIV is 16 bits, others are only 8 + +#define UE_REG_UART_BASE UE_REG_SLAVE(1) + +#define UE_REG_UART_CLKDIV  UE_REG_UART_BASE + 0 +#define UE_REG_UART_TXLEVEL UE_REG_UART_BASE + 2 +#define UE_REG_UART_RXLEVEL UE_REG_UART_BASE + 4 +#define UE_REG_UART_TXCHAR  UE_REG_UART_BASE + 6 +#define UE_REG_UART_RXCHAR  UE_REG_UART_BASE + 8 + +///////////////////////////////////////////////////// +// Slave 2 -- SPI Core +//   This should be accessed through the IOCTL +//   Users should not touch directly + +#define UE_REG_SPI_BASE UE_REG_SLAVE(2) + +//spi slave constants +#define UE_SPI_SS_AD9522    (1 << 3) +#define UE_SPI_SS_AD9862    (1 << 2) +#define UE_SPI_SS_TX_DB     (1 << 1) +#define UE_SPI_SS_RX_DB     (1 << 0) + +//////////////////////////////////////////////// +// Slave 3 -- I2C Core +//   This should be accessed through the IOCTL +//   Users should not touch directly + +#define UE_REG_I2C_BASE UE_REG_SLAVE(3) + + +//////////////////////////////////////////////// +// Slave 4 -- GPIO + +#define UE_REG_GPIO_BASE UE_REG_SLAVE(4) + +#define UE_REG_GPIO_RX_IO      UE_REG_GPIO_BASE + 0 +#define UE_REG_GPIO_TX_IO      UE_REG_GPIO_BASE + 2 +#define UE_REG_GPIO_RX_DDR     UE_REG_GPIO_BASE + 4 +#define UE_REG_GPIO_TX_DDR     UE_REG_GPIO_BASE + 6 +#define UE_REG_GPIO_RX_SEL     UE_REG_GPIO_BASE + 8 +#define UE_REG_GPIO_TX_SEL     UE_REG_GPIO_BASE + 10 +#define UE_REG_GPIO_RX_DBG     UE_REG_GPIO_BASE + 12 +#define UE_REG_GPIO_TX_DBG     UE_REG_GPIO_BASE + 14 + +//possible bit values for sel when dbg is 0: +#define GPIO_SEL_SW    0 // if pin is an output, set by software in the io reg +#define GPIO_SEL_ATR   1 // if pin is an output, set by ATR logic + +//possible bit values for sel when dbg is 1: +#define GPIO_SEL_DEBUG_0   0 // if pin is an output, debug lines from FPGA fabric +#define GPIO_SEL_DEBUG_1   1 // if pin is an output, debug lines from FPGA fabric + + +//////////////////////////////////////////////////// +// Slave 5 -- Settings Bus +// +// Output-only, no readback, 32 registers total +//  Each register must be written 32 bits at a time +//  First the address xxx_xx00 and then xxx_xx10 + +#define UE_REG_SETTINGS_BASE UE_REG_SLAVE(5) + +/////////////////////////////////////////////////// +// Slave 6 -- ATR Controller +//   16 regs + +#define UE_REG_ATR_BASE  UE_REG_SLAVE(6) + +#define	UE_REG_ATR_IDLE_RXSIDE  UE_REG_ATR_BASE + 0 +#define	UE_REG_ATR_IDLE_TXSIDE  UE_REG_ATR_BASE + 2 +#define UE_REG_ATR_INTX_RXSIDE  UE_REG_ATR_BASE + 4 +#define UE_REG_ATR_INTX_TXSIDE  UE_REG_ATR_BASE + 6 +#define	UE_REG_ATR_INRX_RXSIDE  UE_REG_ATR_BASE + 8 +#define	UE_REG_ATR_INRX_TXSIDE  UE_REG_ATR_BASE + 10 +#define	UE_REG_ATR_FULL_RXSIDE  UE_REG_ATR_BASE + 12 +#define	UE_REG_ATR_FULL_TXSIDE  UE_REG_ATR_BASE + 14 + +///////////////////////////////////////////////// +// DSP RX Regs +//////////////////////////////////////////////// +#define UE_REG_DSP_RX_FREQ         UE_REG_SR_ADDR(0) +#define UE_REG_DSP_RX_SCALE_IQ     UE_REG_SR_ADDR(1)  // {scale_i,scale_q} +#define UE_REG_DSP_RX_DECIM_RATE   UE_REG_SR_ADDR(2)  // hb and decim rate +#define UE_REG_DSP_RX_DCOFFSET_I   UE_REG_SR_ADDR(3) // Bit 31 high sets fixed offset mode, using lower 14 bits, // otherwise it is automatic +#define UE_REG_DSP_RX_DCOFFSET_Q   UE_REG_SR_ADDR(4) // Bit 31 high sets fixed offset mode, using lower 14 bits +#define UE_REG_DSP_RX_MUX          UE_REG_SR_ADDR(5) + +/////////////////////////////////////////////////// +// VITA RX CTRL regs +/////////////////////////////////////////////////// +// The following 3 are logically a single command register. +// They are clocked into the underlying fifo when time_ticks is written. +#define UE_REG_CTRL_RX_STREAM_CMD        UE_REG_SR_ADDR(8) // {now, chain, num_samples(30) +#define UE_REG_CTRL_RX_TIME_SECS         UE_REG_SR_ADDR(9) +#define UE_REG_CTRL_RX_TIME_TICKS        UE_REG_SR_ADDR(10) +#define UE_REG_CTRL_RX_CLEAR_OVERRUN     UE_REG_SR_ADDR(11) // write anything to clear overrun +#define UE_REG_CTRL_RX_VRT_HEADER        UE_REG_SR_ADDR(12) // word 0 of packet.  FPGA fills in packet counter +#define UE_REG_CTRL_RX_VRT_STREAM_ID     UE_REG_SR_ADDR(13) // word 1 of packet. +#define UE_REG_CTRL_RX_VRT_TRAILER       UE_REG_SR_ADDR(14) +#define UE_REG_CTRL_RX_NSAMPS_PER_PKT    UE_REG_SR_ADDR(15) +#define UE_REG_CTRL_RX_NCHANNELS         UE_REG_SR_ADDR(16) // 1 in basic case, up to 4 for vector sources + +///////////////////////////////////////////////// +// DSP TX Regs +//////////////////////////////////////////////// +#define UE_REG_DSP_TX_FREQ         UE_REG_SR_ADDR(17) +#define UE_REG_DSP_TX_SCALE_IQ     UE_REG_SR_ADDR(18) // {scale_i,scale_q} +#define UE_REG_DSP_TX_INTERP_RATE  UE_REG_SR_ADDR(19) +#define UE_REG_DSP_TX_UNUSED       UE_REG_SR_ADDR(20) +#define UE_REG_DSP_TX_MUX          UE_REG_SR_ADDR(21) + +///////////////////////////////////////////////// +// VITA TX CTRL regs +//////////////////////////////////////////////// +#define UE_REG_CTRL_TX_NCHANNELS         UE_REG_SR_ADDR(24) +#define UE_REG_CTRL_TX_CLEAR_UNDERRUN    UE_REG_SR_ADDR(25) +#define UE_REG_CTRL_TX_REPORT_SID        UE_REG_SR_ADDR(26) +#define UE_REG_CTRL_TX_POLICY            UE_REG_SR_ADDR(27) + +#define UE_FLAG_CTRL_TX_POLICY_WAIT          (0x1 << 0) +#define UE_FLAG_CTRL_TX_POLICY_NEXT_PACKET   (0x1 << 1) +#define UE_FLAG_CTRL_TX_POLICY_NEXT_BURST    (0x1 << 2) + +///////////////////////////////////////////////// +// VITA49 64 bit time (write only) +//////////////////////////////////////////////// +  /*! +   * \brief Time 64 flags +   * +   * <pre> +   * +   *    3                   2                   1 +   *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +   * +-----------------------------------------------------------+-+-+ +   * |                                                           |S|P| +   * +-----------------------------------------------------------+-+-+ +   * +   * P - PPS edge selection (0=negedge, 1=posedge, default=0) +   * S - Source (0=sma, 1=mimo, 0=default) +   * +   * </pre> +   */ +#define UE_REG_TIME64_SECS  UE_REG_SR_ADDR(28)  // value to set absolute secs to on next PPS +#define UE_REG_TIME64_TICKS UE_REG_SR_ADDR(29)  // value to set absolute ticks to on next PPS +#define UE_REG_TIME64_FLAGS UE_REG_SR_ADDR(30)  // flags - see chart above +#define UE_REG_TIME64_IMM   UE_REG_SR_ADDR(31)  // set immediate (0=latch on next pps, 1=latch immediate, default=0) +#define UE_REG_TIME64_TPS   UE_REG_SR_ADDR(31)  // clock ticks per second (counter rollover) + +//pps flags (see above) +#define UE_FLAG_TIME64_PPS_NEGEDGE (0 << 0) +#define UE_FLAG_TIME64_PPS_POSEDGE (1 << 0) +#define UE_FLAG_TIME64_PPS_SMA     (0 << 1) +#define UE_FLAG_TIME64_PPS_MIMO    (1 << 1) + +#define UE_FLAG_TIME64_LATCH_NOW 1 +#define UE_FLAG_TIME64_LATCH_NEXT_PPS 0 + +#endif + diff --git a/host/lib/usrp/wrapper_utils.hpp b/host/lib/usrp/wrapper_utils.hpp new file mode 100644 index 000000000..a7b5c5da6 --- /dev/null +++ b/host/lib/usrp/wrapper_utils.hpp @@ -0,0 +1,66 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_WRAPPER_UTILS_HPP +#define INCLUDED_LIBUHD_USRP_WRAPPER_UTILS_HPP + +#include <uhd/wax.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <uhd/utils/warning.hpp> +#include <boost/format.hpp> +#include <cmath> + +static inline uhd::freq_range_t add_dsp_shift( +    const uhd::freq_range_t &range, +    wax::obj dsp +){ +    double codec_rate = dsp[uhd::usrp::DSP_PROP_CODEC_RATE].as<double>(); +    return uhd::freq_range_t(range.start() - codec_rate/2.0, range.stop() + codec_rate/2.0); +} + +static inline void do_samp_rate_warning_message( +    double target_rate, +    double actual_rate, +    const std::string &xx +){ +    static const double max_allowed_error = 1.0; //Sps +    if (std::abs(target_rate - actual_rate) > max_allowed_error){ +        uhd::warning::post(str(boost::format( +            "The hardware does not support the requested %s sample rate:\n" +            "Target sample rate: %f MSps\n" +            "Actual sample rate: %f MSps\n" +        ) % xx % (target_rate/1e6) % (actual_rate/1e6))); +    } +} + +static inline void do_tune_freq_warning_message( +    double target_freq, +    double actual_freq, +    const std::string &xx +){ +    static const double max_allowed_error = 1.0; //Hz +    if (std::abs(target_freq - actual_freq) > max_allowed_error){ +        uhd::warning::post(str(boost::format( +            "The hardware does not support the requested %s frequency:\n" +            "Target frequency: %f MHz\n" +            "Actual frequency: %f MHz\n" +        ) % xx % (target_freq/1e6) % (actual_freq/1e6))); +    } +} + +#endif /* INCLUDED_LIBUHD_USRP_WRAPPER_UTILS_HPP */  | 
