diff options
Diffstat (limited to 'host/lib/usrp/usrp2')
| -rw-r--r-- | host/lib/usrp/usrp2/CMakeLists.txt | 36 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/clock_ctrl.cpp | 377 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/clock_ctrl.hpp | 109 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/codec_ctrl.cpp | 216 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/codec_ctrl.hpp | 68 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/dboard_iface.cpp | 295 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/fw_common.h | 160 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/io_impl.cpp | 440 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_clk_regs.hpp | 87 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_iface.cpp | 395 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_iface.hpp | 83 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_impl.cpp | 732 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_impl.hpp | 139 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_regs.hpp | 105 | 
14 files changed, 3242 insertions, 0 deletions
| diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt new file mode 100644 index 000000000..10f7407b0 --- /dev/null +++ b/host/lib/usrp/usrp2/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP2 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF) + +IF(ENABLE_USRP2) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_impl.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..66c7a6c28 --- /dev/null +++ b/host/lib/usrp/usrp2/clock_ctrl.cpp @@ -0,0 +1,377 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9510_regs.hpp" +#include "usrp2_regs.hpp" //spi slave constants +#include "usrp2_clk_regs.hpp" +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/cstdint.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> + +using namespace uhd; + +static const bool enb_test_clk = false; + +/*! + * 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); +        this->enable_mimo_clock_out(false); + +        /* private clock enables, must be set here */ +        this->enable_dac_clock(true); +        this->enable_adc_clock(true); +        this->enable_test_clock(enb_test_clk); +    } + +    ~usrp2_clock_ctrl_impl(void){UHD_SAFE_CALL( +        //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); +        this->enable_test_clock(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){ +        switch(_iface->get_rev()) { +            case usrp2_iface::USRP_N200_R4: +            case usrp2_iface::USRP_N210_R4: +                _ad9510_regs.power_down_lvds_cmos_out7 = enb? 0 : 1; +                _ad9510_regs.lvds_cmos_select_out7 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT7_LVDS; +                _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(); +                break; +            default: +                _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(); +                break; +        } +    } + +    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; +    } +     +    void set_mimo_clock_delay(double delay) { +        //delay_val is a 5-bit value (0-31) for fine control +        //the equations below determine delay for a given ramp current, # of caps and fine delay register +        //delay range: +        //range_ns = 200*((caps+3)/i_ramp_ua)*1.3286 +        //offset (zero delay): +        //offset_ns = 0.34 + (1600 - i_ramp_ua)*1e-4 + ((caps-1)/ramp)*6 +        //delay_ns = offset_ns + range_ns * delay / 31 + +        int delay_val = boost::math::iround(delay/9.744e-9*31); + +        if(delay_val == 0) { +            switch(clk_regs.exp) { +            case 5: +                _ad9510_regs.delay_control_out5 = 1; +                break; +            case 6: +                _ad9510_regs.delay_control_out6 = 1; +                break; +            default: +                break; //delay not supported on U2 rev 3 +            } +        } else { +            switch(clk_regs.exp) { +            case 5: +                _ad9510_regs.delay_control_out5 = 0; +                _ad9510_regs.ramp_current_out5 = ad9510_regs_t::RAMP_CURRENT_OUT5_200UA; +                _ad9510_regs.ramp_capacitor_out5 = ad9510_regs_t::RAMP_CAPACITOR_OUT5_4CAPS; +                _ad9510_regs.delay_fine_adjust_out5 = delay_val; +                this->write_reg(0x34); +                this->write_reg(0x35); +                this->write_reg(0x36); +                break; +            case 6: +                _ad9510_regs.delay_control_out6 = 0; +                _ad9510_regs.ramp_current_out6 = ad9510_regs_t::RAMP_CURRENT_OUT6_200UA; +                _ad9510_regs.ramp_capacitor_out6 = ad9510_regs_t::RAMP_CAPACITOR_OUT6_4CAPS; +                _ad9510_regs.delay_fine_adjust_out6 = delay_val; +                this->write_reg(0x38); +                this->write_reg(0x39); +                this->write_reg(0x3A); +                break; +            default: +                break; +            } +        } +    } + +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->write_spi(SPI_SS_AD9510, spi_config_t::EDGE_RISE, data, 24); +    } + +    /*! +     * 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..9ccbc959e --- /dev/null +++ b/host/lib/usrp/usrp2/clock_ctrl.hpp @@ -0,0 +1,109 @@ +// +// 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; + +    /*! +     * Enable/disable the ref clock output over the serdes cable. +     * \param enb true to enable +     */ +    virtual void enable_mimo_clock_out(bool enb) = 0; +     +    /*! +     * Set the output delay of the mimo clock +     * Used to synchronise daisy-chained USRPs over the MIMO cable +     * Can also be used to adjust delay for uneven reference cable lengths +     * \param delay the clock delay in seconds +     */ +    virtual void set_mimo_clock_delay(double delay) = 0; + +}; + +#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..06bf83b15 --- /dev/null +++ b/host/lib/usrp/usrp2/codec_ctrl.cpp @@ -0,0 +1,216 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9777_regs.hpp" +#include "ads62p44_regs.hpp" +#include "usrp2_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/exception.hpp> +#include <boost/cstdint.hpp> +#include <boost/foreach.hpp> + +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_COMPLEX; +        _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); +        } +        set_tx_mod_mode(0); + +        //power-up adc +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP2_REV3: +        case usrp2_iface::USRP2_REV4: +            _iface->poke32(U2_REG_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); +            this->set_rx_analog_gain(1); +            break; + +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _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.override = 1; +            this->send_ads62p44_reg(0x14); +            _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_NORMAL; +            _ads62p44_regs.output_interface = ads62p44_regs_t::OUTPUT_INTERFACE_LVDS; +            _ads62p44_regs.lvds_current = ads62p44_regs_t::LVDS_CURRENT_2_5MA; +            _ads62p44_regs.lvds_data_term = ads62p44_regs_t::LVDS_DATA_TERM_100; +            this->send_ads62p44_reg(0x11); +            this->send_ads62p44_reg(0x12); +            this->send_ads62p44_reg(0x14); +            this->set_rx_analog_gain(1); +            break; + +        case usrp2_iface::USRP_NXXX: break; +        } +    } + +    ~usrp2_codec_ctrl_impl(void){UHD_SAFE_CALL( +        //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(U2_REG_MISC_CTRL_ADC, U2_FLAG_MISC_CTRL_ADC_OFF); +            break; + +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            //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_tx_mod_mode(int mod_mode){ +        //set the sign of the frequency shift +        _ad9777_regs.modulation_form = (mod_mode > 0)? +            ad9777_regs_t::MODULATION_FORM_E_PLUS_JWT: +            ad9777_regs_t::MODULATION_FORM_E_MINUS_JWT +        ; + +        //set the frequency shift +        switch(std::abs(mod_mode)){ +        case 0: +        case 1: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_NONE; break; +        case 2: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_2; break; +        case 4: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_4; break; +        case 8: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_8; break; +        default: throw uhd::value_error("unknown modulation mode for ad9777"); +        } + +        this->send_ad9777_reg(0x01); //set the register +    } + +    void set_rx_digital_gain(double gain) {  //fine digital gain +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _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(double gain) { //gain correction       +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _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: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _ads62p44_regs.coarse_gain = ads62p44_regs_t::COARSE_GAIN_3_5DB;//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); +        UHD_LOGV(always) << "send_ad9777_reg: " << std::hex << reg << std::endl; +        _iface->write_spi( +            SPI_SS_AD9777, spi_config_t::EDGE_RISE, +            reg, 16 +        ); +    } + +    void send_ads62p44_reg(boost::uint8_t addr) { +        boost::uint16_t reg = _ads62p44_regs.get_write_reg(addr); +        _iface->write_spi( +            SPI_SS_ADS62P44, spi_config_t::EDGE_FALL, +            reg, 16 +        ); +    } +}; + +/*********************************************************************** + * 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..ca300e2b1 --- /dev/null +++ b/host/lib/usrp/usrp2/codec_ctrl.hpp @@ -0,0 +1,68 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#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 modulation mode for the DAC. +     * Possible modes are 0, +/-1, +/-2, +/-4, +/-8 +     * which correspond to shifts of fs/mod_mode. +     * A mode of 0 or +/-1 means no modulation. +     * \param mod_mode the modulation mode +     */ +    virtual void set_tx_mod_mode(int mod_mode) = 0; + +    /*! +     * 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(double 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(double gain) = 0; + +}; + +#endif /* INCLUDED_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp new file mode 100644 index 000000000..c31fc52b7 --- /dev/null +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -0,0 +1,295 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "gpio_core_200.hpp" +#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/exception.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, double); +    double 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; +    gpio_core_200::sptr _gpio; + +    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; +    _gpio = gpio_core_200::make(_iface, GPIO_BASE); + +    //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 + **********************************************************************/ +void usrp2_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ +    return _gpio->set_pin_ctrl(unit, value); +} + +void usrp2_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ +    return _gpio->set_gpio_ddr(unit, value); +} + +void usrp2_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ +    return _gpio->set_gpio_out(unit, value); +} + +boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){ +    return _gpio->read_gpio(unit); +} + +void usrp2_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ +    return _gpio->set_atr_reg(unit, atr, value); +} + +void usrp2_dboard_iface::set_gpio_debug(unit_t, int){ +    throw uhd::not_implemented_error("no set_gpio_debug implemented"); +} + +/*********************************************************************** + * 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->write_spi(unit_to_spi_dev[unit], config, data, num_bits); +} + +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->read_spi(unit_to_spi_dev[unit], config, data, num_bits); +} + +/*********************************************************************** + * 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->write_spi( +        unit_to_spi_dac[unit], spi_config_t::EDGE_FALL,  +        _dac_regs[unit].get_reg(), 24 +    ); +} + +void usrp2_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double 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); +} + +double 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->write_spi( +        unit_to_spi_adc[unit], config, +        ad7922_regs.get_reg(), 16 +    ); +    ad7922_regs.set_reg(boost::uint16_t(_iface->read_spi( +        unit_to_spi_adc[unit], config, +        ad7922_regs.get_reg(), 16 +    ))); + +    //convert to voltage and return +    return 3.3*ad7922_regs.result/4095; +} diff --git a/host/lib/usrp/usrp2/fw_common.h b/host/lib/usrp/usrp2/fw_common.h new file mode 100644 index 000000000..46a57fc95 --- /dev/null +++ b/host/lib/usrp/usrp2/fw_common.h @@ -0,0 +1,160 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_FW_COMMON_H +#define INCLUDED_USRP2_FW_COMMON_H + +#include <stdint.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 +extern "C" { +#endif + +//fpga and firmware compatibility numbers +#define USRP2_FPGA_COMPAT_NUM 7 +#define USRP2_FW_COMPAT_NUM 10 +#define USRP2_FW_VER_MINOR 2 + +//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_UPDATE_PORT 49154 +#define USRP2_UDP_RX_DSP0_PORT 49156 +#define USRP2_UDP_TX_DSP0_PORT 49157 +#define USRP2_UDP_RX_DSP1_PORT 49158 + +// Map for virtual firmware regs (not very big so we can keep it here for now) +#define U2_FW_REG_LOCK_TIME 0 +#define U2_FW_REG_LOCK_GPID 1 +#define U2_FW_REG_VER_MINOR 7 + +//////////////////////////////////////////////////////////////////////// +// 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_GET_THIS_REGISTER_FOR_ME_BRO = 'r', +    USRP2_CTRL_ID_OMG_GOT_REGISTER_SO_BAD_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_HOLLER_AT_ME_BRO = 'l', +    USRP2_CTRL_ID_HOLLER_BACK_DUDE = 'L', + +    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 enum{ +    USRP2_REG_ACTION_FPGA_PEEK32 = 1, +    USRP2_REG_ACTION_FPGA_PEEK16 = 2, +    USRP2_REG_ACTION_FPGA_POKE32 = 3, +    USRP2_REG_ACTION_FPGA_POKE16 = 4, +    USRP2_REG_ACTION_FW_PEEK32   = 5, +    USRP2_REG_ACTION_FW_POKE32   = 6 +} usrp2_reg_action_t; + +typedef struct{ +    uint32_t proto_ver; +    uint32_t id; +    uint32_t seq; +    union{ +        uint32_t ip_addr; +        struct { +            uint32_t dev; +            uint32_t data; +            uint8_t miso_edge; +            uint8_t mosi_edge; +            uint8_t num_bits; +            uint8_t readback; +        } spi_args; +        struct { +            uint8_t addr; +            uint8_t bytes; +            uint8_t data[20]; +        } i2c_args; +        struct { +            uint32_t addr; +            uint32_t data; +            uint8_t action; +        } reg_args; +        struct { +            uint8_t dev; +            uint8_t bytes; +            uint8_t data[20]; +        } uart_args; +        struct { +            uint32_t len; +        } echo_args; +    } data; +} usrp2_ctrl_data_t; + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_USRP2_FW_COMMON_H */ diff --git a/host/lib/usrp/usrp2/io_impl.cpp b/host/lib/usrp/usrp2/io_impl.cpp new file mode 100644 index 000000000..aef3f3a39 --- /dev/null +++ b/host/lib/usrp/usrp2/io_impl.cpp @@ -0,0 +1,440 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "validate_subdev_spec.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "usrp2_impl.hpp" +#include "usrp2_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread/mutex.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; +namespace pt = boost::posix_time; + +/*********************************************************************** + * helpers + **********************************************************************/ +static UHD_INLINE pt::time_duration to_time_dur(double timeout){ +    return pt::microseconds(long(timeout*1e6)); +} + +static UHD_INLINE double from_time_dur(const pt::time_duration &time_dur){ +    return 1e-6*time_dur.total_microseconds(); +} + +/*********************************************************************** + * constants + **********************************************************************/ +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; +        _ready_fcn = boost::bind(&flow_control_monitor::ready, this); +    } + +    /*! +     * Gets the current sequence number to go out. +     * Increments the sequence for the next call +     * \return the sequence to be sent to the dsp +     */ +    UHD_INLINE seq_type get_curr_seq_out(void){ +        return _last_seq_out++; +    } + +    /*! +     * Check the flow control condition. +     * \param timeout the timeout in seconds +     * \return false on timeout +     */ +    UHD_INLINE bool check_fc_condition(double timeout){ +        boost::mutex::scoped_lock lock(_fc_mutex); +        if (this->ready()) return true; +        boost::this_thread::disable_interruption di; //disable because the wait can throw +        return _fc_cond.timed_wait(lock, to_time_dur(timeout), _ready_fcn); +    } + +    /*! +     * 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::mutex::scoped_lock 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; +    boost::function<bool(void)> _ready_fcn; +}; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew + * - alignment buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct usrp2_impl::io_impl{ + +    io_impl(void): +        async_msg_fifo(100/*messages deep*/) +    { +        /* NOP */ +    } + +    managed_send_buffer::sptr get_send_buff(size_t chan, double timeout){ +        flow_control_monitor &fc_mon = *fc_mons[chan]; + +        //wait on flow control w/ timeout +        if (not fc_mon.check_fc_condition(timeout)) return managed_send_buffer::sptr(); + +        //get a buffer from the transport w/ timeout +        managed_send_buffer::sptr buff = tx_xports[chan]->get_send_buff(timeout); + +        //write the flow control word into the buffer +        if (buff.get()) buff->cast<boost::uint32_t *>()[0] = uhd::htonx(fc_mon.get_curr_seq_out()); + +        return buff; +    } + +    //tx dsp: xports and flow control monitors +    std::vector<zero_copy_if::sptr> tx_xports; +    std::vector<flow_control_monitor::sptr> fc_mons; + +    //state management for the vrt packet handler code +    sph::recv_packet_handler recv_handler; +    sph::send_packet_handler send_handler; + +    //methods and variables for the pirate crew +    void recv_pirate_loop(zero_copy_if::sptr, size_t); +    std::list<task::sptr> pirate_tasks; +    bounded_buffer<async_metadata_t> async_msg_fifo; +    double tick_rate; +}; + +/*********************************************************************** + * Receive Pirate Loop + * - while raiding, loot for message packet + * - update flow control condition count + * - put async message packets into queue + **********************************************************************/ +void usrp2_impl::io_impl::recv_pirate_loop( +    zero_copy_if::sptr err_xport, size_t index +){ +    set_thread_priority_safe(); + +    //store a reference to the flow control monitor (offset by max dsps) +    flow_control_monitor &fc_mon = *(this->fc_mons[index]); + +    while (not boost::this_thread::interruption_requested()){ +        managed_recv_buffer::sptr buff = err_xport->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 a tx async report message +            if (if_packet_info.sid == USRP2_TX_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), tick_rate +                ); +                metadata.event_code = async_metadata_t::event_code_t(sph::get_context_code(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]; +                    fc_mon.update_fc_condition(uhd::ntohx(fc_word32)); +                    continue; +                } +                //else UHD_MSG(often) << "metadata.event_code " << metadata.event_code << std::endl; +                async_msg_fifo.push_with_pop_on_full(metadata); + +                if (metadata.event_code & +                    ( async_metadata_t::EVENT_CODE_UNDERFLOW +                    | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET) +                ) UHD_MSG(fastpath) << "U"; +                else if (metadata.event_code & +                    ( async_metadata_t::EVENT_CODE_SEQ_ERROR +                    | async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST) +                ) UHD_MSG(fastpath) << "S"; +            } +            else{ +                //TODO unknown received packet, may want to print error... +            } +        }catch(const std::exception &e){ +            UHD_MSG(error) << "Error in recv pirate loop: " << e.what() << std::endl; +        } +    } +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void usrp2_impl::io_init(void){ + +    //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; + +    //create new io impl +    _io_impl = UHD_PIMPL_MAKE(io_impl, ()); + +    //init first so we dont have an access race +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        //init the tx xport and flow control monitor +        _io_impl->tx_xports.push_back(_mbc[mb].tx_dsp_xport); +        _io_impl->fc_mons.push_back(flow_control_monitor::sptr(new flow_control_monitor( +            USRP2_SRAM_BYTES/_mbc[mb].tx_dsp_xport->get_send_frame_size() +        ))); +    } + +    //create a new pirate thread for each zc if (yarr!!) +    size_t index = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        //spawn a new pirate to plunder the recv booty +        _io_impl->pirate_tasks.push_back(task::make(boost::bind( +            &usrp2_impl::io_impl::recv_pirate_loop, _io_impl.get(), +            _mbc[mb].tx_dsp_xport, index++ +        ))); +    } + +    //init some handler stuff +    _io_impl->recv_handler.set_vrt_unpacker(&vrt::if_hdr_unpack_be); +    _io_impl->recv_handler.set_converter(_rx_otw_type); +    _io_impl->send_handler.set_vrt_packer(&vrt::if_hdr_pack_be, vrt_send_header_offset_words32); +    _io_impl->send_handler.set_converter(_tx_otw_type); +    _io_impl->send_handler.set_max_samples_per_packet(get_max_send_samps_per_packet()); + +    //set the packet threshold to be an entire socket buffer's worth +    const size_t packets_per_sock_buff = size_t(50e6/_mbc[_mbc.keys().front()].rx_dsp_xports[0]->get_recv_frame_size()); +    _io_impl->recv_handler.set_alignment_failure_threshold(packets_per_sock_buff); +} + +void usrp2_impl::update_tick_rate(const double rate){ +    _io_impl->tick_rate = rate; +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.set_tick_rate(rate); +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.set_tick_rate(rate); +} + +void usrp2_impl::update_rx_samp_rate(const double rate){ +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.set_samp_rate(rate); +    const double adj = _mbc[_mbc.keys().front()].rx_dsps.front()->get_scaling_adjustment(); +    _io_impl->recv_handler.set_scale_factor(adj/32767.); +} + +void usrp2_impl::update_tx_samp_rate(const double rate){ +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.set_samp_rate(rate); +} + +static subdev_spec_t replace_zero_in_spec(const std::string &type, const subdev_spec_t &spec){ +    subdev_spec_t new_spec; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, spec){ +        if (pair.db_name == "0"){ +            UHD_MSG(warning) +                << boost::format("In the %s subdevice specification: %s") % type % spec.to_string() << std::endl +                << "Accepting dboard slot name \"0\" for backward compatibility." << std::endl +                << "The official name of the dboard slot on USRP2/N-Series is \"A\"." << std::endl +            ; +            new_spec.push_back(subdev_spec_pair_t("A", pair.sd_name)); +        } +        else new_spec.push_back(pair); +    } +    return new_spec; +} + +subdev_spec_t usrp2_impl::update_rx_subdev_spec(const std::string &which_mb, const subdev_spec_t &spec_){ +    const subdev_spec_t spec = replace_zero_in_spec("RX", spec_); +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    fs_path root = "/mboards/" + which_mb + "/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "rx", which_mb); + +    //setup mux for this spec +    bool fe_swapped = false; +    for (size_t i = 0; i < spec.size(); i++){ +        const std::string conn = _tree->access<std::string>(root / spec[i].db_name / "rx_frontends" / spec[i].sd_name / "connection").get(); +        if (i == 0 and (conn == "QI" or conn == "Q")) fe_swapped = true; +        _mbc[which_mb].rx_dsps[i]->set_mux(conn, fe_swapped); +    } +    _mbc[which_mb].rx_fe->set_mux(fe_swapped); + +    //compute the new occupancy and resize +    _mbc[which_mb].rx_chan_occ = spec.size(); +    size_t nchan = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()) nchan += _mbc[mb].rx_chan_occ; +    _io_impl->recv_handler.resize(nchan); + +    //bind new callbacks for the handler +    size_t chan = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        for (size_t dsp = 0; dsp < _mbc[mb].rx_chan_occ; dsp++){ +            _mbc[mb].rx_dsps[dsp]->set_nsamps_per_packet(get_max_recv_samps_per_packet()); //seems to be a good place to set this +            _io_impl->recv_handler.set_xport_chan_get_buff(chan++, boost::bind( +                &zero_copy_if::get_recv_buff, _mbc[mb].rx_dsp_xports[dsp], _1 +            )); +        } +    } +    return spec; +} + +subdev_spec_t usrp2_impl::update_tx_subdev_spec(const std::string &which_mb, const subdev_spec_t &spec_){ +    const subdev_spec_t spec = replace_zero_in_spec("TX", spec_); +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    fs_path root = "/mboards/" + which_mb + "/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "tx", which_mb); + +    //set the mux for this spec +    const std::string conn = _tree->access<std::string>(root / spec[0].db_name / "tx_frontends" / spec[0].sd_name / "connection").get(); +    _mbc[which_mb].tx_fe->set_mux(conn); + +    //compute the new occupancy and resize +    _mbc[which_mb].tx_chan_occ = spec.size(); +    size_t nchan = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()) nchan += _mbc[mb].tx_chan_occ; +    _io_impl->send_handler.resize(nchan); + +    //bind new callbacks for the handler +    size_t chan = 0, i = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        for (size_t dsp = 0; dsp < _mbc[mb].tx_chan_occ; dsp++){ +            _io_impl->send_handler.set_xport_chan_get_buff(chan++, boost::bind( +                &usrp2_impl::io_impl::get_send_buff, _io_impl.get(), i++, _1 +            )); +        } +    } +    return spec; +} + +/*********************************************************************** + * 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 = _mbc[_mbc.keys().front()].tx_dsp_xport->get_send_frame_size() - hdr_size; +    return bpp/_tx_otw_type.get_sample_size(); +} + +size_t usrp2_impl::send( +    const send_buffs_type &buffs, size_t nsamps_per_buff, +    const tx_metadata_t &metadata, const io_type_t &io_type, +    send_mode_t send_mode, double timeout +){ +    return _io_impl->send_handler.send( +        buffs, nsamps_per_buff, +        metadata, io_type, +        send_mode, timeout +    ); +} + +/*********************************************************************** + * 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 = _mbc[_mbc.keys().front()].rx_dsp_xports[0]->get_recv_frame_size() - hdr_size; +    return bpp/_rx_otw_type.get_sample_size(); +} + +size_t usrp2_impl::recv( +    const recv_buffs_type &buffs, size_t nsamps_per_buff, +    rx_metadata_t &metadata, const io_type_t &io_type, +    recv_mode_t recv_mode, double timeout +){ +    return _io_impl->recv_handler.recv( +        buffs, nsamps_per_buff, +        metadata, io_type, +        recv_mode, timeout +    ); +} 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..8b185eac0 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_clk_regs.hpp @@ -0,0 +1,87 @@ +// +// 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: +    case usrp2_iface::USRP_N200_R4: +    case usrp2_iface::USRP_N210_R4: +        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..aaa5acbd5 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.cpp @@ -0,0 +1,395 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_regs.hpp" +#include "fw_common.h" +#include "usrp2_iface.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.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/bind.hpp> +#include <boost/tokenizer.hpp> +#include <boost/functional/hash.hpp> +#include <algorithm> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const double CTRL_RECV_TIMEOUT = 1.0; + +static const boost::uint32_t MIN_PROTO_COMPAT_SPI = 7; +static const boost::uint32_t MIN_PROTO_COMPAT_I2C = 7; +// The register compat number must reflect the protocol compatibility +// and the compatibility of the register mapping (more likely to change). +static const boost::uint32_t MIN_PROTO_COMPAT_REG = USRP2_FW_COMPAT_NUM; +static const boost::uint32_t MIN_PROTO_COMPAT_UART = 7; + +//Define get_gpid() to get a globally unique identifier for this process. +//The gpid is implemented as a hash of the pid and a unique machine identifier. +#ifdef UHD_PLATFORM_WIN32 +#include <Windows.h> +static inline size_t get_gpid(void){ +    //extract volume serial number +    char szVolName[MAX_PATH+1], szFileSysName[MAX_PATH+1]; +    DWORD dwSerialNumber, dwMaxComponentLen, dwFileSysFlags; +    GetVolumeInformation("C:\\", szVolName, MAX_PATH, +        &dwSerialNumber, &dwMaxComponentLen, +        &dwFileSysFlags, szFileSysName, sizeof(szFileSysName)); + +    size_t hash = 0; +    boost::hash_combine(hash, GetCurrentProcessId()); +    boost::hash_combine(hash, dwSerialNumber); +    return hash; +} +#else +#include <unistd.h> +static inline size_t get_gpid(void){ +    size_t hash = 0; +    boost::hash_combine(hash, getpid()); +    boost::hash_combine(hash, gethostid()); +    return hash; +} +#endif + +class usrp2_iface_impl : public usrp2_iface{ +public: +/*********************************************************************** + * Structors + **********************************************************************/ +    usrp2_iface_impl(udp_simple::sptr ctrl_transport): +        _ctrl_transport(ctrl_transport), +        _ctrl_seq_num(0), +        _protocol_compat(0) //initialized below... +    { +        //Obtain the firmware's compat number. +        //Save the response compat number for communication. +        //TODO can choose to reject certain older compat numbers +        usrp2_ctrl_data_t ctrl_data; +        ctrl_data.id = htonl(USRP2_CTRL_ID_WAZZUP_BRO); +        ctrl_data = ctrl_send_and_recv(ctrl_data, 0, ~0); +        if (ntohl(ctrl_data.id) != USRP2_CTRL_ID_WAZZUP_DUDE) +            throw uhd::runtime_error("firmware not responding"); +        _protocol_compat = ntohl(ctrl_data.proto_ver); + +        mb_eeprom = mboard_eeprom_t(*this, mboard_eeprom_t::MAP_N100); +    } + +    ~usrp2_iface_impl(void){ +        this->lock_device(false); +    } + +/*********************************************************************** + * Device locking + **********************************************************************/ + +    void lock_device(bool lock){ +        if (lock){ +            this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_GPID, boost::uint32_t(get_gpid())); +            _lock_task = task::make(boost::bind(&usrp2_iface_impl::lock_task, this)); +        } +        else{ +            _lock_task.reset(); //shutdown the task +            this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_TIME, 0); //unlock +        } +    } + +    bool is_device_locked(void){ +        boost::uint32_t lock_secs = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_LOCK_TIME); +        boost::uint32_t lock_gpid = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_LOCK_GPID); +        boost::uint32_t curr_secs = this->peek32(U2_REG_TIME64_SECS_RB_IMM); + +        //if the difference is larger, assume not locked anymore +        if (curr_secs - lock_secs >= 3) return false; + +        //otherwise only lock if the device hash is different that ours +        return lock_gpid != boost::uint32_t(get_gpid()); +    } + +    void lock_task(void){ +        //re-lock in task +        boost::uint32_t curr_secs = this->peek32(U2_REG_TIME64_SECS_RB_IMM); +        this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_TIME, curr_secs); +        //sleep for a bit +        boost::this_thread::sleep(boost::posix_time::milliseconds(1500)); +    } + +/*********************************************************************** + * Peek and Poke + **********************************************************************/ +    void poke32(wb_addr_type addr, boost::uint32_t data){ +        this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FPGA_POKE32>(addr, data); +    } + +    boost::uint32_t peek32(wb_addr_type addr){ +        return this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FPGA_PEEK32>(addr); +    } + +    void poke16(wb_addr_type addr, boost::uint16_t data){ +        this->get_reg<boost::uint16_t, USRP2_REG_ACTION_FPGA_POKE16>(addr, data); +    } + +    boost::uint16_t peek16(wb_addr_type addr){ +        return this->get_reg<boost::uint16_t, USRP2_REG_ACTION_FPGA_PEEK16>(addr); +    } + +    template <class T, usrp2_reg_action_t action> +    T get_reg(wb_addr_type addr, T data = 0){ +        //setup the out data +        usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); +        out_data.id = htonl(USRP2_CTRL_ID_GET_THIS_REGISTER_FOR_ME_BRO); +        out_data.data.reg_args.addr = htonl(addr); +        out_data.data.reg_args.data = htonl(boost::uint32_t(data)); +        out_data.data.reg_args.action = action; + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_REG); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_OMG_GOT_REGISTER_SO_BAD_DUDE); +        return T(ntohl(in_data.data.reg_args.data)); +    } + +/*********************************************************************** + * 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 = usrp2_ctrl_data_t(); +        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, MIN_PROTO_COMPAT_SPI); +        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 = usrp2_ctrl_data_t(); +        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, MIN_PROTO_COMPAT_I2C); +        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 = usrp2_ctrl_data_t(); +        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, MIN_PROTO_COMPAT_I2C); +        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, 20, 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 = usrp2_ctrl_data_t(); +        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, MIN_PROTO_COMPAT_UART); +        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 = usrp2_ctrl_data_t(); +        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, MIN_PROTO_COMPAT_UART); +        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; +    } +     +    gps_send_fn_t get_gps_write_fn(void) { +        return boost::bind(&usrp2_iface_impl::write_uart, this, 2, _1); //2 is the GPS UART port on USRP2 +    } +     +    gps_recv_fn_t get_gps_read_fn(void) { +        return boost::bind(&usrp2_iface_impl::read_uart, this, 2); //2 is the GPS UART port on USRP2 +    } + +/*********************************************************************** + * Send/Recv over control + **********************************************************************/ +    usrp2_ctrl_data_t ctrl_send_and_recv( +        const usrp2_ctrl_data_t &out_data, +        boost::uint32_t lo = USRP2_FW_COMPAT_NUM, +        boost::uint32_t hi = USRP2_FW_COMPAT_NUM +    ){ +        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(_protocol_compat); +        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); +            boost::uint32_t compat = ntohl(ctrl_data_in->proto_ver); +            if(len >= sizeof(boost::uint32_t) and (hi < compat or lo > compat)){ +                throw uhd::runtime_error(str(boost::format( +                    "\nPlease update the firmware and FPGA images for your device.\n" +                    "See the application notes for USRP2/N-Series for instructions.\n" +                    "Expected protocol compatibility number %s, but got %d:\n" +                    "The firmware build is not compatible with the host code build." +                ) % ((lo == hi)? (boost::format("%d") % hi) : (boost::format("[%d to %d]") % lo % hi)) % compat)); +            } +            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 uhd::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; +        case 0x0A10: return USRP_N200_R4; +        case 0x0A11: return USRP_N210_R4; +        } +        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_N200_R4: return "USRP-N200-REV4"; +        case USRP_N210_R4: return "USRP-N210-REV4"; +        case USRP_NXXX: return "USRP-N???"; +        } +        UHD_THROW_INVALID_CODE_PATH(); +    } + +    const std::string get_fw_version_string(void){ +        boost::uint32_t minor = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_VER_MINOR); +        return str(boost::format("%u.%u") % _protocol_compat % minor); +    } + +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; +    boost::uint32_t _protocol_compat; + +    //lock thread stuff +    task::sptr _lock_task; +}; + +/*********************************************************************** + * 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..b3c3ef4a2 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.hpp @@ -0,0 +1,83 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#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/function.hpp> +#include "usrp2_regs.hpp" +#include "wb_iface.hpp" +#include <string> + +//TODO: kill this crap when you have the top level GPS include file +typedef boost::function<void(std::string)> gps_send_fn_t; +typedef boost::function<std::string(void)> gps_recv_fn_t; + +/*! + * The usrp2 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp2_iface : public wb_iface, public uhd::spi_iface, public uhd::i2c_iface, public uhd::uart_iface{ +public: +    typedef boost::shared_ptr<usrp2_iface> sptr; +    /*! +     * 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); + +    virtual gps_recv_fn_t get_gps_read_fn(void) = 0; +    virtual gps_send_fn_t get_gps_write_fn(void) = 0; + +    //! The list of possible revision types +    enum rev_type { +        USRP2_REV3 = 3, +        USRP2_REV4 = 4, +        USRP_N200 = 200, +        USRP_N200_R4 = 201, +        USRP_N210 = 210, +        USRP_N210_R4 = 211, +        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; + +    //! Lock the device to this iface +    virtual void lock_device(bool lock) = 0; + +    //! Is this device locked? +    virtual bool is_device_locked(void) = 0; + +    //! A version string for firmware +    virtual const std::string get_fw_version_string(void) = 0; + +    //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..774f91d28 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -0,0 +1,732 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_impl.hpp" +#include "fw_common.h" +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; + +/*********************************************************************** + * 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 = separate_device_addr(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 uhd::value_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_device_addrs(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: +    //Some devices will cause a throw when opened for a broadcast address. +    //We print and recover so the caller can loop through all bcast addrs. +    udp_simple::sptr udp_transport; +    try{ +        udp_transport = udp_simple::make_broadcast(hint["addr"], BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT)); +    } +    catch(const std::exception &e){ +        UHD_MSG(error) << boost::format("Cannot open UDP transport on %s\n%s") % hint["addr"] % e.what() << std::endl; +        return usrp2_addrs; //dont throw, but return empty address so caller can insert +    } + +    //send a hello control packet +    usrp2_ctrl_data_t ctrl_data_out = usrp2_ctrl_data_t(); +    ctrl_data_out.proto_ver = uhd::htonx<boost::uint32_t>(USRP2_FW_COMPAT_NUM); +    ctrl_data_out.id = uhd::htonx<boost::uint32_t>(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)); +        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. +            try{ +                usrp2_iface::sptr iface = usrp2_iface::make(udp_simple::make_connected( +                    new_addr["addr"], BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) +                )); +                if (iface->is_device_locked()) continue; //ignore locked devices +                mboard_eeprom_t mb_eeprom = iface->mb_eeprom; +                new_addr["name"] = mb_eeprom["name"]; +                new_addr["serial"] = mb_eeprom["serial"]; +            } +            catch(const std::exception &){ +                //set these values as empty string so the device may still be found +                //and the filter's below can still operate on the discovered device +                new_addr["name"] = ""; +                new_addr["serial"] = ""; +            } + +            //filter the discovered device below by matching optional keys +            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); +            } + +            //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){ +    return device::sptr(new usrp2_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_usrp2_device){ +    device::register_device(&usrp2_find, &usrp2_make); +} + +/*********************************************************************** + * MTU Discovery + **********************************************************************/ +struct mtu_result_t{ +    size_t recv_mtu, send_mtu; +}; + +static mtu_result_t determine_mtu(const std::string &addr, const mtu_result_t &user_mtu){ +    udp_simple::sptr udp_sock = udp_simple::make_connected( +        addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) +    ); + +    //The FPGA offers 4K buffers, and the user may manually request this. +    //However, multiple simultaneous receives (2DSP slave + 2DSP master), +    //require that buffering to be used internally, and this is a safe setting. +    std::vector<boost::uint8_t> buffer(std::max(user_mtu.recv_mtu, user_mtu.send_mtu)); +    usrp2_ctrl_data_t *ctrl_data = reinterpret_cast<usrp2_ctrl_data_t *>(&buffer.front()); +    static const double echo_timeout = 0.020; //20 ms + +    //test holler - check if its supported in this fw version +    ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); +    ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); +    ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t)); +    udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t))); +    udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); +    if (ntohl(ctrl_data->id) != USRP2_CTRL_ID_HOLLER_BACK_DUDE) +        throw uhd::not_implemented_error("holler protocol not implemented"); + +    size_t min_recv_mtu = sizeof(usrp2_ctrl_data_t), max_recv_mtu = user_mtu.recv_mtu; +    size_t min_send_mtu = sizeof(usrp2_ctrl_data_t), max_send_mtu = user_mtu.send_mtu; + +    while (min_recv_mtu < max_recv_mtu){ + +        size_t test_mtu = (max_recv_mtu/2 + min_recv_mtu/2 + 3) & ~3; + +        ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); +        ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); +        ctrl_data->data.echo_args.len = htonl(test_mtu); +        udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t))); + +        size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); + +        if (len >= test_mtu) min_recv_mtu = test_mtu; +        else                 max_recv_mtu = test_mtu - 4; + +    } + +    while (min_send_mtu < max_send_mtu){ + +        size_t test_mtu = (max_send_mtu/2 + min_send_mtu/2 + 3) & ~3; + +        ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); +        ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); +        ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t)); +        udp_sock->send(boost::asio::buffer(buffer, test_mtu)); + +        size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); +        if (len >= sizeof(usrp2_ctrl_data_t)) len = ntohl(ctrl_data->data.echo_args.len); + +        if (len >= test_mtu) min_send_mtu = test_mtu; +        else                 max_send_mtu = test_mtu - 4; +    } + +    mtu_result_t mtu; +    mtu.recv_mtu = min_recv_mtu; +    mtu.send_mtu = min_send_mtu; +    return mtu; +} + +/*********************************************************************** + * Helpers + **********************************************************************/ +static zero_copy_if::sptr make_xport( +    const std::string &addr, +    const std::string &port, +    const device_addr_t &hints, +    const std::string &filter +){ + +    //only copy hints that contain the filter word +    device_addr_t filtered_hints; +    BOOST_FOREACH(const std::string &key, hints.keys()){ +        if (key.find(filter) == std::string::npos) continue; +        filtered_hints[key] = hints[key]; +    } + +    //make the transport object with the filtered hints +    zero_copy_if::sptr xport = udp_zero_copy::make(addr, port, filtered_hints); + +    //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. +    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)) +    }; +    transport::managed_send_buffer::sptr send_buff = xport->get_send_buff(); +    std::memcpy(send_buff->cast<void*>(), &data, sizeof(data)); +    send_buff->commit(sizeof(data)); + +    return xport; +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp2_impl::usrp2_impl(const device_addr_t &_device_addr){ +    UHD_MSG(status) << "Opening a USRP2/N-Series device..." << std::endl; +    device_addr_t device_addr = _device_addr; + +    //setup the dsp transport hints (default to a large recv buff) +    if (not device_addr.has_key("recv_buff_size")){ +        #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) +            //limit buffer resize on macos or it will error +            device_addr["recv_buff_size"] = "1e6"; +        #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) +            //set to half-a-second of buffering at max rate +            device_addr["recv_buff_size"] = "50e6"; +        #endif +    } +    if (not device_addr.has_key("send_buff_size")){ +        #if defined(UHD_PLATFORM_WIN32) +            //a large send buff is ok to have on windows +            device_addr["send_buff_size"] = "50e6"; +        #endif +    } + +    device_addrs_t device_args = separate_device_addr(device_addr); + +    //extract the user's requested MTU size or default +    mtu_result_t user_mtu; +    user_mtu.recv_mtu = size_t(device_addr.cast<double>("recv_frame_size", udp_simple::mtu)); +    user_mtu.send_mtu = size_t(device_addr.cast<double>("send_frame_size", udp_simple::mtu)); + +    try{ +        //calculate the minimum send and recv mtu of all devices +        mtu_result_t mtu = determine_mtu(device_args[0]["addr"], user_mtu); +        for (size_t i = 1; i < device_args.size(); i++){ +            mtu_result_t mtu_i = determine_mtu(device_args[i]["addr"], user_mtu); +            mtu.recv_mtu = std::min(mtu.recv_mtu, mtu_i.recv_mtu); +            mtu.send_mtu = std::min(mtu.send_mtu, mtu_i.send_mtu); +        } + +        device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(mtu.recv_mtu); +        device_addr["send_frame_size"] = boost::lexical_cast<std::string>(mtu.send_mtu); + +        UHD_MSG(status) << boost::format("Current recv frame size: %d bytes") % mtu.recv_mtu << std::endl; +        UHD_MSG(status) << boost::format("Current send frame size: %d bytes") % mtu.send_mtu << std::endl; +    } +    catch(const uhd::not_implemented_error &){ +        //just ignore this error, makes older fw work... +    } + +    device_args = separate_device_addr(device_addr); //update args for new frame sizes + +    //////////////////////////////////////////////////////////////////// +    // create controller objects and initialize the properties tree +    //////////////////////////////////////////////////////////////////// +    _tree = property_tree::make(); +    _tree->create<std::string>("/name").set("USRP2 / N-Series Device"); + +    for (size_t mbi = 0; mbi < device_args.size(); mbi++){ +        const device_addr_t device_args_i = device_args[mbi]; +        const std::string mb = boost::lexical_cast<std::string>(mbi); +        const std::string addr = device_args_i["addr"]; +        const fs_path mb_path = "/mboards/" + mb; + +        //////////////////////////////////////////////////////////////// +        // create the iface that controls i2c, spi, uart, and wb +        //////////////////////////////////////////////////////////////// +        _mbc[mb].iface = usrp2_iface::make(udp_simple::make_connected( +            addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) +        )); +        _tree->create<std::string>(mb_path / "name").set(_mbc[mb].iface->get_cname()); +        _tree->create<std::string>(mb_path / "fw_version").set(_mbc[mb].iface->get_fw_version_string()); + +        //check the fpga compatibility number +        const boost::uint32_t fpga_compat_num = _mbc[mb].iface->peek32(U2_REG_COMPAT_NUM_RB); +        boost::uint16_t fpga_major = fpga_compat_num >> 16, fpga_minor = fpga_compat_num & 0xffff; +        if (fpga_major == 0){ //old version scheme +            fpga_major = fpga_minor; +            fpga_minor = 0; +        } +        if (fpga_major != USRP2_FPGA_COMPAT_NUM){ +            throw uhd::runtime_error(str(boost::format( +                "\nPlease update the firmware and FPGA images for your device.\n" +                "See the application notes for USRP2/N-Series for instructions.\n" +                "Expected FPGA compatibility number %d, but got %d:\n" +                "The FPGA build is not compatible with the host code build." +            ) % int(USRP2_FPGA_COMPAT_NUM) % fpga_major)); +        } +        _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % fpga_major % fpga_minor)); + +        //-------------------------------------------------------------- +        if (fpga_minor == 0){ //!!! special temporary check !!!// +            throw uhd::runtime_error( +                "Detected FPGA pre-release minor version 7.0.\n" +                "This host code build requires at least 7.1.\n" +                "Please update both firmware and FPGA images\n" +            ); +        } +        //-------------------------------------------------------------- + +        //lock the device/motherboard to this process +        _mbc[mb].iface->lock_device(true); + +        //////////////////////////////////////////////////////////////// +        // construct transports for RX and TX DSPs +        //////////////////////////////////////////////////////////////// +        UHD_LOG << "Making transport for RX DSP0..." << std::endl; +        _mbc[mb].rx_dsp_xports.push_back(make_xport( +            addr, BOOST_STRINGIZE(USRP2_UDP_RX_DSP0_PORT), device_args_i, "recv" +        )); +        UHD_LOG << "Making transport for RX DSP1..." << std::endl; +        _mbc[mb].rx_dsp_xports.push_back(make_xport( +            addr, BOOST_STRINGIZE(USRP2_UDP_RX_DSP1_PORT), device_args_i, "recv" +        )); +        UHD_LOG << "Making transport for TX DSP0..." << std::endl; +        _mbc[mb].tx_dsp_xport = make_xport( +            addr, BOOST_STRINGIZE(USRP2_UDP_TX_DSP0_PORT), device_args_i, "send" +        ); +        //set the filter on the router to take dsp data from this port +        _mbc[mb].iface->poke32(U2_REG_ROUTER_CTRL_PORTS, USRP2_UDP_TX_DSP0_PORT); + +        //////////////////////////////////////////////////////////////// +        // setup the mboard eeprom +        //////////////////////////////////////////////////////////////// +        _tree->create<mboard_eeprom_t>(mb_path / "eeprom") +            .set(_mbc[mb].iface->mb_eeprom) +            .subscribe(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); + +        //////////////////////////////////////////////////////////////// +        // create clock control objects +        //////////////////////////////////////////////////////////////// +        _mbc[mb].clock = usrp2_clock_ctrl::make(_mbc[mb].iface); +        _tree->create<double>(mb_path / "tick_rate") +            .publish(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) +            .subscribe(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); + +        //////////////////////////////////////////////////////////////// +        // create codec control objects +        //////////////////////////////////////////////////////////////// +        const fs_path rx_codec_path = mb_path / "rx_codecs/A"; +        const fs_path tx_codec_path = mb_path / "tx_codecs/A"; +        _tree->create<int>(rx_codec_path / "gains"); //phony property so this dir exists +        _tree->create<int>(tx_codec_path / "gains"); //phony property so this dir exists +        _mbc[mb].codec = usrp2_codec_ctrl::make(_mbc[mb].iface); +        switch(_mbc[mb].iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4:{ +            _tree->create<std::string>(rx_codec_path / "name").set("ads62p44"); +            _tree->create<meta_range_t>(rx_codec_path / "gains/digital/range").set(meta_range_t(0, 6.0, 0.5)); +            _tree->create<double>(rx_codec_path / "gains/digital/value") +                .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); +            _tree->create<meta_range_t>(rx_codec_path / "gains/fine/range").set(meta_range_t(0, 0.5, 0.05)); +            _tree->create<double>(rx_codec_path / "gains/fine/value") +                .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); +        }break; + +        case usrp2_iface::USRP2_REV3: +        case usrp2_iface::USRP2_REV4: +            _tree->create<std::string>(rx_codec_path / "name").set("ltc2284"); +            break; + +        case usrp2_iface::USRP_NXXX: +            _tree->create<std::string>(rx_codec_path / "name").set("??????"); +            break; +        } +        _tree->create<std::string>(tx_codec_path / "name").set("ad9777"); + +        //////////////////////////////////////////////////////////////// +        // create gpsdo control objects +        //////////////////////////////////////////////////////////////// +        if (_mbc[mb].iface->mb_eeprom["gpsdo"] == "internal"){ +            _mbc[mb].gps = gps_ctrl::make( +                _mbc[mb].iface->get_gps_write_fn(), +                _mbc[mb].iface->get_gps_read_fn() +            ); +            BOOST_FOREACH(const std::string &name, _mbc[mb].gps->get_sensors()){ +                _tree->create<sensor_value_t>(mb_path / "sensors" / name) +                    .publish(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); +            } +        } + +        //////////////////////////////////////////////////////////////// +        // and do the misc mboard sensors +        //////////////////////////////////////////////////////////////// +        _tree->create<sensor_value_t>(mb_path / "sensors/mimo_locked") +            .publish(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); +        _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") +            .publish(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); + +        //////////////////////////////////////////////////////////////// +        // create frontend control objects +        //////////////////////////////////////////////////////////////// +        _mbc[mb].rx_fe = rx_frontend_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_FRONT) +        ); +        _mbc[mb].tx_fe = tx_frontend_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_TX_FRONT) +        ); +        //TODO lots of properties to expose here for frontends +        _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") +            .coerce(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); +        _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") +            .coerce(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); + +        //////////////////////////////////////////////////////////////// +        // create rx dsp control objects +        //////////////////////////////////////////////////////////////// +        _mbc[mb].rx_dsps.push_back(rx_dsp_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_DSP0), U2_REG_SR_ADDR(SR_RX_CTRL0), USRP2_RX_SID_BASE + 0, true +        )); +        _mbc[mb].rx_dsps.push_back(rx_dsp_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_DSP1), U2_REG_SR_ADDR(SR_RX_CTRL1), USRP2_RX_SID_BASE + 1, true +        )); +        for (size_t dspno = 0; dspno < _mbc[mb].rx_dsps.size(); dspno++){ +            _mbc[mb].rx_dsps[dspno]->set_link_rate(USRP2_LINK_RATE_BPS); +            _tree->access<double>(mb_path / "tick_rate") +                .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); +            //This is a hack/fix for the lingering packet problem. +            //The dsp core starts streaming briefly... now we flush +            _mbc[mb].rx_dsp_xports[dspno]->get_recv_buff(0.01).get(); //recv with timeout for lingering +            _mbc[mb].rx_dsp_xports[dspno]->get_recv_buff(0.01).get(); //recv with timeout for expected +            fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); +            _tree->create<double>(rx_dsp_path / "rate/value") +                .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) +                .subscribe(boost::bind(&usrp2_impl::update_rx_samp_rate, this, _1)); +            _tree->create<double>(rx_dsp_path / "freq/value") +                .coerce(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); +            _tree->create<meta_range_t>(rx_dsp_path / "freq/range") +                .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); +            _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") +                .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); +        } + +        //////////////////////////////////////////////////////////////// +        // create tx dsp control objects +        //////////////////////////////////////////////////////////////// +        _mbc[mb].tx_dsp = tx_dsp_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_TX_DSP), U2_REG_SR_ADDR(SR_TX_CTRL), USRP2_TX_ASYNC_SID +        ); +        _mbc[mb].tx_dsp->set_link_rate(USRP2_LINK_RATE_BPS); +        _tree->access<double>(mb_path / "tick_rate") +            .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); +        _tree->create<double>(mb_path / "tx_dsps/0/rate/value") +            .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) +            .subscribe(boost::bind(&usrp2_impl::update_tx_samp_rate, this, _1)); +        _tree->create<double>(mb_path / "tx_dsps/0/freq/value") +            .coerce(boost::bind(&usrp2_impl::set_tx_dsp_freq, this, mb, _1)); +        _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") +            .publish(boost::bind(&usrp2_impl::get_tx_dsp_freq_range, this, mb)); + +        //setup dsp flow control +        const double ups_per_sec = device_args_i.cast<double>("ups_per_sec", 20); +        const size_t send_frame_size = _mbc[mb].tx_dsp_xport->get_send_frame_size(); +        const double ups_per_fifo = device_args_i.cast<double>("ups_per_fifo", 8.0); +        _mbc[mb].tx_dsp->set_updates( +            (ups_per_sec > 0.0)? size_t(100e6/*approx tick rate*//ups_per_sec) : 0, +            (ups_per_fifo > 0.0)? size_t(USRP2_SRAM_BYTES/ups_per_fifo/send_frame_size) : 0 +        ); + +        //////////////////////////////////////////////////////////////// +        // create time control objects +        //////////////////////////////////////////////////////////////// +        time64_core_200::readback_bases_type time64_rb_bases; +        time64_rb_bases.rb_secs_now = U2_REG_TIME64_SECS_RB_IMM; +        time64_rb_bases.rb_ticks_now = U2_REG_TIME64_TICKS_RB_IMM; +        time64_rb_bases.rb_secs_pps = U2_REG_TIME64_SECS_RB_PPS; +        time64_rb_bases.rb_ticks_pps = U2_REG_TIME64_TICKS_RB_PPS; +        _mbc[mb].time64 = time64_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases, mimo_clock_sync_delay_cycles +        ); +        _tree->access<double>(mb_path / "tick_rate") +            .subscribe(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); +        _tree->create<time_spec_t>(mb_path / "time/now") +            .publish(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) +            .subscribe(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); +        _tree->create<time_spec_t>(mb_path / "time/pps") +            .publish(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) +            .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); +        //setup time source props +        _tree->create<std::string>(mb_path / "time_source/value") +            .subscribe(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)); +        _tree->create<std::vector<std::string> >(mb_path / "time_source/options") +            .publish(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); +        //setup reference source props +        _tree->create<std::string>(mb_path / "clock_source/value") +            .subscribe(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)); +        static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("mimo"); +        _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); + +        //////////////////////////////////////////////////////////////// +        // create dboard control objects +        //////////////////////////////////////////////////////////////// + +        //read the dboard eeprom to extract the dboard ids +        dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; +        rx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_RX_DB); +        tx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB); +        gdb_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5); + +        //create the properties and register subscribers +        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") +            .set(rx_db_eeprom) +            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); +        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") +            .set(tx_db_eeprom) +            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); +        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") +            .set(gdb_eeprom) +            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); + +        //create a new dboard interface and manager +        _mbc[mb].dboard_iface = make_usrp2_dboard_iface(_mbc[mb].iface, _mbc[mb].clock); +        _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_mbc[mb].dboard_iface); +        _mbc[mb].dboard_manager = dboard_manager::make( +            rx_db_eeprom.id, +            ((gdb_eeprom.id == dboard_id_t::none())? tx_db_eeprom : gdb_eeprom).id, +            _mbc[mb].dboard_iface +        ); +        BOOST_FOREACH(const std::string &name, _mbc[mb].dboard_manager->get_rx_subdev_names()){ +            dboard_manager::populate_prop_tree_from_subdev( +                _tree->subtree(mb_path / "dboards/A/rx_frontends" / name), +                _mbc[mb].dboard_manager->get_rx_subdev(name) +            ); +        } +        BOOST_FOREACH(const std::string &name, _mbc[mb].dboard_manager->get_tx_subdev_names()){ +            dboard_manager::populate_prop_tree_from_subdev( +                _tree->subtree(mb_path / "dboards/A/tx_frontends" / name), +                _mbc[mb].dboard_manager->get_tx_subdev(name) +            ); +        } +    } + +    //initialize io handling +    this->io_init(); + +    //do some post-init tasks +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        fs_path root = "/mboards/" + mb; +        _tree->access<double>(root / "tick_rate").update(); + +        //and now that the tick rate is set, init the host rates to something +        BOOST_FOREACH(const std::string &name, _tree->list(root / "rx_dsps")){ +            _tree->access<double>(root / "rx_dsps" / name / "rate" / "value").set(1e6); +        } +        BOOST_FOREACH(const std::string &name, _tree->list(root / "tx_dsps")){ +            _tree->access<double>(root / "tx_dsps" / name / "rate" / "value").set(1e6); +        } + +        _tree->access<subdev_spec_t>(root / "rx_subdev_spec").set(subdev_spec_t("A:"+_mbc[mb].dboard_manager->get_rx_subdev_names()[0])); +        _tree->access<subdev_spec_t>(root / "tx_subdev_spec").set(subdev_spec_t("A:"+_mbc[mb].dboard_manager->get_tx_subdev_names()[0])); +        _tree->access<std::string>(root / "clock_source/value").set("internal"); +        _tree->access<std::string>(root / "time_source/value").set("none"); + +        //GPS installed: use external ref, time, and init time spec +        if (_mbc[mb].gps.get() != NULL){ +            _tree->access<std::string>(root / "time_source/value").set("external"); +            _tree->access<std::string>(root / "clock_source/value").set("external"); +            _mbc[mb].time64->set_time_next_pps(time_spec_t(time_t(_mbc[mb].gps->get_sensor("gps_time").to_int()+1))); +        } +    } + +} + +usrp2_impl::~usrp2_impl(void){UHD_SAFE_CALL( +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        _mbc[mb].tx_dsp->set_updates(0, 0); +    } +)} + +void usrp2_impl::set_mb_eeprom(const std::string &mb, const uhd::usrp::mboard_eeprom_t &mb_eeprom){ +    mb_eeprom.commit(*(_mbc[mb].iface), mboard_eeprom_t::MAP_N100); +} + +void usrp2_impl::set_db_eeprom(const std::string &mb, const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ +    if (type == "rx") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_RX_DB); +    if (type == "tx") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB); +    if (type == "gdb") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5); +} + +sensor_value_t usrp2_impl::get_mimo_locked(const std::string &mb){ +    const bool lock = (_mbc[mb].iface->peek32(U2_REG_IRQ_RB) & (1<<10)) != 0; +    return sensor_value_t("MIMO", lock, "locked", "unlocked"); +} + +sensor_value_t usrp2_impl::get_ref_locked(const std::string &mb){ +    const bool lock = (_mbc[mb].iface->peek32(U2_REG_IRQ_RB) & (1<<11)) != 0; +    return sensor_value_t("Ref", lock, "locked", "unlocked"); +} + +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> + +double usrp2_impl::set_tx_dsp_freq(const std::string &mb, const double freq_){ +    double new_freq = freq_; +    const double tick_rate = _tree->access<double>("/mboards/"+mb+"/tick_rate").get(); + +    //calculate the DAC shift (multiples of rate) +    const int sign = boost::math::sign(new_freq); +    const int zone = std::min(boost::math::iround(new_freq/tick_rate), 2); +    const double dac_shift = sign*zone*tick_rate; +    new_freq -= dac_shift; //update FPGA DSP target freq + +    //set the DAC shift (modulation mode) +    if (zone == 0) _mbc[mb].codec->set_tx_mod_mode(0); //no shift +    else _mbc[mb].codec->set_tx_mod_mode(sign*4/zone); //DAC interp = 4 + +    return _mbc[mb].tx_dsp->set_freq(new_freq) + dac_shift; //actual freq +} + +meta_range_t usrp2_impl::get_tx_dsp_freq_range(const std::string &mb){ +    const double tick_rate = _tree->access<double>("/mboards/"+mb+"/tick_rate").get(); +    const meta_range_t dsp_range = _mbc[mb].tx_dsp->get_freq_range(); +    return meta_range_t(dsp_range.start() - tick_rate*2, dsp_range.stop() + tick_rate*2, dsp_range.step()); +} + +void usrp2_impl::update_clock_source(const std::string &mb, const std::string &source){ +    //clock source ref 10mhz +    switch(_mbc[mb].iface->get_rev()){ +    case usrp2_iface::USRP_N200: +    case usrp2_iface::USRP_N210: +    case usrp2_iface::USRP_N200_R4: +    case usrp2_iface::USRP_N210_R4: +        if (source == "internal")       _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x12); +        else if (source == "external")  _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C); +        else if (source == "mimo")      _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x15); +        else throw uhd::value_error("unhandled clock configuration reference source: " + source); +        _mbc[mb].clock->enable_external_ref(true); //USRP2P has an internal 10MHz TCXO +        break; + +    case usrp2_iface::USRP2_REV3: +    case usrp2_iface::USRP2_REV4: +        if (source == "internal")       _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x10); +        else if (source == "external")  _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C); +        else if (source == "mimo")      _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x15); +        else throw uhd::value_error("unhandled clock configuration reference source: " + source); +        _mbc[mb].clock->enable_external_ref(source != "internal"); +        break; + +    case usrp2_iface::USRP_NXXX: break; +    } + +    //always drive the clock over serdes if not locking to it +    _mbc[mb].clock->enable_mimo_clock_out(source != "mimo"); + +    //set the mimo clock delay over the serdes +    if (source != "mimo"){ +        switch(_mbc[mb].iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _mbc[mb].clock->set_mimo_clock_delay(mimo_clock_delay_usrp_n2xx); +            break; + +        case usrp2_iface::USRP2_REV4: +            _mbc[mb].clock->set_mimo_clock_delay(mimo_clock_delay_usrp2_rev4); +            break; + +        default: break; //not handled +        } +    } +} diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp new file mode 100644 index 000000000..c0f4b1e6e --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -0,0 +1,139 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_IMPL_HPP +#define INCLUDED_USRP2_IMPL_HPP + +#include "usrp2_iface.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "rx_dsp_core_200.hpp" +#include "tx_dsp_core_200.hpp" +#include "time64_core_200.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/usrp/gps_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> + +static const double USRP2_LINK_RATE_BPS = 1000e6/8; +static const double mimo_clock_delay_usrp2_rev4 = 4.18e-9; +static const double mimo_clock_delay_usrp_n2xx = 3.55e-9; +static const size_t mimo_clock_sync_delay_cycles = 138; +static const size_t USRP2_SRAM_BYTES = size_t(1 << 20); +static const boost::uint32_t USRP2_TX_ASYNC_SID = 2; +static const boost::uint32_t USRP2_RX_SID_BASE = 3; + +/*! + * 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 +); + +/*! + * USRP2 implementation guts: + * The implementation details are encapsulated here. + * Handles device properties and streaming... + */ +class usrp2_impl : public uhd::device{ +public: +    usrp2_impl(const uhd::device_addr_t &); +    ~usrp2_impl(void); + +    //the io interface +    size_t send( +        const send_buffs_type &, size_t, +        const uhd::tx_metadata_t &, const uhd::io_type_t &, +        uhd::device::send_mode_t, double +    ); +    size_t recv( +        const recv_buffs_type &, 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: +    uhd::property_tree::sptr _tree; +    struct mb_container_type{ +        usrp2_iface::sptr iface; +        usrp2_clock_ctrl::sptr clock; +        usrp2_codec_ctrl::sptr codec; +        gps_ctrl::sptr gps; +        rx_frontend_core_200::sptr rx_fe; +        tx_frontend_core_200::sptr tx_fe; +        std::vector<rx_dsp_core_200::sptr> rx_dsps; +        tx_dsp_core_200::sptr tx_dsp; +        time64_core_200::sptr time64; +        std::vector<uhd::transport::zero_copy_if::sptr> rx_dsp_xports; +        uhd::transport::zero_copy_if::sptr tx_dsp_xport; +        uhd::usrp::dboard_manager::sptr dboard_manager; +        uhd::usrp::dboard_iface::sptr dboard_iface; +        size_t rx_chan_occ, tx_chan_occ; +    }; +    uhd::dict<std::string, mb_container_type> _mbc; + +    void set_mb_eeprom(const std::string &, const uhd::usrp::mboard_eeprom_t &); +    void set_db_eeprom(const std::string &, const std::string &, const uhd::usrp::dboard_eeprom_t &); + +    uhd::sensor_value_t get_mimo_locked(const std::string &); +    uhd::sensor_value_t get_ref_locked(const std::string &); + +    //device properties interface +    void get(const wax::obj &, wax::obj &val){ +        val = _tree; //entry point into property tree +    } +    uhd::property_tree::sptr get_tree(void) const{ +        return _tree; +    } + +    //io impl methods and members +    uhd::otw_type_t _rx_otw_type, _tx_otw_type; +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void io_init(void); +    void update_tick_rate(const double rate); +    void update_rx_samp_rate(const double rate); +    void update_tx_samp_rate(const double rate); +    //update spec methods are coercers until we only accept db_name == A +    uhd::usrp::subdev_spec_t update_rx_subdev_spec(const std::string &, const uhd::usrp::subdev_spec_t &); +    uhd::usrp::subdev_spec_t update_tx_subdev_spec(const std::string &, const uhd::usrp::subdev_spec_t &); +    double set_tx_dsp_freq(const std::string &, const double); +    uhd::meta_range_t get_tx_dsp_freq_range(const std::string &); +    void update_clock_source(const std::string &, const std::string &); +}; + +#endif /* INCLUDED_USRP2_IMPL_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_regs.hpp b/host/lib/usrp/usrp2/usrp2_regs.hpp new file mode 100644 index 000000000..8839997f1 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_regs.hpp @@ -0,0 +1,105 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_REGS_HPP +#define INCLUDED_USRP2_REGS_HPP + +//////////////////////////////////////////////////////////////////////// +// Define slave bases +//////////////////////////////////////////////////////////////////////// +#define ROUTER_RAM_BASE     0x4000 +#define SPI_BASE            0x5000 +#define I2C_BASE            0x5400 +#define GPIO_BASE           0x5800 +#define READBACK_BASE       0x5C00 +#define ETH_BASE            0x6000 +#define SETTING_REGS_BASE   0x7000 +#define PIC_BASE            0x8000 +#define UART_BASE           0x8800 +#define ATR_BASE            0x8C00 + +//////////////////////////////////////////////////////////////////////// +// Setting register offsets +//////////////////////////////////////////////////////////////////////// +#define SR_MISC       0   // 7 regs +#define SR_SIMTIMER   8   // 2 +#define SR_TIME64    10   // 6 +#define SR_BUF_POOL  16   // 4 + +#define SR_RX_FRONT  24   // 5 +#define SR_RX_CTRL0  32   // 9 +#define SR_RX_DSP0   48   // 7 +#define SR_RX_CTRL1  80   // 9 +#define SR_RX_DSP1   96   // 7 + +#define SR_TX_FRONT 128   // ? +#define SR_TX_CTRL  144   // 6 +#define SR_TX_DSP   160   // 5 + +#define SR_UDP_SM   192   // 64 + +#define U2_REG_SR_ADDR(sr) (SETTING_REGS_BASE + (4 * (sr))) + +#define U2_REG_ROUTER_CTRL_PORTS U2_REG_SR_ADDR(SR_BUF_POOL) + 8 + +///////////////////////////////////////////////// +// 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_REG_MISC_CTRL_CLOCK U2_REG_SR_ADDR(0) +#define U2_REG_MISC_CTRL_SERDES U2_REG_SR_ADDR(1) +#define U2_REG_MISC_CTRL_ADC U2_REG_SR_ADDR(2) +#define U2_REG_MISC_CTRL_LEDS U2_REG_SR_ADDR(3) +#define U2_REG_MISC_CTRL_PHY U2_REG_SR_ADDR(4) +#define U2_REG_MISC_CTRL_DBG_MUX U2_REG_SR_ADDR(5) +#define U2_REG_MISC_CTRL_RAM_PAGE U2_REG_SR_ADDR(6) +#define U2_REG_MISC_CTRL_FLUSH_ICACHE U2_REG_SR_ADDR(7) +#define U2_REG_MISC_CTRL_LED_SRC U2_REG_SR_ADDR(8) + +#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 + +///////////////////////////////////////////////// +// Readback regs +//////////////////////////////////////////////// +#define U2_REG_STATUS READBACK_BASE + 4*8 +#define U2_REG_TIME64_SECS_RB_IMM READBACK_BASE + 4*10 +#define U2_REG_TIME64_TICKS_RB_IMM READBACK_BASE + 4*11 +#define U2_REG_COMPAT_NUM_RB READBACK_BASE + 4*12 +#define U2_REG_IRQ_RB READBACK_BASE + 4*13 +#define U2_REG_TIME64_SECS_RB_PPS READBACK_BASE + 4*14 +#define U2_REG_TIME64_TICKS_RB_PPS READBACK_BASE + 4*15 + +#endif /* INCLUDED_USRP2_REGS_HPP */ | 
