diff options
Diffstat (limited to 'host/lib/usrp/e100')
| -rw-r--r-- | host/lib/usrp/e100/CMakeLists.txt | 40 | ||||
| -rw-r--r-- | host/lib/usrp/e100/clock_ctrl.cpp | 538 | ||||
| -rw-r--r-- | host/lib/usrp/e100/clock_ctrl.hpp | 127 | ||||
| -rw-r--r-- | host/lib/usrp/e100/codec_ctrl.cpp | 288 | ||||
| -rw-r--r-- | host/lib/usrp/e100/codec_ctrl.hpp | 90 | ||||
| -rw-r--r-- | host/lib/usrp/e100/dboard_iface.cpp | 258 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_ctrl.cpp | 445 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_ctrl.hpp | 47 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_impl.cpp | 552 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_impl.hpp | 139 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_mmap_zero_copy.cpp | 262 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_regs.hpp | 65 | ||||
| -rw-r--r-- | host/lib/usrp/e100/fpga_downloader.cpp | 272 | ||||
| -rw-r--r-- | host/lib/usrp/e100/include/linux/usrp_e.h | 60 | ||||
| -rw-r--r-- | host/lib/usrp/e100/io_impl.cpp | 230 | 
15 files changed, 3413 insertions, 0 deletions
diff --git a/host/lib/usrp/e100/CMakeLists.txt b/host/lib/usrp/e100/CMakeLists.txt new file mode 100644 index 000000000..ac9d8c655 --- /dev/null +++ b/host/lib/usrp/e100/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP-E100 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF) + +IF(ENABLE_E100) +    INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include) + +    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}/e100_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e100_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e100_mmap_zero_copy.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/fpga_downloader.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp +    ) +ENDIF(ENABLE_E100) diff --git a/host/lib/usrp/e100/clock_ctrl.cpp b/host/lib/usrp/e100/clock_ctrl.cpp new file mode 100644 index 000000000..9e355ce17 --- /dev/null +++ b/host/lib/usrp/e100/clock_ctrl.cpp @@ -0,0 +1,538 @@ +// +// 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 "ad9522_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/cstdint.hpp> +#include "e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <algorithm> +#include <utility> + +using namespace uhd; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const bool ENABLE_THE_TEST_OUT = true; +static const double REFERENCE_INPUT_RATE = 10e6; + +/*********************************************************************** + * Helpers + **********************************************************************/ +template <typename div_type, typename bypass_type> static void set_clock_divider( +    size_t divider, div_type &low, div_type &high, bypass_type &bypass +){ +    high = divider/2 - 1; +    low = divider - high - 2; +    bypass = (divider == 1)? 1 : 0; +} + +/*********************************************************************** + * Clock rate calculation stuff: + *   Using the internal VCO between 1400 and 1800 MHz + **********************************************************************/ +struct clock_settings_type{ +    size_t ref_clock_doubler, r_counter, a_counter, b_counter, prescaler, vco_divider, chan_divider; +    size_t get_n_counter(void) const{return prescaler * b_counter + a_counter;} +    double get_ref_rate(void) const{return REFERENCE_INPUT_RATE * ref_clock_doubler;} +    double get_vco_rate(void) const{return get_ref_rate()/r_counter * get_n_counter();} +    double get_chan_rate(void) const{return get_vco_rate()/vco_divider;} +    double get_out_rate(void) const{return get_chan_rate()/chan_divider;} +    std::string to_pp_string(void) const{ +        return str(boost::format( +            "  r_counter: %d\n" +            "  a_counter: %d\n" +            "  b_counter: %d\n" +            "  prescaler: %d\n" +            "  vco_divider: %d\n" +            "  chan_divider: %d\n" +            "  vco_rate: %fMHz\n" +            "  chan_rate: %fMHz\n" +            "  out_rate: %fMHz\n" +            ) +            % r_counter +            % a_counter +            % b_counter +            % prescaler +            % vco_divider +            % chan_divider +            % (get_vco_rate()/1e6) +            % (get_chan_rate()/1e6) +            % (get_out_rate()/1e6) +        ); +    } +}; + +//! gives the greatest divisor of num between 1 and max inclusive +template<typename T> static inline T greatest_divisor(T num, T max){ +    for (T i = max; i > 1; i--) if (num%i == 0) return i; return 1; +} + +//! gives the least divisor of num between min and num exclusive +template<typename T> static inline T least_divisor(T num, T min){ +    for (T i = min; i < num; i++) if (num%i == 0) return i; return 1; +} + +static clock_settings_type get_clock_settings(double rate){ +    clock_settings_type cs; +    cs.ref_clock_doubler = 2; //always doubling +    cs.prescaler = 8; //set to 8 when input is under 2400 MHz + +    //basic formulas used below: +    //out_rate*X = ref_rate*Y +    //X = i*ref_rate/gcd +    //Y = i*out_rate/gcd +    //X = chan_div * vco_div * R +    //Y = P*B + A + +    const boost::uint64_t out_rate = boost::uint64_t(rate); +    const boost::uint64_t ref_rate = boost::uint64_t(cs.get_ref_rate()); +    const size_t gcd = size_t(boost::math::gcd(ref_rate, out_rate)); + +    for (size_t i = 1; i <= 100; i++){ +        const size_t X = i*ref_rate/gcd; +        const size_t Y = i*out_rate/gcd; + +        //determine A and B (P is fixed) +        cs.b_counter = Y/cs.prescaler; +        cs.a_counter = Y - cs.b_counter*cs.prescaler; + +        static const double vco_bound_pad = 100e6; +        for ( //calculate an r divider that fits into the bounds of the vco +            cs.r_counter  = size_t(cs.get_n_counter()*cs.get_ref_rate()/(1800e6 - vco_bound_pad)); +            cs.r_counter <= size_t(cs.get_n_counter()*cs.get_ref_rate()/(1400e6 + vco_bound_pad)) +            and cs.r_counter > 0; cs.r_counter++ +        ){ + +            //determine chan_div and vco_div +            //and fill in that order of preference +            cs.chan_divider = greatest_divisor<size_t>(X/cs.r_counter, 32); +            cs.vco_divider = greatest_divisor<size_t>(X/cs.chan_divider/cs.r_counter, 6); + +            //avoid a vco divider of 1 (if possible) +            if (cs.vco_divider == 1){ +                cs.vco_divider = least_divisor<size_t>(cs.chan_divider, 2); +                cs.chan_divider /= cs.vco_divider; +            } + +            UHD_LOGV(always) +                << "gcd " << gcd << std::endl +                << "X " << X << std::endl +                << "Y " << Y << std::endl +                << cs.to_pp_string() << std::endl +            ; + +            //filter limits on the counters +            if (cs.vco_divider == 1) continue; +            if (cs.r_counter >= (1<<14)) continue; +            if (cs.b_counter == 2) continue; +            if (cs.b_counter == 1 and cs.a_counter != 0) continue; +            if (cs.b_counter >= (1<<13)) continue; +            if (cs.a_counter >= (1<<6)) continue; +            if (cs.get_vco_rate() > 1800e6 - vco_bound_pad) continue; +            if (cs.get_vco_rate() < 1400e6 + vco_bound_pad) continue; +            if (cs.get_out_rate() != rate) continue; + +            UHD_MSG(status) << "USRP-E100 clock control: " << i << std::endl << cs.to_pp_string() << std::endl; +            return cs; +        } +    } + +    throw uhd::value_error(str(boost::format( +        "USRP-E100 clock control: could not calculate settings for clock rate %fMHz" +    ) % (rate/1e6))); +} + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class e100_clock_ctrl_impl : public e100_clock_ctrl{ +public: +    e100_clock_ctrl_impl(spi_iface::sptr iface, double master_clock_rate, const bool dboard_clocks_diff): +        _dboard_clocks_diff(dboard_clocks_diff) +    { +        _iface = iface; +        _chan_rate = 0.0; +        _out_rate = 0.0; + +        //perform soft-reset +        _ad9522_regs.soft_reset = 1; +        this->send_reg(0x000); +        this->latch_regs(); +        _ad9522_regs.soft_reset = 0; + +        //init the clock gen registers +        //Note: out0 should already be clocking the FPGA or this isnt going to work +        _ad9522_regs.sdo_active = ad9522_regs_t::SDO_ACTIVE_SDO_SDIO; +        _ad9522_regs.enb_stat_eeprom_at_stat_pin = 0; //use status pin +        _ad9522_regs.status_pin_control = 0x1; //n divider +        _ad9522_regs.ld_pin_control = 0x00; //dld +        _ad9522_regs.refmon_pin_control = 0x12; //show ref2 +        _ad9522_regs.lock_detect_counter = ad9522_regs_t::LOCK_DETECT_COUNTER_16CYC; + +        this->use_internal_ref(); + +        //initialize the FPGA clock rate +        UHD_MSG(status) << boost::format("Initializing FPGA clock to %fMHz...") % (master_clock_rate/1e6) << std::endl; +        this->set_fpga_clock_rate(master_clock_rate); + +        this->enable_test_clock(ENABLE_THE_TEST_OUT); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +    } + +    ~e100_clock_ctrl_impl(void){ +        this->enable_test_clock(ENABLE_THE_TEST_OUT); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +    } + +    /*********************************************************************** +     * Clock rate control: +     *  - set clock rate w/ internal VCO +     *  - set clock rate w/ external VCXO +     **********************************************************************/ +    void set_clock_settings_with_internal_vco(double rate){ +        const clock_settings_type cs = get_clock_settings(rate); + +        //set the rates to private variables so the implementation knows! +        _chan_rate = cs.get_chan_rate(); +        _out_rate = cs.get_out_rate(); + +        _ad9522_regs.enable_clock_doubler = (cs.ref_clock_doubler == 2)? 1 : 0; + +        _ad9522_regs.set_r_counter(cs.r_counter); +        _ad9522_regs.a_counter = cs.a_counter; +        _ad9522_regs.set_b_counter(cs.b_counter); +        UHD_ASSERT_THROW(cs.prescaler == 8); //assumes this below: +        _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV8_9; + +        _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + +        _ad9522_regs.bypass_vco_divider = 0; +        switch(cs.vco_divider){ +        case 1: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV1; break; +        case 2: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV2; break; +        case 3: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV3; break; +        case 4: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV4; break; +        case 5: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV5; break; +        case 6: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV6; break; +        } +        _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_VCO; + +        //setup fpga master clock +        _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; +        set_clock_divider(cs.chan_divider, +            _ad9522_regs.divider0_low_cycles, +            _ad9522_regs.divider0_high_cycles, +            _ad9522_regs.divider0_bypass +        ); + +        //setup codec clock +        _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; +        set_clock_divider(cs.chan_divider, +            _ad9522_regs.divider1_low_cycles, +            _ad9522_regs.divider1_high_cycles, +            _ad9522_regs.divider1_bypass +        ); + +        this->send_all_regs(); +        calibrate_now(); +    } + +    void set_clock_settings_with_external_vcxo(double rate){ +        //set the rates to private variables so the implementation knows! +        _chan_rate = rate; +        _out_rate = rate; + +        _ad9522_regs.enable_clock_doubler = 1; //doubler always on +        const double ref_rate = REFERENCE_INPUT_RATE*2; + +        //bypass prescaler such that N = B +        long gcd = boost::math::gcd(long(ref_rate), long(rate)); +        _ad9522_regs.set_r_counter(int(ref_rate/gcd)); +        _ad9522_regs.a_counter = 0; +        _ad9522_regs.set_b_counter(int(rate/gcd)); +        _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV1; + +        //setup external vcxo +        _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; +        _ad9522_regs.bypass_vco_divider = 1; +        _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_EXTERNAL; + +        //setup fpga master clock +        _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; +        _ad9522_regs.divider0_bypass = 1; + +        //setup codec clock +        _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; +        _ad9522_regs.divider1_bypass = 1; + +        this->send_all_regs(); +    } + +    void set_fpga_clock_rate(double rate){ +        if (_out_rate == rate) return; +        if (rate == 61.44e6) set_clock_settings_with_external_vcxo(rate); +        else                 set_clock_settings_with_internal_vco(rate); +        set_rx_dboard_clock_rate(rate); +        set_tx_dboard_clock_rate(rate); +    } + +    double get_fpga_clock_rate(void){ +        return this->_out_rate; +    } + +    /*********************************************************************** +     * Special test clock output +     **********************************************************************/ +    void enable_test_clock(bool enb){ +        //setup test clock (same divider as codec clock) +        _ad9522_regs.out4_format = ad9522_regs_t::OUT4_FORMAT_CMOS; +        _ad9522_regs.out4_cmos_configuration = (enb)? +            ad9522_regs_t::OUT4_CMOS_CONFIGURATION_A_ON : +            ad9522_regs_t::OUT4_CMOS_CONFIGURATION_OFF; +        this->send_reg(0x0F4); +        this->latch_regs(); +    } + +    /*********************************************************************** +     * RX Dboard Clock Control (output 9, divider 3) +     **********************************************************************/ +    void enable_rx_dboard_clock(bool enb){ +        if (_dboard_clocks_diff){ +            _ad9522_regs.out9_format = ad9522_regs_t::OUT9_FORMAT_LVDS; +            _ad9522_regs.out9_lvds_power_down = enb? 0 : 1; +        } +        else{ +            _ad9522_regs.out9_format = ad9522_regs_t::OUT9_FORMAT_CMOS; +            _ad9522_regs.out9_cmos_configuration = (enb)? +                ad9522_regs_t::OUT9_CMOS_CONFIGURATION_B_ON : +                ad9522_regs_t::OUT9_CMOS_CONFIGURATION_OFF; +        } +        this->send_reg(0x0F9); +        this->latch_regs(); +    } + +    std::vector<double> get_rx_dboard_clock_rates(void){ +        std::vector<double> rates; +        for(size_t div = 1; div <= 16+16; div++) +            rates.push_back(this->_chan_rate/div); +        return rates; +    } + +    void set_rx_dboard_clock_rate(double rate){ +        assert_has(get_rx_dboard_clock_rates(), rate, "rx dboard clock rate"); +        _rx_clock_rate = rate; +        size_t divider = size_t(this->_chan_rate/rate); +        //set the divider registers +        set_clock_divider(divider, +            _ad9522_regs.divider3_low_cycles, +            _ad9522_regs.divider3_high_cycles, +            _ad9522_regs.divider3_bypass +        ); +        this->send_reg(0x199); +        this->send_reg(0x19a); +        this->soft_sync(); +    } + +    double get_rx_clock_rate(void){ +        return _rx_clock_rate; +    } + +    /*********************************************************************** +     * TX Dboard Clock Control (output 6, divider 2) +     **********************************************************************/ +    void enable_tx_dboard_clock(bool enb){ +        if (_dboard_clocks_diff){ +            _ad9522_regs.out6_format = ad9522_regs_t::OUT6_FORMAT_LVDS; +            _ad9522_regs.out6_lvds_power_down = enb? 0 : 1; +        } +        else{ +            _ad9522_regs.out6_format = ad9522_regs_t::OUT6_FORMAT_CMOS; +            _ad9522_regs.out6_cmos_configuration = (enb)? +                ad9522_regs_t::OUT6_CMOS_CONFIGURATION_B_ON : +                ad9522_regs_t::OUT6_CMOS_CONFIGURATION_OFF; +        } +        this->send_reg(0x0F6); +        this->latch_regs(); +    } + +    std::vector<double> get_tx_dboard_clock_rates(void){ +        return get_rx_dboard_clock_rates(); //same master clock, same dividers... +    } + +    void set_tx_dboard_clock_rate(double rate){ +        assert_has(get_tx_dboard_clock_rates(), rate, "tx dboard clock rate"); +        _tx_clock_rate = rate; +        size_t divider = size_t(this->_chan_rate/rate); +        //set the divider registers +        set_clock_divider(divider, +            _ad9522_regs.divider2_low_cycles, +            _ad9522_regs.divider2_high_cycles, +            _ad9522_regs.divider2_bypass +        ); +        this->send_reg(0x196); +        this->send_reg(0x197); +        this->soft_sync(); +    } + +    double get_tx_clock_rate(void){ +        return _tx_clock_rate; +    } + +    /*********************************************************************** +     * Clock reference control +     **********************************************************************/ +    void use_internal_ref(void) { +        _ad9522_regs.enable_ref2 = 1; +        _ad9522_regs.enable_ref1 = 0; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF2; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; +        this->send_reg(0x01C); +        this->latch_regs(); +    } +     +    void use_external_ref(void) { +        _ad9522_regs.enable_ref2 = 0; +        _ad9522_regs.enable_ref1 = 1; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; +        this->send_reg(0x01C); +        this->latch_regs(); +    } +     +    void use_auto_ref(void) { +        _ad9522_regs.enable_ref2 = 1; +        _ad9522_regs.enable_ref1 = 1; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_AUTO; +        this->send_reg(0x01C); +        this->latch_regs(); +    } + +    bool get_locked(void){ +        static const boost::uint8_t addr = 0x01F; +        boost::uint32_t reg = _iface->read_spi( +            UE_SPI_SS_AD9522, spi_config_t::EDGE_RISE, +            _ad9522_regs.get_read_reg(addr), 24 +        ); +        _ad9522_regs.set_reg(addr, reg); +        return _ad9522_regs.digital_lock_detect != 0; +    } + +private: +    spi_iface::sptr _iface; +    const bool _dboard_clocks_diff; +    ad9522_regs_t _ad9522_regs; +    double _out_rate; //rate at the fpga and codec +    double _chan_rate; //rate before final dividers +    double _rx_clock_rate, _tx_clock_rate; + +    void latch_regs(void){ +        _ad9522_regs.io_update = 1; +        this->send_reg(0x232); +    } + +    void send_reg(boost::uint16_t addr){ +        boost::uint32_t reg = _ad9522_regs.get_write_reg(addr); +        UHD_LOGV(often) << "clock control write reg: " << std::hex << reg << std::endl; +        _iface->write_spi( +            UE_SPI_SS_AD9522, +            spi_config_t::EDGE_RISE, +            reg, 24 +        ); +    } + +    void calibrate_now(void){ +        //vco calibration routine: +        _ad9522_regs.vco_calibration_now = 0; +        this->send_reg(0x18); +        this->latch_regs(); +        _ad9522_regs.vco_calibration_now = 1; +        this->send_reg(0x18); +        this->latch_regs(); +        //wait for calibration done: +        static const boost::uint8_t addr = 0x01F; +        for (size_t ms10 = 0; ms10 < 100; ms10++){ +            boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +            boost::uint32_t reg = _iface->read_spi( +                UE_SPI_SS_AD9522, spi_config_t::EDGE_RISE, +                _ad9522_regs.get_read_reg(addr), 24 +            ); +            _ad9522_regs.set_reg(addr, reg); +            if (_ad9522_regs.vco_calibration_finished) goto wait_for_ld; +        } +        UHD_MSG(error) << "USRP-E100 clock control: VCO calibration timeout" << std::endl; +        wait_for_ld: +        //wait for digital lock detect: +        for (size_t ms10 = 0; ms10 < 100; ms10++){ +            boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +            boost::uint32_t reg = _iface->read_spi( +                UE_SPI_SS_AD9522, spi_config_t::EDGE_RISE, +                _ad9522_regs.get_read_reg(addr), 24 +            ); +            _ad9522_regs.set_reg(addr, reg); +            if (_ad9522_regs.digital_lock_detect) return; +        } +        UHD_MSG(error) << "USRP-E100 clock control: lock detection timeout" << std::endl; +    } + +    void soft_sync(void){ +        _ad9522_regs.soft_sync = 1; +        this->send_reg(0x230); +        this->latch_regs(); +        _ad9522_regs.soft_sync = 0; +        this->send_reg(0x230); +        this->latch_regs(); +    } + +    void send_all_regs(void){ +        //setup a list of register ranges to write +        typedef std::pair<boost::uint16_t, boost::uint16_t> range_t; +        static const std::vector<range_t> ranges = boost::assign::list_of +            (range_t(0x000, 0x000)) (range_t(0x010, 0x01F)) +            (range_t(0x0F0, 0x0FD)) (range_t(0x190, 0x19B)) +            (range_t(0x1E0, 0x1E1)) (range_t(0x230, 0x230)) +        ; + +        //write initial register values and latch/update +        BOOST_FOREACH(const range_t &range, ranges){ +            for(boost::uint16_t addr = range.first; addr <= range.second; addr++){ +                this->send_reg(addr); +            } +        } +        this->latch_regs(); +    } +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +e100_clock_ctrl::sptr e100_clock_ctrl::make(spi_iface::sptr iface, double master_clock_rate, const bool dboard_clocks_diff){ +    return sptr(new e100_clock_ctrl_impl(iface, master_clock_rate, dboard_clocks_diff)); +} diff --git a/host/lib/usrp/e100/clock_ctrl.hpp b/host/lib/usrp/e100/clock_ctrl.hpp new file mode 100644 index 000000000..803265556 --- /dev/null +++ b/host/lib/usrp/e100/clock_ctrl.hpp @@ -0,0 +1,127 @@ +// +// 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_USRP_E100_CLOCK_CTRL_HPP +#define INCLUDED_USRP_E100_CLOCK_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp-e clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class e100_clock_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<e100_clock_ctrl> sptr; + +    /*! +     * Make a new clock control object. +     * \param iface the spi iface object +     * \param master clock rate the FPGA rate +     * param dboard_clocks_diff are they differential? +     * \return the clock control object +     */ +    static sptr make(uhd::spi_iface::sptr iface, double master_clock_rate, const bool dboard_clocks_diff); + +    /*! +     * Set the rate of the fpga clock line. +     * Throws if rate is not valid. +     * \param rate the new rate in Hz +     */ +    virtual void set_fpga_clock_rate(double rate) = 0; + +    /*! +     * Get the rate of the fpga clock line. +     * \return the fpga clock rate in Hz +     */ +    virtual double get_fpga_clock_rate(void) = 0; + +    /*! +     * Get the possible rates of the rx dboard clock. +     * \return a vector of clock rates in Hz +     */ +    virtual std::vector<double> get_rx_dboard_clock_rates(void) = 0; + +    /*! +     * Get the possible rates of the tx dboard clock. +     * \return a vector of clock rates in Hz +     */ +    virtual std::vector<double> get_tx_dboard_clock_rates(void) = 0; + +    /*! +     * Set the rx dboard clock rate to a possible rate. +     * \param rate the new clock rate in Hz +     * \throw exception when rate cannot be achieved +     */ +    virtual void set_rx_dboard_clock_rate(double rate) = 0; + +    /*! +     * Set the tx dboard clock rate to a possible rate. +     * \param rate the new clock rate in Hz +     * \throw exception when rate cannot be achieved +     */ +    virtual void set_tx_dboard_clock_rate(double rate) = 0; + +    /*! +     * Get the current rx dboard clock rate. +     * \return the clock rate in Hz +     */ +    virtual double get_rx_clock_rate(void) = 0; + +    /*! +     * Get the current tx dboard clock rate. +     * \return the clock rate in Hz +     */ +    virtual double get_tx_clock_rate(void) = 0; + +    /*! +     * Enable/disable the rx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_rx_dboard_clock(bool enb) = 0; + +    /*! +     * Enable/disable the tx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_tx_dboard_clock(bool enb) = 0; +     +    /*! +     * Use the internal TCXO reference +     */ +    virtual void use_internal_ref(void) = 0; +     +    /*! +     * Use the external SMA reference +     */ +    virtual void use_external_ref(void) = 0; +     +    /*! +     * Use external if available, internal otherwise +     */ +    virtual void use_auto_ref(void) = 0; + +    //! Is the reference locked? +    virtual bool get_locked(void) = 0; + +}; + +#endif /* INCLUDED_USRP_E100_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/e100/codec_ctrl.cpp b/host/lib/usrp/e100/codec_ctrl.cpp new file mode 100644 index 000000000..13b3bc951 --- /dev/null +++ b/host/lib/usrp/e100/codec_ctrl.cpp @@ -0,0 +1,288 @@ +// +// 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 "ad9862_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/cstdint.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include "e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> + +using namespace uhd; + +const gain_range_t e100_codec_ctrl::tx_pga_gain_range(-20, 0, double(0.1)); +const gain_range_t e100_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class e100_codec_ctrl_impl : public e100_codec_ctrl{ +public: +    //structors +    e100_codec_ctrl_impl(spi_iface::sptr iface); +    ~e100_codec_ctrl_impl(void); + +    //aux adc and dac control +    double read_aux_adc(aux_adc_t which); +    void write_aux_dac(aux_dac_t which, double volts); + +    //pga gain control +    void set_tx_pga_gain(double); +    double get_tx_pga_gain(void); +    void set_rx_pga_gain(double, char); +    double get_rx_pga_gain(char); + +private: +    spi_iface::sptr _iface; +    ad9862_regs_t _ad9862_regs; +    void send_reg(boost::uint8_t addr); +    void recv_reg(boost::uint8_t addr); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +e100_codec_ctrl_impl::e100_codec_ctrl_impl(spi_iface::sptr iface){ +    _iface = iface; + +    //soft reset +    _ad9862_regs.soft_reset = 1; +    this->send_reg(0); + +    //initialize the codec register settings +    _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; +    _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; +    _ad9862_regs.soft_reset = 0; + +    //setup rx side of codec +    _ad9862_regs.byp_buffer_a = 0; +    _ad9862_regs.byp_buffer_b = 0; +    _ad9862_regs.buffer_a_pd = 0; +    _ad9862_regs.buffer_b_pd = 0; +    _ad9862_regs.rx_pga_a = 0;//0x1f;  //TODO bring under api control +    _ad9862_regs.rx_pga_b = 0;//0x1f;  //TODO bring under api control +    _ad9862_regs.rx_twos_comp = 1; +    _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; +    _ad9862_regs.shared_ref = 1; + +    //setup tx side of codec +    _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; +    _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; +    _ad9862_regs.tx_retime = ad9862_regs_t::TX_RETIME_CLKOUT2; +    _ad9862_regs.tx_pga_gain = 199; //TODO bring under api control +    _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; +    _ad9862_regs.interp = ad9862_regs_t::INTERP_2; +    _ad9862_regs.tx_twos_comp = 1; +    _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_BYPASS; +    _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; +    _ad9862_regs.dac_a_coarse_gain = 0x3; +    _ad9862_regs.dac_b_coarse_gain = 0x3; +    _ad9862_regs.edges = ad9862_regs_t::EDGES_NORMAL; + +    //setup the dll +    _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; +    _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; +    _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; +    _ad9862_regs.hs_duty_cycle = 1; +    _ad9862_regs.clk_duty = 1; + +    //disable clkout1 and clkout2 +    _ad9862_regs.dis1 = ad9862_regs_t::DIS1_DIS; +    //_ad9862_regs.dis2 = ad9862_regs_t::DIS2_DIS; needed for transmit + +    //write the register settings to the codec +    for (uint8_t addr = 0; addr <= 25; addr++){ +        this->send_reg(addr); +    } + +    //always start conversions for aux ADC +    _ad9862_regs.start_a = 1; +    _ad9862_regs.start_b = 1; + +    //aux adc clock +    _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; +    this->send_reg(34); +    this->send_reg(35); +} + +e100_codec_ctrl_impl::~e100_codec_ctrl_impl(void){ +    //set aux dacs to zero +    this->write_aux_dac(AUX_DAC_A, 0); +    this->write_aux_dac(AUX_DAC_B, 0); +    this->write_aux_dac(AUX_DAC_C, 0); +    this->write_aux_dac(AUX_DAC_D, 0); + +    //power down +    _ad9862_regs.all_rx_pd = 1; +    this->send_reg(1); +    _ad9862_regs.tx_digital_pd = 1; +    _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; +    this->send_reg(8); +} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void e100_codec_ctrl_impl::set_tx_pga_gain(double gain){ +    int gain_word = int(mtpgw*(gain - tx_pga_gain_range.start())/(tx_pga_gain_range.stop() - tx_pga_gain_range.start())); +    _ad9862_regs.tx_pga_gain = uhd::clip(gain_word, 0, mtpgw); +    this->send_reg(16); +} + +double e100_codec_ctrl_impl::get_tx_pga_gain(void){ +    return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.stop() - tx_pga_gain_range.start())/mtpgw) + tx_pga_gain_range.start(); +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void e100_codec_ctrl_impl::set_rx_pga_gain(double gain, char which){ +    int gain_word = int(mrpgw*(gain - rx_pga_gain_range.start())/(rx_pga_gain_range.stop() - rx_pga_gain_range.start())); +    gain_word = uhd::clip(gain_word, 0, mrpgw); +    switch(which){ +    case 'A': +        _ad9862_regs.rx_pga_a = gain_word; +        this->send_reg(2); +        return; +    case 'B': +        _ad9862_regs.rx_pga_b = gain_word; +        this->send_reg(3); +        return; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +double e100_codec_ctrl_impl::get_rx_pga_gain(char which){ +    int gain_word; +    switch(which){ +    case 'A': gain_word = _ad9862_regs.rx_pga_a; break; +    case 'B': gain_word = _ad9862_regs.rx_pga_b; break; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +    return (gain_word*(rx_pga_gain_range.stop() - rx_pga_gain_range.start())/mrpgw) + rx_pga_gain_range.start(); +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static double aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low){ +    return double((boost::uint16_t(high) << 2) | low)*3.3/0x3ff; +} + +double e100_codec_ctrl_impl::read_aux_adc(aux_adc_t which){ +    switch(which){ +    case AUX_ADC_A1: +        _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC1; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(28); //read the value (2 bytes, 2 reads) +        this->recv_reg(29); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); + +    case AUX_ADC_A2: +        _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC2; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(26); //read the value (2 bytes, 2 reads) +        this->recv_reg(27); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); + +    case AUX_ADC_B1: +        _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC1; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(32); //read the value (2 bytes, 2 reads) +        this->recv_reg(33); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); + +    case AUX_ADC_B2: +        _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC2; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(30); //read the value (2 bytes, 2 reads) +        this->recv_reg(31); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void e100_codec_ctrl_impl::write_aux_dac(aux_dac_t which, double volts){ +    //special case for aux dac d (aka sigma delta word) +    if (which == AUX_DAC_D){ +        boost::uint16_t dac_word = uhd::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); +        _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); +        _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); +        this->send_reg(42); +        this->send_reg(43); +        return; +    } + +    //calculate the dac word for aux dac a, b, c +    boost::uint8_t dac_word = uhd::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + +    //setup a lookup table for the aux dac params (reg ref, reg addr) +    typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; +    uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of +        (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) +        (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) +        (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) +    ; + +    //set the aux dac register +    UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); +    boost::uint8_t *reg_ref, reg_addr; +    boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; +    *reg_ref = dac_word; +    this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void e100_codec_ctrl_impl::send_reg(boost::uint8_t addr){ +    boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); +    UHD_LOGV(often) << "codec control write reg: " << std::hex << reg << std::endl; +    _iface->write_spi( +        UE_SPI_SS_AD9862, +        spi_config_t::EDGE_RISE, +        reg, 16 +    ); +} + +void e100_codec_ctrl_impl::recv_reg(boost::uint8_t addr){ +    boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); +    UHD_LOGV(often) << "codec control read reg: " << std::hex << reg << std::endl; +    boost::uint32_t ret = _iface->read_spi( +        UE_SPI_SS_AD9862, +        spi_config_t::EDGE_RISE, +        reg, 16 +    ); +    UHD_LOGV(often) << "codec control read ret: " << std::hex << ret << std::endl; +    _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +e100_codec_ctrl::sptr e100_codec_ctrl::make(spi_iface::sptr iface){ +    return sptr(new e100_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/e100/codec_ctrl.hpp b/host/lib/usrp/e100/codec_ctrl.hpp new file mode 100644 index 000000000..707f6f521 --- /dev/null +++ b/host/lib/usrp/e100/codec_ctrl.hpp @@ -0,0 +1,90 @@ +// +// 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_USRP_E100_CODEC_CTRL_HPP +#define INCLUDED_USRP_E100_CODEC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp-e codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class e100_codec_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<e100_codec_ctrl> sptr; + +    static const uhd::gain_range_t tx_pga_gain_range; +    static const uhd::gain_range_t rx_pga_gain_range; + +    /*! +     * Make a new codec control object. +     * \param iface the spi iface object +     * \return the codec control object +     */ +    static sptr make(uhd::spi_iface::sptr iface); + +    //! aux adc identifier constants +    enum aux_adc_t{ +        AUX_ADC_A2 = 0xA2, +        AUX_ADC_A1 = 0xA1, +        AUX_ADC_B2 = 0xB2, +        AUX_ADC_B1 = 0xB1 +    }; + +    /*! +     * Read an auxiliary adc: +     * The internals remember which aux adc was read last. +     * Therefore, the aux adc switch is only changed as needed. +     * \param which which of the 4 adcs +     * \return a value in volts +     */ +    virtual double read_aux_adc(aux_adc_t which) = 0; + +    //! aux dac identifier constants +    enum aux_dac_t{ +        AUX_DAC_A = 0xA, +        AUX_DAC_B = 0xB, +        AUX_DAC_C = 0xC, +        AUX_DAC_D = 0xD //really the sigma delta output +    }; + +    /*! +     * Write an auxiliary dac. +     * \param which which of the 4 dacs +     * \param volts the level in in volts +     */ +    virtual void write_aux_dac(aux_dac_t which, double volts) = 0; + +    //! Set the TX PGA gain +    virtual void set_tx_pga_gain(double gain) = 0; + +    //! Get the TX PGA gain +    virtual double get_tx_pga_gain(void) = 0; + +    //! Set the RX PGA gain ('A' or 'B') +    virtual void set_rx_pga_gain(double gain, char which) = 0; + +    //! Get the RX PGA gain ('A' or 'B') +    virtual double get_rx_pga_gain(char which) = 0; +}; + +#endif /* INCLUDED_USRP_E100_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/e100/dboard_iface.cpp b/host/lib/usrp/e100/dboard_iface.cpp new file mode 100644 index 000000000..532b2dc9e --- /dev/null +++ b/host/lib/usrp/e100/dboard_iface.cpp @@ -0,0 +1,258 @@ +// +// 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 <uhd/types/serial.hpp> +#include "e100_regs.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> +#include <linux/usrp_e.h> //i2c and spi constants + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class e100_dboard_iface : public dboard_iface{ +public: + +    e100_dboard_iface( +        wb_iface::sptr wb_iface, +        i2c_iface::sptr i2c_iface, +        spi_iface::sptr spi_iface, +        e100_clock_ctrl::sptr clock, +        e100_codec_ctrl::sptr codec +    ){ +        _wb_iface = wb_iface; +        _i2c_iface = i2c_iface; +        _spi_iface = spi_iface; +        _clock = clock; +        _codec = codec; +        _gpio = gpio_core_200::make(_wb_iface, TOREG(SR_GPIO), REG_RB_GPIO); + +        //init the clock rate shadows +        this->set_clock_rate(UNIT_RX, _clock->get_fpga_clock_rate()); +        this->set_clock_rate(UNIT_TX, _clock->get_fpga_clock_rate()); +    } + +    ~e100_dboard_iface(void){ +        /* NOP */ +    } + +    special_props_t get_special_props(void){ +        special_props_t props; +        props.soft_clock_divider = false; +        props.mangle_i2c_addrs = false; +        return props; +    } + +    void write_aux_dac(unit_t, aux_dac_t, 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 write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    boost::uint32_t read_write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    void set_clock_rate(unit_t, double); +    std::vector<double> get_clock_rates(unit_t); +    double get_clock_rate(unit_t); +    void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t); + +private: +    wb_iface::sptr _wb_iface; +    i2c_iface::sptr _i2c_iface; +    spi_iface::sptr _spi_iface; +    e100_clock_ctrl::sptr _clock; +    e100_codec_ctrl::sptr _codec; +    gpio_core_200::sptr _gpio; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_e100_dboard_iface( +    wb_iface::sptr wb_iface, +    i2c_iface::sptr i2c_iface, +    spi_iface::sptr spi_iface, +    e100_clock_ctrl::sptr clock, +    e100_codec_ctrl::sptr codec +){ +    return dboard_iface::sptr(new e100_dboard_iface(wb_iface, i2c_iface, spi_iface, clock, codec)); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +void e100_dboard_iface::set_clock_rate(unit_t unit, double rate){ +    switch(unit){ +    case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); +    case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); +    } +} + +std::vector<double> e100_dboard_iface::get_clock_rates(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _clock->get_rx_dboard_clock_rates(); +    case UNIT_TX: return _clock->get_tx_dboard_clock_rates(); +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +double e100_dboard_iface::get_clock_rate(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _clock->get_rx_clock_rate(); +    case UNIT_TX: return _clock->get_tx_clock_rate(); +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +void e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ +    switch(unit){ +    case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); +    case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); +    } +} + +double e100_dboard_iface::get_codec_rate(unit_t){ +    return _clock->get_fpga_clock_rate(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void e100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ +    return _gpio->set_pin_ctrl(unit, value); +} + +void e100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ +    return _gpio->set_gpio_ddr(unit, value); +} + +void e100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ +    return _gpio->set_gpio_out(unit, value); +} + +boost::uint16_t e100_dboard_iface::read_gpio(unit_t unit){ +    return _gpio->read_gpio(unit); +} + +void e100_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 e100_dboard_iface::set_gpio_debug(unit_t, int){ +    throw uhd::not_implemented_error("no set_gpio_debug implemented"); +} + +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ +    switch(unit){ +    case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB; +    case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +void e100_dboard_iface::write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    _spi_iface->write_spi(unit_to_otw_spi_dev(unit), config, data, num_bits); +} + +boost::uint32_t e100_dboard_iface::read_write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    return _spi_iface->read_spi(unit_to_otw_spi_dev(unit), config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void e100_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +    return _i2c_iface->write_i2c(addr, bytes); +} + +byte_vector_t e100_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ +    return _i2c_iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void e100_dboard_iface::write_aux_dac(dboard_iface::unit_t, aux_dac_t which, double value){ +    //same aux dacs for each unit +    static const uhd::dict<aux_dac_t, e100_codec_ctrl::aux_dac_t> which_to_aux_dac = map_list_of +        (AUX_DAC_A, e100_codec_ctrl::AUX_DAC_A) +        (AUX_DAC_B, e100_codec_ctrl::AUX_DAC_B) +        (AUX_DAC_C, e100_codec_ctrl::AUX_DAC_C) +        (AUX_DAC_D, e100_codec_ctrl::AUX_DAC_D) +    ; +    _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +double e100_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, aux_adc_t which){ +    static const uhd::dict< +        unit_t, uhd::dict<aux_adc_t, e100_codec_ctrl::aux_adc_t> +    > unit_to_which_to_aux_adc = map_list_of +        (UNIT_RX, map_list_of +            (AUX_ADC_A, e100_codec_ctrl::AUX_ADC_A1) +            (AUX_ADC_B, e100_codec_ctrl::AUX_ADC_B1) +        ) +        (UNIT_TX, map_list_of +            (AUX_ADC_A, e100_codec_ctrl::AUX_ADC_A2) +            (AUX_ADC_B, e100_codec_ctrl::AUX_ADC_B2) +        ) +    ; +    return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/e100/e100_ctrl.cpp b/host/lib/usrp/e100/e100_ctrl.cpp new file mode 100644 index 000000000..c9c86c8af --- /dev/null +++ b/host/lib/usrp/e100/e100_ctrl.cpp @@ -0,0 +1,445 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include "e100_regs.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <sys/ioctl.h> //ioctl +#include <fcntl.h> //open, close +#include <linux/usrp_e.h> //ioctl structures and constants +#include <poll.h> //poll +#include <boost/thread/thread.hpp> //sleep +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <fstream> + +using namespace uhd; +using namespace uhd::transport; + +/*********************************************************************** + * Sysfs GPIO wrapper class + **********************************************************************/ +class gpio{ +public: +    gpio(const int num, const std::string &dir) : _num(num){ +        this->set_xport("export"); +        this->set_dir(dir); +        _value_file.open(str(boost::format("/sys/class/gpio/gpio%d/value") % num).c_str(), std::ios_base::in | std::ios_base::out); +    } +    ~gpio(void){ +        _value_file.close(); +        this->set_dir("in"); +        this->set_xport("unexport"); +    } +    void operator()(const int val){ +        _value_file << val << std::endl << std::flush; +    } +    int operator()(void){ +        std::string val; +        std::getline(_value_file, val); +        _value_file.seekg(0); +        return int(val.at(0) - '0') & 0x1; +    } +private: +    void set_xport(const std::string &xport){ +        std::ofstream export_file(("/sys/class/gpio/" + xport).c_str()); +        export_file << _num << std::endl << std::flush; +        export_file.close(); +    } +    void set_dir(const std::string &dir){ +        std::ofstream dir_file(str(boost::format("/sys/class/gpio/gpio%d/direction") % _num).c_str()); +        dir_file << dir << std::endl << std::flush; +        dir_file.close(); +    } +    const int _num; +    std::fstream _value_file; +}; + +/*********************************************************************** + * Protection for dual GPIO access - sometimes MISO, sometimes have resp + **********************************************************************/ +static boost::mutex gpio_irq_resp_mutex; + +/*********************************************************************** + * Aux spi implementation + **********************************************************************/ +class aux_spi_iface_impl : public spi_iface{ +public: +    aux_spi_iface_impl(void): +        spi_sclk_gpio(65, "out"), +        spi_sen_gpio(186, "out"), +        spi_mosi_gpio(145, "out"), +        spi_miso_gpio(147, "in") +    { +        this->spi_sen_gpio(1); //not selected +        this->spi_sclk_gpio(0); //into reset +        this->spi_sclk_gpio(1); //out of reset +    } + +    boost::uint32_t transact_spi( +        int, const spi_config_t &, //not used params +        boost::uint32_t bits, +        size_t num_bits, +        bool readback +    ){ +        boost::mutex::scoped_lock lock(gpio_irq_resp_mutex); + +        boost::uint32_t rb_bits = 0; +        this->spi_sen_gpio(0); + +        for (size_t i = 0; i < num_bits; i++){ +            this->spi_sclk_gpio(0); +            this->spi_mosi_gpio((bits >> (num_bits-i-1)) & 0x1); +            boost::this_thread::sleep(boost::posix_time::microseconds(10)); +            if (readback) rb_bits = (rb_bits << 1) | this->spi_miso_gpio(); +            this->spi_sclk_gpio(1); +            boost::this_thread::sleep(boost::posix_time::microseconds(10)); +        } + +        this->spi_sen_gpio(1); +        boost::this_thread::sleep(boost::posix_time::microseconds(100)); +        return rb_bits; +    } + +private: +    gpio spi_sclk_gpio, spi_sen_gpio, spi_mosi_gpio, spi_miso_gpio; +}; + +uhd::spi_iface::sptr e100_ctrl::make_aux_spi_iface(void){ +    return uhd::spi_iface::sptr(new aux_spi_iface_impl()); +} + +/*********************************************************************** + * I2C device node implementation wrapper + **********************************************************************/ +#include <linux/i2c-dev.h> +#include <linux/i2c.h> +class i2c_dev_iface : public i2c_iface{ +public: +    i2c_dev_iface(const std::string &node){ +        if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ +            throw uhd::io_error("Failed to open " + node); +        } +    } + +    ~i2c_dev_iface(void){ +        ::close(_node_fd); +    } + +    void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +        byte_vector_t rw_bytes(bytes); + +        //setup the message +        i2c_msg msg; +        msg.addr = addr; +        msg.flags = 0; +        msg.len = bytes.size(); +        msg.buf = &rw_bytes.front(); + +        //setup the data +        i2c_rdwr_ioctl_data data; +        data.msgs = &msg; +        data.nmsgs = 1; + +        //call the ioctl +        UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ +        byte_vector_t bytes(num_bytes); + +        //setup the message +        i2c_msg msg; +        msg.addr = addr; +        msg.flags = I2C_M_RD; +        msg.len = bytes.size(); +        msg.buf = &bytes.front(); + +        //setup the data +        i2c_rdwr_ioctl_data data; +        data.msgs = &msg; +        data.nmsgs = 1; + +        //call the ioctl +        UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); + +        return bytes; +    } + +private: int _node_fd; +}; + +uhd::i2c_iface::sptr e100_ctrl::make_dev_i2c_iface(const std::string &node){ +    return uhd::i2c_iface::sptr(new i2c_dev_iface(node)); +} + +/*********************************************************************** + * UART control implementation + **********************************************************************/ +#include <termios.h> +#include <cstring> +class uart_dev_iface : public uart_iface{ +public: +    uart_dev_iface(const std::string &node){ +        if ((_node_fd = ::open(node.c_str(), O_RDWR | O_NONBLOCK)) < 0){ +            throw uhd::io_error("Failed to open " + node); +        } + +        //init the tty settings w/ termios +        termios tio; +        std::memset(&tio,0,sizeof(tio)); +        tio.c_iflag = IGNCR; //Ignore CR +        tio.c_oflag = OPOST | ONLCR; //Map NL to CR-NL on output +        tio.c_cflag = CS8 | CREAD | CLOCAL; // 8n1 +        tio.c_lflag = 0; + +        cfsetospeed(&tio, B115200);            // 115200 baud +        cfsetispeed(&tio, B115200);            // 115200 baud + +        tcsetattr(_node_fd, TCSANOW, &tio); +    } + +    void write_uart(const std::string &buf){ +        const ssize_t ret = ::write(_node_fd, buf.c_str(), buf.size()); +        if (size_t(ret) != buf.size()) UHD_LOG << ret; +    } + +    std::string read_uart(double timeout){ +        const boost::system_time exit_time = boost::get_system_time() + boost::posix_time::milliseconds(long(timeout*1000)); + +        std::string line; +        while(true){ +            char ch; +            const ssize_t ret = ::read(_node_fd, &ch, 1); + +            //got a character -> process it +            if (ret == 1){ +                const bool flush  = ch == '\n' or ch == '\r'; +                if (flush and line.empty()) continue; //avoid flushing on empty lines +                line += std::string(1, ch); +                if (flush) break; +            } + +            //didnt get a character, check the timeout +            else if (boost::get_system_time() > exit_time){ +                break; +            } + +            //otherwise sleep for a bit +            else{ +                boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +            } +        } +        return line; +    } + +private: int _node_fd; +}; + +uhd::uart_iface::sptr e100_ctrl::make_gps_uart_iface(const std::string &node){ +    return uhd::uart_iface::sptr(new uart_dev_iface(node)); +} + +/*********************************************************************** + * Simple managed buffers + **********************************************************************/ +struct e100_simpl_mrb : managed_recv_buffer +{ +    usrp_e_ctl32 data; +    e100_ctrl *ctrl; + +    void release(void) +    { +        //NOP +    } + +    sptr get_new(void) +    { +        const size_t max_words32 = 8; //.LAST_ADDR(10'h00f)) resp_fifo_to_gpmc + +        //load the data struct +        data.offset = 0; +        data.count = max_words32; + +        //call the ioctl +        ctrl->ioctl(USRP_E_READ_CTL32, &data); + +        if (data.buf[0] == 0 or ~data.buf[0] == 0) return sptr(); //bad VRT hdr, treat like timeout + +        return make(this, data.buf, sizeof(data.buf)); +    } +}; + +struct e100_simpl_msb : managed_send_buffer +{ +    usrp_e_ctl32 data; +    e100_ctrl *ctrl; + +    void release(void) +    { +        const size_t max_words32 = 8; //.LAST_ADDR(10'h00f)) resp_fifo_to_gpmc + +        //load the data struct +        data.offset = 0; +        data.count = max_words32; + +        //call the ioctl +        ctrl->ioctl(USRP_E_WRITE_CTL32, &data); +    } + +    sptr get_new(void) +    { +        return make(this, data.buf, sizeof(data.buf)); +    } +}; + +/*********************************************************************** + * USRP-E100 control implementation + **********************************************************************/ +class e100_ctrl_impl : public e100_ctrl{ +public: + +    int get_file_descriptor(void){ +        return _node_fd; +    } + +    /******************************************************************* +     * Structors +     ******************************************************************/ +    e100_ctrl_impl(const std::string &node){ +        UHD_MSG(status) << "Opening device node " << node << "..." << std::endl; + +        //open the device node and check file descriptor +        if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ +            throw uhd::io_error("Failed to open " + node); +        } + +        //check the module compatibility number +        int module_compat_num = ::ioctl(_node_fd, USRP_E_GET_COMPAT_NUMBER, NULL); +        if (module_compat_num != USRP_E_COMPAT_NUMBER){ +            throw uhd::runtime_error(str(boost::format( +                "Expected module compatibility number 0x%x, but got 0x%x:\n" +                "The module build is not compatible with the host code build." +            ) % USRP_E_COMPAT_NUMBER % module_compat_num)); +        } + +        std::ofstream edge_file("/sys/class/gpio/gpio147/edge"); +        edge_file << "rising" << std::endl << std::flush; +        edge_file.close(); +        _irq_fd = ::open("/sys/class/gpio/gpio147/value", O_RDONLY); +        if (_irq_fd < 0) UHD_MSG(error) << "Unable to open GPIO for IRQ\n"; +    } + +    ~e100_ctrl_impl(void){ +        ::close(_irq_fd); +        ::close(_node_fd); +    } + +    /******************************************************************* +     * IOCTL: provides the communication base for all other calls +     ******************************************************************/ +    void ioctl(int request, void *mem){ +        boost::mutex::scoped_lock lock(_ioctl_mutex); + +        if (::ioctl(_node_fd, request, mem) < 0){ +            throw uhd::os_error(str( +                boost::format("ioctl failed with request %d") % request +            )); +        } +    } + +    /******************************************************************* +     * The managed buffer interface +     ******************************************************************/ +    UHD_INLINE bool resp_read(void) +    { +        //thread stuff ensures that this GPIO isnt shared +        boost::mutex::scoped_lock lock(gpio_irq_resp_mutex); + +        //perform a read of the GPIO IRQ state +        char ch; +        ::read(_irq_fd, &ch, sizeof(ch)); +        ::lseek(_irq_fd, SEEK_SET, 0); +        return ch == '1'; +    } + +    UHD_INLINE bool resp_wait(const double timeout) +    { +        //perform a check, if it fails, poll +        if (this->resp_read()) return true; + +        //poll IRQ GPIO for some action +        pollfd pfd; +        pfd.fd = _irq_fd; +        pfd.events = POLLPRI | POLLERR; +        ::poll(&pfd, 1, long(timeout*1000)/*ms*/); + +        //perform a GPIO read again for result +        return this->resp_read(); +    } + +    managed_recv_buffer::sptr get_recv_buff(double timeout) +    { +        if (not this->resp_wait(timeout)) +        { +            return managed_recv_buffer::sptr(); +        } + +        _mrb.ctrl = this; +        return _mrb.get_new(); +    } + +    managed_send_buffer::sptr get_send_buff(double) +    { +        _msb.ctrl = this; +        return _msb.get_new(); +    } + +    size_t get_num_recv_frames(void) const{ +        return 1; +    } + +    size_t get_recv_frame_size(void) const{ +        return sizeof(_mrb.data.buf); +    } + +    size_t get_num_send_frames(void) const{ +        return 1; +    } + +    size_t get_send_frame_size(void) const{ +        return sizeof(_msb.data.buf); +    } + +private: +    int _node_fd; +    int _irq_fd; +    boost::mutex _ioctl_mutex; +    e100_simpl_mrb _mrb; +    e100_simpl_msb _msb; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +e100_ctrl::sptr e100_ctrl::make(const std::string &node){ +    return sptr(new e100_ctrl_impl(node)); +} diff --git a/host/lib/usrp/e100/e100_ctrl.hpp b/host/lib/usrp/e100/e100_ctrl.hpp new file mode 100644 index 000000000..72dce134e --- /dev/null +++ b/host/lib/usrp/e100/e100_ctrl.hpp @@ -0,0 +1,47 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CTRL_HPP +#define INCLUDED_B100_CTRL_HPP + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> + +class e100_ctrl : public uhd::transport::zero_copy_if{ +public: +    typedef boost::shared_ptr<e100_ctrl> sptr; + +    //! Make a new controller for E100 +    static sptr make(const std::string &node); + +    //! Make an i2c iface for the i2c device node +    static uhd::i2c_iface::sptr make_dev_i2c_iface(const std::string &node); + +    //! Make a spi iface for the spi gpio +    static uhd::spi_iface::sptr make_aux_spi_iface(void); + +    //! Make a uart iface for the uart device node +    static uhd::uart_iface::sptr make_gps_uart_iface(const std::string &node); + +    virtual void ioctl(int request, void *mem) = 0; + +    virtual int get_file_descriptor(void) = 0; + +}; + +#endif /* INCLUDED_B100_CTRL_HPP */ diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp new file mode 100644 index 000000000..619ea8f8e --- /dev/null +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -0,0 +1,552 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "apply_corrections.hpp" +#include "e100_impl.hpp" +#include "e100_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/functional/hash.hpp> +#include <boost/assign/list_of.hpp> +#include <fstream> +#include <ctime> + +using namespace uhd; +using namespace uhd::usrp; +namespace fs = boost::filesystem; + +//////////////////////////////////////////////////////////////////////// +// I2C addresses +//////////////////////////////////////////////////////////////////////// +#define I2C_DEV_EEPROM  0x50 // 24LC02[45]:  7-bits 1010xxx +#define	I2C_ADDR_MBOARD (I2C_DEV_EEPROM | 0x0) +#define	I2C_ADDR_TX_DB  (I2C_DEV_EEPROM | 0x4) +#define	I2C_ADDR_RX_DB  (I2C_DEV_EEPROM | 0x5) + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t e100_find(const device_addr_t &hint){ +    device_addrs_t e100_addrs; + +    //return an empty list of addresses when type is set to non-usrp-e +    if (hint.has_key("type") and hint["type"] != "e100") return e100_addrs; + +    //device node not provided, assume its 0 +    if (not hint.has_key("node")){ +        device_addr_t new_addr = hint; +        new_addr["node"] = "/dev/usrp_e0"; +        return e100_find(new_addr); +    } + +    //use the given device node name +    if (fs::exists(hint["node"])){ +        device_addr_t new_addr; +        new_addr["type"] = "e100"; +        new_addr["node"] = fs::system_complete(fs::path(hint["node"])).string(); +        try{ +            i2c_iface::sptr i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); +            const mboard_eeprom_t mb_eeprom(*i2c_iface, E100_EEPROM_MAP_KEY); +            new_addr["name"] = mb_eeprom["name"]; +            new_addr["serial"] = mb_eeprom["serial"]; +        } +        catch(const std::exception &e){ +            new_addr["name"] = ""; +            new_addr["serial"] = ""; +        } +        if ( +            (not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +            (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) +        ){ +            e100_addrs.push_back(new_addr); +        } +    } + +    return e100_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr e100_make(const device_addr_t &device_addr){ +    return device::sptr(new e100_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_e100_device){ +    device::register_device(&e100_find, &e100_make); +} + +static const uhd::dict<std::string, std::string> model_to_fpga_file_name = boost::assign::map_list_of +    ("E100", "usrp_e100_fpga_v2.bin") +    ("E110", "usrp_e110_fpga.bin") +; + +/*********************************************************************** + * Structors + **********************************************************************/ +e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ +    _tree = property_tree::make(); + +    //read the eeprom so we can determine the hardware +    _dev_i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); +    const mboard_eeprom_t mb_eeprom(*_dev_i2c_iface, E100_EEPROM_MAP_KEY); + +    //determine the model string for this device +    const std::string model = device_addr.get("model", mb_eeprom.get("model", "")); +    if (not model_to_fpga_file_name.has_key(model)) throw uhd::runtime_error(str(boost::format( +        "\n" +        "  The specified model string \"%s\" is not recognized.\n" +        "  Perhaps the EEPROM is uninitialized, missing, or damaged.\n" +        "  Or, a monitor is pirating the I2C address of the EEPROM.\n" +    ) % model)); + +    //extract the fpga path and compute hash +    const std::string default_fpga_file_name = model_to_fpga_file_name[model]; +    std::string e100_fpga_image; +    try{ +        e100_fpga_image = find_image_path(device_addr.get("fpga", default_fpga_file_name)); +    } +    catch(...){ +        UHD_MSG(error) << boost::format("Could not find FPGA image. %s\n") % print_images_error(); +        throw; +    } +    e100_load_fpga(e100_fpga_image); + +    //////////////////////////////////////////////////////////////////// +    // Setup the FPGA clock over AUX SPI +    //////////////////////////////////////////////////////////////////// +    bool dboard_clocks_diff = true; +    if      (mb_eeprom.get("revision", "0") == "3") dboard_clocks_diff = false; +    else if (mb_eeprom.get("revision", "0") == "4") dboard_clocks_diff = true; +    else UHD_MSG(warning) +        << "Unknown E1XX revision number!\n" +        << "defaulting to differential dboard clocks to be safe.\n" +        << std::endl; +    const double master_clock_rate = device_addr.cast<double>("master_clock_rate", E100_DEFAULT_CLOCK_RATE); +    _aux_spi_iface = e100_ctrl::make_aux_spi_iface(); +    _clock_ctrl = e100_clock_ctrl::make(_aux_spi_iface, master_clock_rate, dboard_clocks_diff); + +    //////////////////////////////////////////////////////////////////// +    // setup the main interface into fpga +    //  - do this after aux spi, because we share gpio147 +    //////////////////////////////////////////////////////////////////// +    const std::string node = device_addr["node"]; +    _fpga_ctrl = e100_ctrl::make(node); + +    //////////////////////////////////////////////////////////////////// +    // Initialize FPGA control communication +    //////////////////////////////////////////////////////////////////// +    fifo_ctrl_excelsior_config fifo_ctrl_config; +    fifo_ctrl_config.async_sid_base = E100_TX_ASYNC_SID; +    fifo_ctrl_config.num_async_chan = 1; +    fifo_ctrl_config.ctrl_sid_base = E100_CTRL_MSG_SID; +    fifo_ctrl_config.spi_base = TOREG(SR_SPI); +    fifo_ctrl_config.spi_rb = REG_RB_SPI; +    _fifo_ctrl = fifo_ctrl_excelsior::make(_fpga_ctrl, fifo_ctrl_config); + +    //Perform wishbone readback tests, these tests also write the hash +    bool test_fail = false; +    UHD_MSG(status) << "Performing control readback test... " << std::flush; +    size_t hash = time(NULL); +    for (size_t i = 0; i < 100; i++){ +        boost::hash_combine(hash, i); +        _fifo_ctrl->poke32(TOREG(SR_MISC+0), boost::uint32_t(hash)); +        test_fail = _fifo_ctrl->peek32(REG_RB_CONFIG0) != boost::uint32_t(hash); +        if (test_fail) break; //exit loop on any failure +    } +    UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; + +    if (test_fail) UHD_MSG(error) << boost::format( +        "The FPGA is either clocked improperly\n" +        "or the FPGA build is not compatible.\n" +        "Subsequent errors may follow...\n" +    ); + +    //check that the compatibility is correct +    this->check_fpga_compat(); + +    //////////////////////////////////////////////////////////////////// +    // Create controller objects +    //////////////////////////////////////////////////////////////////// +    _fpga_i2c_ctrl = i2c_core_200::make(_fifo_ctrl, TOREG(SR_I2C), REG_RB_I2C); +    _data_transport = e100_make_mmap_zero_copy(_fpga_ctrl); + +    //////////////////////////////////////////////////////////////////// +    // Initialize the properties tree +    //////////////////////////////////////////////////////////////////// +    _tree->create<std::string>("/name").set("E-Series Device"); +    const fs_path mb_path = "/mboards/0"; +    _tree->create<std::string>(mb_path / "name").set(model); +    _tree->create<std::string>(mb_path / "codename").set("Euwanee"); + +    //////////////////////////////////////////////////////////////////// +    // setup the mboard eeprom +    //////////////////////////////////////////////////////////////////// +    _tree->create<mboard_eeprom_t>(mb_path / "eeprom") +        .set(mb_eeprom) +        .subscribe(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create clock control objects +    //////////////////////////////////////////////////////////////////// +    //^^^ clock created up top, just reg props here... ^^^ +    _tree->create<double>(mb_path / "tick_rate") +        .publish(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) +        .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) +        .subscribe(boost::bind(&e100_impl::update_tick_rate, this, _1)); + +    //subscribe the command time while we are at it +    _tree->create<time_spec_t>(mb_path / "time/cmd") +        .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create codec control objects +    //////////////////////////////////////////////////////////////////// +    _codec_ctrl = e100_codec_ctrl::make(_fifo_ctrl/*spi*/); +    const fs_path rx_codec_path = mb_path / "rx_codecs/A"; +    const fs_path tx_codec_path = mb_path / "tx_codecs/A"; +    _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); +    _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(e100_codec_ctrl::rx_pga_gain_range); +    _tree->create<double>(rx_codec_path / "gains/pga/value") +        .coerce(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); +    _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); +    _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(e100_codec_ctrl::tx_pga_gain_range); +    _tree->create<double>(tx_codec_path / "gains/pga/value") +        .subscribe(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) +        .publish(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); + +    //////////////////////////////////////////////////////////////////// +    // and do the misc mboard sensors +    //////////////////////////////////////////////////////////////////// +    _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") +        .publish(boost::bind(&e100_impl::get_ref_locked, this)); + +    //////////////////////////////////////////////////////////////////// +    // Create the GPSDO control +    //////////////////////////////////////////////////////////////////// +    static const fs::path GPSDO_VOLATILE_PATH("/media/ram/e100_internal_gpsdo.cache"); +    if (not fs::exists(GPSDO_VOLATILE_PATH)) +    { +        UHD_MSG(status) << "Detecting internal GPSDO.... " << std::flush; +        try{ +            _gps = gps_ctrl::make(e100_ctrl::make_gps_uart_iface(E100_UART_DEV_NODE)); +        } +        catch(std::exception &e){ +            UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; +        } +        if (_gps and _gps->gps_detected()) +        { +            UHD_MSG(status) << "found" << std::endl; +            BOOST_FOREACH(const std::string &name, _gps->get_sensors()) +            { +                _tree->create<sensor_value_t>(mb_path / "sensors" / name) +                    .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name)); +            } +        } +        else +        { +            UHD_MSG(status) << "not found" << std::endl; +            std::ofstream(GPSDO_VOLATILE_PATH.string().c_str(), std::ofstream::binary) << "42" << std::endl; +        } +    } + +    //////////////////////////////////////////////////////////////////// +    // create frontend control objects +    //////////////////////////////////////////////////////////////////// +    _rx_fe = rx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_RX_FE)); +    _tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE)); + +    _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") +        .subscribe(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); +    _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") +        .subscribe(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); + +    const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; +    const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; + +    _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") +        .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) +        .set(std::complex<double>(0.0, 0.0)); +    _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") +        .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) +        .set(true); +    _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") +        .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) +        .set(std::complex<double>(0.0, 0.0)); +    _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") +        .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) +        .set(std::complex<double>(0.0, 0.0)); +    _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") +        .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) +        .set(std::complex<double>(0.0, 0.0)); + +    //////////////////////////////////////////////////////////////////// +    // create rx dsp control objects +    //////////////////////////////////////////////////////////////////// +    const size_t num_rx_dsps = _fifo_ctrl->peek32(REG_RB_NUM_RX_DSP); +    for (size_t dspno = 0; dspno < num_rx_dsps; dspno++) +    { +        const size_t sr_off = dspno*32; +        _rx_dsps.push_back(rx_dsp_core_200::make( +            _fifo_ctrl, +            TOREG(SR_RX_DSP0+sr_off), +            TOREG(SR_RX_CTRL0+sr_off), +            E100_RX_SID_BASE + dspno +        )); + +        _rx_dsps[dspno]->set_link_rate(E100_RX_LINK_RATE_BPS); +        _tree->access<double>(mb_path / "tick_rate") +            .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); +        fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); +        _tree->create<meta_range_t>(rx_dsp_path / "rate/range") +            .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); +        _tree->create<double>(rx_dsp_path / "rate/value") +            .set(1e6) //some default +            .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) +            .subscribe(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1)); +        _tree->create<double>(rx_dsp_path / "freq/value") +            .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); +        _tree->create<meta_range_t>(rx_dsp_path / "freq/range") +            .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); +        _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") +            .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); +    } + +    //////////////////////////////////////////////////////////////////// +    // create tx dsp control objects +    //////////////////////////////////////////////////////////////////// +    _tx_dsp = tx_dsp_core_200::make( +        _fifo_ctrl, TOREG(SR_TX_DSP), TOREG(SR_TX_CTRL), E100_TX_ASYNC_SID +    ); +    _tx_dsp->set_link_rate(E100_TX_LINK_RATE_BPS); +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); +    _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") +        .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); +    _tree->create<double>(mb_path / "tx_dsps/0/rate/value") +        .set(1e6) //some default +        .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) +        .subscribe(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1)); +    _tree->create<double>(mb_path / "tx_dsps/0/freq/value") +        .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); +    _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") +        .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + +    //////////////////////////////////////////////////////////////////// +    // create time control objects +    //////////////////////////////////////////////////////////////////// +    time64_core_200::readback_bases_type time64_rb_bases; +    time64_rb_bases.rb_hi_now = REG_RB_TIME_NOW_HI; +    time64_rb_bases.rb_lo_now = REG_RB_TIME_NOW_LO; +    time64_rb_bases.rb_hi_pps = REG_RB_TIME_PPS_HI; +    time64_rb_bases.rb_lo_pps = REG_RB_TIME_PPS_LO; +    _time64 = time64_core_200::make( +        _fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases +    ); +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); +    _tree->create<time_spec_t>(mb_path / "time/now") +        .publish(boost::bind(&time64_core_200::get_time_now, _time64)) +        .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); +    _tree->create<time_spec_t>(mb_path / "time/pps") +        .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) +        .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); +    //setup time source props +    _tree->create<std::string>(mb_path / "time_source/value") +        .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); +    _tree->create<std::vector<std::string> >(mb_path / "time_source/options") +        .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); +    //setup reference source props +    _tree->create<std::string>(mb_path / "clock_source/value") +        .subscribe(boost::bind(&e100_impl::update_clock_source, this, _1)); +    std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); +    if (_gps and _gps->gps_detected()) clock_sources.push_back("gpsdo"); +    _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); + +    //////////////////////////////////////////////////////////////////// +    // create user-defined control objects +    //////////////////////////////////////////////////////////////////// +    _user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS)); +    _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") +        .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + +    //////////////////////////////////////////////////////////////////// +    // 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(*_fpga_i2c_ctrl, I2C_ADDR_RX_DB); +    tx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB); +    gdb_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB ^ 5); + +    //disable rx dc offset if LFRX +    if (rx_db_eeprom.id == 0x000f) _tree->access<bool>(rx_fe_path / "dc_offset" / "enable").set(false); + +    //create the properties and register subscribers +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") +        .set(rx_db_eeprom) +        .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") +        .set(tx_db_eeprom) +        .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") +        .set(gdb_eeprom) +        .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); + +    //create a new dboard interface and manager +    _dboard_iface = make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl); +    _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); +    _dboard_manager = dboard_manager::make( +        rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, +        _dboard_iface, _tree->subtree(mb_path / "dboards/A") +    ); + +    //bind frontend corrections to the dboard freq props +    const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; +    BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ +        _tree->access<double>(db_tx_fe_path / name / "freq" / "value") +            .subscribe(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1)); +    } +    const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; +    BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ +        _tree->access<double>(db_rx_fe_path / name / "freq" / "value") +            .subscribe(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1)); +    } + +    //initialize io handling +    _recv_demuxer = recv_packet_demuxer::make(_data_transport, _rx_dsps.size(), E100_RX_SID_BASE); + +    //allocate streamer weak ptrs containers +    _rx_streamers.resize(_rx_dsps.size()); +    _tx_streamers.resize(1/*known to be 1 dsp*/); + +    //////////////////////////////////////////////////////////////////// +    // do some post-init tasks +    //////////////////////////////////////////////////////////////////// +    this->update_rates(); + +    _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter +        .subscribe(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + +    //reset cordic rates and their properties to zero +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ +        _tree->access<double>(mb_path / "rx_dsps" / name / "freq" / "value").set(0.0); +    } +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ +        _tree->access<double>(mb_path / "tx_dsps" / name / "freq" / "value").set(0.0); +    } + +    _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(mb_path / "dboards/A/rx_frontends").at(0))); +    _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(mb_path / "dboards/A/tx_frontends").at(0))); +    _tree->access<std::string>(mb_path / "clock_source/value").set("internal"); +    _tree->access<std::string>(mb_path / "time_source/value").set("none"); + +    //GPS installed: use external ref, time, and init time spec +    if (_gps and _gps->gps_detected()){ +        _time64->enable_gpsdo(); +        UHD_MSG(status) << "Setting references to the internal GPSDO" << std::endl; +        _tree->access<std::string>(mb_path / "time_source/value").set("gpsdo"); +        _tree->access<std::string>(mb_path / "clock_source/value").set("gpsdo"); +        UHD_MSG(status) << "Initializing time to the internal GPSDO" << std::endl; +        _time64->set_time_next_pps(time_spec_t(time_t(_gps->get_sensor("gps_time").to_int()+1))); +    } + +} + +e100_impl::~e100_impl(void){ +    /* NOP */ +} + +double e100_impl::update_rx_codec_gain(const double gain){ +    //set gain on both I and Q, readback on one +    //TODO in the future, gains should have individual control +    _codec_ctrl->set_rx_pga_gain(gain, 'A'); +    _codec_ctrl->set_rx_pga_gain(gain, 'B'); +    return _codec_ctrl->get_rx_pga_gain('A'); +} + +void e100_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom){ +    mb_eeprom.commit(*_dev_i2c_iface, E100_EEPROM_MAP_KEY); +} + +void e100_impl::set_db_eeprom(const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ +    if (type == "rx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_RX_DB); +    if (type == "tx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB); +    if (type == "gdb") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB ^ 5); +} + +void e100_impl::update_clock_source(const std::string &source){ + +    if (source == "pps_sync"){ +        _clock_ctrl->use_external_ref(); +        _fifo_ctrl->poke32(TOREG(SR_MISC+2), 1); +        return; +    } +    if (source == "_pps_sync_"){ +        _clock_ctrl->use_external_ref(); +        _fifo_ctrl->poke32(TOREG(SR_MISC+2), 3); +        return; +    } +    _fifo_ctrl->poke32(TOREG(SR_MISC+2), 0); + +    if      (source == "auto")     _clock_ctrl->use_auto_ref(); +    else if (source == "internal") _clock_ctrl->use_internal_ref(); +    else if (source == "external") _clock_ctrl->use_external_ref(); +    else if (source == "gpsdo")    _clock_ctrl->use_external_ref(); +    else throw uhd::runtime_error("unhandled clock configuration reference source: " + source); +} + +sensor_value_t e100_impl::get_ref_locked(void){ +    const bool lock = _clock_ctrl->get_locked(); +    return sensor_value_t("Ref", lock, "locked", "unlocked"); +} + +void e100_impl::check_fpga_compat(void){ +    const boost::uint32_t fpga_compat_num = _fifo_ctrl->peek32(REG_RB_COMPAT); +    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 != E100_FPGA_COMPAT_NUM){ +        throw uhd::runtime_error(str(boost::format( +            "Expected FPGA compatibility number %d, but got %d:\n" +            "The FPGA build is not compatible with the host code build." +        ) % int(E100_FPGA_COMPAT_NUM) % fpga_major)); +    } +    if (fpga_minor < 2){ +        throw uhd::runtime_error(str(boost::format( +            "Expected FPGA compatibility minor number at least %d, but got %d:\n" +            "The FPGA build is not compatible with the host code build." +        ) % int(2) % fpga_minor)); +    } +    _tree->create<std::string>("/mboards/0/fpga_version").set(str(boost::format("%u.%u") % fpga_major % fpga_minor)); +} + +void e100_impl::set_rx_fe_corrections(const double lo_freq){ +    apply_rx_fe_corrections(this->get_tree()->subtree("/mboards/0"), "A", lo_freq); +} + +void e100_impl::set_tx_fe_corrections(const double lo_freq){ +    apply_tx_fe_corrections(this->get_tree()->subtree("/mboards/0"), "A", lo_freq); +} diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp new file mode 100644 index 000000000..6f64d4b80 --- /dev/null +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -0,0 +1,139 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "i2c_core_200.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 "fifo_ctrl_excelsior.hpp" +#include "user_settings_core_200.hpp" +#include "recv_packet_demuxer.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/weak_ptr.hpp> + +#ifndef INCLUDED_E100_IMPL_HPP +#define INCLUDED_E100_IMPL_HPP + +uhd::transport::zero_copy_if::sptr e100_make_mmap_zero_copy(e100_ctrl::sptr iface); + +// = gpmc_clock_rate/clk_div/cycles_per_transaction*bytes_per_transaction +static const double          E100_RX_LINK_RATE_BPS = 166e6/3/2*2; +static const double          E100_TX_LINK_RATE_BPS = 166e6/3/1*2; +static const std::string     E100_I2C_DEV_NODE = "/dev/i2c-3"; +static const std::string     E100_UART_DEV_NODE = "/dev/ttyO0"; +static const boost::uint16_t E100_FPGA_COMPAT_NUM = 11; +static const boost::uint32_t E100_RX_SID_BASE = 30; +static const boost::uint32_t E100_TX_ASYNC_SID = 10; +static const boost::uint32_t E100_CTRL_MSG_SID = 20; +static const double          E100_DEFAULT_CLOCK_RATE = 64e6; +static const std::string     E100_EEPROM_MAP_KEY = "E100"; + +//! load an fpga image from a bin file into the usrp-e fpga +extern void e100_load_fpga(const std::string &bin_file); + +//! Make an e100 dboard interface +uhd::usrp::dboard_iface::sptr make_e100_dboard_iface( +    wb_iface::sptr wb_iface, +    uhd::i2c_iface::sptr i2c_iface, +    uhd::spi_iface::sptr spi_iface, +    e100_clock_ctrl::sptr clock, +    e100_codec_ctrl::sptr codec +); + +/*! + * USRP-E100 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class e100_impl : public uhd::device{ +public: +    //structors +    e100_impl(const uhd::device_addr_t &); +    ~e100_impl(void); + +    //the io interface +    uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); +    uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); +    bool recv_async_msg(uhd::async_metadata_t &, double); + +private: +    uhd::property_tree::sptr _tree; + +    //controllers +    fifo_ctrl_excelsior::sptr _fifo_ctrl; +    i2c_core_200::sptr _fpga_i2c_ctrl; +    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; +    user_settings_core_200::sptr _user; +    e100_clock_ctrl::sptr _clock_ctrl; +    e100_codec_ctrl::sptr _codec_ctrl; +    e100_ctrl::sptr _fpga_ctrl; +    uhd::i2c_iface::sptr _dev_i2c_iface; +    uhd::spi_iface::sptr _aux_spi_iface; +    uhd::gps_ctrl::sptr _gps; + +    //transports +    uhd::transport::zero_copy_if::sptr _data_transport; +    uhd::usrp::recv_packet_demuxer::sptr _recv_demuxer; + +    //dboard stuff +    uhd::usrp::dboard_manager::sptr _dboard_manager; +    uhd::usrp::dboard_iface::sptr _dboard_iface; + +    //device properties interface +    uhd::property_tree::sptr get_tree(void) const{ +        return _tree; +    } + +    std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; +    std::vector<boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; + +    double update_rx_codec_gain(const double); //sets A and B at once +    void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); +    void set_db_eeprom(const std::string &, const uhd::usrp::dboard_eeprom_t &); +    void update_tick_rate(const double rate); +    void update_rx_samp_rate(const size_t, const double rate); +    void update_tx_samp_rate(const size_t, const double rate); +    void update_rates(void); +    void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void update_clock_source(const std::string &); +    uhd::sensor_value_t get_ref_locked(void); +    void check_fpga_compat(void); +    void set_rx_fe_corrections(const double); +    void set_tx_fe_corrections(const double); + +}; + +#endif /* INCLUDED_E100_IMPL_HPP */ diff --git a/host/lib/usrp/e100/e100_mmap_zero_copy.cpp b/host/lib/usrp/e100/e100_mmap_zero_copy.cpp new file mode 100644 index 000000000..57e4e32d9 --- /dev/null +++ b/host/lib/usrp/e100/e100_mmap_zero_copy.cpp @@ -0,0 +1,262 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include <uhd/transport/zero_copy.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/exception.hpp> +#include <boost/make_shared.hpp> +#include <linux/usrp_e.h> +#include <sys/mman.h> //mmap +#include <unistd.h> //getpagesize +#include <poll.h> //poll +#include <vector> + +using namespace uhd; +using namespace uhd::transport; + +#define fp_verbose false //fast-path verbose +static const size_t poll_breakout = 10; //how many poll timeouts constitute a full timeout + +/*********************************************************************** + * Reusable managed receiver buffer: + *  - The buffer knows how to claim and release a frame. + **********************************************************************/ +class e100_mmap_zero_copy_mrb : public managed_recv_buffer{ +public: +    e100_mmap_zero_copy_mrb(void *mem, ring_buffer_info *info): +        _mem(mem), _info(info) { /* NOP */ } + +    void release(void){ +        if (fp_verbose) UHD_LOGV(always) << "recv buff: release" << std::endl; +        _info->flags = RB_KERNEL; //release the frame +    } + +    UHD_INLINE bool ready(void){return _info->flags & RB_USER;} + +    UHD_INLINE sptr get_new(void){ +        if (fp_verbose) UHD_LOGV(always) << "  make_recv_buff: " << _info->len << std::endl; +        _info->flags = RB_USER_PROCESS; //claim the frame +        return make(this, _mem, _info->len); +    } + +private: +    void *_mem; +    ring_buffer_info *_info; +}; + +/*********************************************************************** + * Reusable managed send buffer: + *  - The buffer knows how to claim and release a frame. + **********************************************************************/ +class e100_mmap_zero_copy_msb : public managed_send_buffer{ +public: +    e100_mmap_zero_copy_msb(void *mem, ring_buffer_info *info, size_t len, int fd): +        _mem(mem), _info(info), _len(len), _fd(fd) { /* NOP */ } + +    void release(void){ +        if (fp_verbose) UHD_LOGV(always) << "send buff: commit " << size() << std::endl; +        _info->len = _len;//size(); +        _info->flags = RB_USER; //release the frame +        if (::write(_fd, NULL, 0) < 0){ //notifies the kernel +            UHD_LOGV(rarely) << UHD_THROW_SITE_INFO("write error") << std::endl; +        } +    } + +    UHD_INLINE bool ready(void){return _info->flags & RB_KERNEL;} + +    UHD_INLINE sptr get_new(void){ +        if (fp_verbose) UHD_LOGV(always) << "  make_send_buff: " << _len << std::endl; +        _info->flags = RB_USER_PROCESS; //claim the frame +        return make(this, _mem, _len); +    } + +private: +    void *_mem; +    ring_buffer_info *_info; +    size_t _len; +    int _fd; +}; + +/*********************************************************************** + * The zero copy interface implementation + **********************************************************************/ +class e100_mmap_zero_copy_impl : public zero_copy_if{ +public: +    e100_mmap_zero_copy_impl(e100_ctrl::sptr iface): +        _fd(iface->get_file_descriptor()), _recv_index(0), _send_index(0) +    { +        //get system sizes +        iface->ioctl(USRP_E_GET_RB_INFO, &_rb_size); +        size_t page_size = getpagesize(); +        _frame_size = page_size/2; + +        //calculate the memory size +        _map_size = +            (_rb_size.num_pages_rx_flags + _rb_size.num_pages_tx_flags) * page_size + +            (_rb_size.num_rx_frames + _rb_size.num_tx_frames) * _frame_size; + +        //print sizes summary +        UHD_LOG +            << "page_size:          " << page_size                   << std::endl +            << "frame_size:         " << _frame_size                 << std::endl +            << "num_pages_rx_flags: " << _rb_size.num_pages_rx_flags << std::endl +            << "num_rx_frames:      " << _rb_size.num_rx_frames      << std::endl +            << "num_pages_tx_flags: " << _rb_size.num_pages_tx_flags << std::endl +            << "num_tx_frames:      " << _rb_size.num_tx_frames      << std::endl +            << "map_size:           " << _map_size                   << std::endl +        ; + +        //call mmap to get the memory +        _mapped_mem = ::mmap( +            NULL, _map_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0 +        ); +        UHD_ASSERT_THROW(_mapped_mem != MAP_FAILED); + +        //calculate the memory offsets for info and buffers +        size_t recv_info_off = 0; +        size_t recv_buff_off = recv_info_off + (_rb_size.num_pages_rx_flags * page_size); +        size_t send_info_off = recv_buff_off + (_rb_size.num_rx_frames * _frame_size); +        size_t send_buff_off = send_info_off + (_rb_size.num_pages_tx_flags * page_size); + +        //print offset summary +        UHD_LOG +            << "recv_info_off: " << recv_info_off << std::endl +            << "recv_buff_off: " << recv_buff_off << std::endl +            << "send_info_off: " << send_info_off << std::endl +            << "send_buff_off: " << send_buff_off << std::endl +        ; + +        //pointers to sections in the mapped memory +        ring_buffer_info (*recv_info)[], (*send_info)[]; +        char *recv_buff, *send_buff; + +        //set the internal pointers for info and buffers +        typedef ring_buffer_info (*rbi_pta)[]; +        char *rb_ptr = reinterpret_cast<char *>(_mapped_mem); +        recv_info = reinterpret_cast<rbi_pta>(rb_ptr + recv_info_off); +        recv_buff = rb_ptr + recv_buff_off; +        send_info = reinterpret_cast<rbi_pta>(rb_ptr + send_info_off); +        send_buff = rb_ptr + send_buff_off; + +        //initialize the managed receive buffers +        for (size_t i = 0; i < get_num_recv_frames(); i++){ +            _mrb_pool.push_back(boost::make_shared<e100_mmap_zero_copy_mrb>( +                recv_buff + get_recv_frame_size()*i, (*recv_info) + i +            )); +        } + +        //initialize the managed send buffers +        for (size_t i = 0; i < get_num_recv_frames(); i++){ +            _msb_pool.push_back(boost::make_shared<e100_mmap_zero_copy_msb>( +                send_buff + get_send_frame_size()*i, (*send_info) + i, +                get_send_frame_size(), _fd +            )); +        } +    } + +    ~e100_mmap_zero_copy_impl(void){ +        UHD_LOG << "cleanup: munmap" << std::endl; +        ::munmap(_mapped_mem, _map_size); +    } + +    managed_recv_buffer::sptr get_recv_buff(double timeout){ +        if (fp_verbose) UHD_LOGV(always) << "get_recv_buff: " << _recv_index << std::endl; +        e100_mmap_zero_copy_mrb &mrb = *_mrb_pool[_recv_index]; + +        //poll/wait for a ready frame +        if (not mrb.ready()){ +            for (size_t i = 0; i < poll_breakout; i++){ +                pollfd pfd; +                pfd.fd = _fd; +                pfd.events = POLLIN; +                ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3/poll_breakout)); +                if (fp_verbose) UHD_LOGV(always) << "  POLLIN: " << poll_ret << std::endl; +                if (poll_ret > 0) goto found_user_frame; //good poll, continue on +            } +            return managed_recv_buffer::sptr(); //timed-out for real +        } found_user_frame: + +        //increment the index for the next call +        if (++_recv_index == get_num_recv_frames()) _recv_index = 0; + +        //return the managed buffer for this frame +        return mrb.get_new(); +    } + +    size_t get_num_recv_frames(void) const{ +        return _rb_size.num_rx_frames; +    } + +    size_t get_recv_frame_size(void) const{ +        return _frame_size; +    } + +    managed_send_buffer::sptr get_send_buff(double timeout){ +        if (fp_verbose) UHD_LOGV(always) << "get_send_buff: " << _send_index << std::endl; +        e100_mmap_zero_copy_msb &msb = *_msb_pool[_send_index]; + +        //poll/wait for a ready frame +        if (not msb.ready()){ +            pollfd pfd; +            pfd.fd = _fd; +            pfd.events = POLLOUT; +            ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3)); +            if (fp_verbose) UHD_LOGV(always) << "  POLLOUT: " << poll_ret << std::endl; +            if (poll_ret <= 0) return managed_send_buffer::sptr(); +        } + +        //increment the index for the next call +        if (++_send_index == get_num_send_frames()) _send_index = 0; + +        //return the managed buffer for this frame +        return msb.get_new(); +    } + +    size_t get_num_send_frames(void) const{ +        return _rb_size.num_tx_frames; +    } + +    size_t get_send_frame_size(void) const{ +        return _frame_size; +    } + +private: +    //file descriptor for mmap +    int _fd; + +    //the mapped memory itself +    void *_mapped_mem; + +    //mapped memory sizes +    usrp_e_ring_buffer_size_t _rb_size; +    size_t _frame_size, _map_size; + +    //re-usable managed buffers +    std::vector<boost::shared_ptr<e100_mmap_zero_copy_mrb> > _mrb_pool; +    std::vector<boost::shared_ptr<e100_mmap_zero_copy_msb> > _msb_pool; + +    //indexes into sub-sections of mapped memory +    size_t _recv_index, _send_index; +}; + +/*********************************************************************** + * The zero copy interface make function + **********************************************************************/ +zero_copy_if::sptr e100_make_mmap_zero_copy(e100_ctrl::sptr iface){ +    return zero_copy_if::sptr(new e100_mmap_zero_copy_impl(iface)); +} diff --git a/host/lib/usrp/e100/e100_regs.hpp b/host/lib/usrp/e100/e100_regs.hpp new file mode 100644 index 000000000..654163dce --- /dev/null +++ b/host/lib/usrp/e100/e100_regs.hpp @@ -0,0 +1,65 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_E100_REGS_HPP +#define INCLUDED_E100_REGS_HPP + +#include <boost/cstdint.hpp> + +#define TOREG(x) ((x)*4) + +#define localparam static const int + +localparam SR_MISC         = 0;      // 5 +localparam SR_USER_REGS    = 5;      // 2 + +localparam SR_TX_CTRL      = 32;     // 6 +localparam SR_TX_DSP       = 40;     // 5 +localparam SR_TX_FE        = 48;     // 5 + +localparam SR_RX_CTRL0     = 96;      // 9 +localparam SR_RX_DSP0      = 106;     // 7 +localparam SR_RX_FE        = 114;     // 5 + +localparam SR_RX_CTRL1     = 128;     // 9 +localparam SR_RX_DSP1      = 138;     // 7 + +localparam SR_TIME64       = 192;     // 6 +localparam SR_SPI          = 208;     // 3 +localparam SR_I2C          = 216;     // 1 +localparam SR_GPIO         = 224;     // 5 + +#define REG_RB_TIME_NOW_HI TOREG(10) +#define REG_RB_TIME_NOW_LO TOREG(11) +#define REG_RB_TIME_PPS_HI TOREG(14) +#define REG_RB_TIME_PPS_LO TOREG(15) +#define REG_RB_SPI         TOREG(0) +#define REG_RB_COMPAT      TOREG(1) +#define REG_RB_GPIO        TOREG(3) +#define REG_RB_I2C         TOREG(2) +#define REG_RB_CONFIG0     TOREG(4) +#define REG_RB_CONFIG1     TOREG(5) +#define REG_RB_NUM_RX_DSP  TOREG(6) + +//spi slave constants +#define UE_SPI_SS_AD9522    (1 << 3) +#define UE_SPI_SS_AD9862    (1 << 2) +#define UE_SPI_SS_TX_DB     (1 << 1) +#define UE_SPI_SS_RX_DB     (1 << 0) + +#endif /*INCLUDED_E100_REGS_HPP*/ + diff --git a/host/lib/usrp/e100/fpga_downloader.cpp b/host/lib/usrp/e100/fpga_downloader.cpp new file mode 100644 index 000000000..7074c8299 --- /dev/null +++ b/host/lib/usrp/e100/fpga_downloader.cpp @@ -0,0 +1,272 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#ifdef UHD_DLL_EXPORTS +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#else //special case when this file is externally included +#include <stdexcept> +#include <iostream> +#define UHD_MSG(type) std::cout +namespace uhd{ +    typedef std::runtime_error os_error; +    typedef std::runtime_error io_error; +} +#endif + +#include <sstream> +#include <fstream> +#include <string> +#include <cstdlib> + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <linux/spi/spidev.h> + +/* + * Configuration connections + * + * CCK    - MCSPI1_CLK + * DIN    - MCSPI1_MOSI + * PROG_B - GPIO_175     - output (change mux) + * DONE   - GPIO_173     - input  (change mux) + * INIT_B - GPIO_114     - input  (change mux) + * +*/ + +namespace usrp_e_fpga_downloader_utility{ + +const unsigned int PROG_B = 175; +const unsigned int DONE   = 173; +const unsigned int INIT_B = 114; + +//static std::string bit_file = "safe_u1e.bin"; + +const int BUF_SIZE = 4096; + +enum gpio_direction {IN, OUT}; + +class gpio { +	public: + +	gpio(unsigned int gpio_num, gpio_direction pin_direction); + +	bool get_value(); +	void set_value(bool state); + +	private: + +	std::stringstream base_path; +	std::fstream value_file;	 +}; + +class spidev { +	public: + +	spidev(std::string dev_name); +	~spidev(); + +	void send(char *wbuf, char *rbuf, unsigned int nbytes); + +	private: + +	int fd; + +}; + +gpio::gpio(unsigned int gpio_num, gpio_direction pin_direction) +{ +	std::fstream export_file; + +	export_file.open("/sys/class/gpio/export", std::ios::out); +	if (not export_file.is_open()) throw uhd::os_error( +		"Failed to open gpio export file." +	); + +	export_file << gpio_num << std::endl; + +	base_path << "/sys/class/gpio/gpio" << gpio_num << std::flush; + +	std::fstream direction_file; +	std::string direction_file_name; + +	if (gpio_num != 114) { +		direction_file_name = base_path.str() + "/direction"; + +		direction_file.open(direction_file_name.c_str()); +		if (!direction_file.is_open()) throw uhd::os_error( +			"Failed to open direction file." +		); +		if (pin_direction == OUT) +			direction_file << "out" << std::endl; +		else +			direction_file << "in" << std::endl; +	} + +	std::string value_file_name; + +	value_file_name = base_path.str() + "/value"; + +	value_file.open(value_file_name.c_str(), std::ios_base::in | std::ios_base::out); +	if (!value_file.is_open()) throw uhd::os_error( +		"Failed to open value file." +	); +} + +bool gpio::get_value() +{ + +	std::string val; + +	std::getline(value_file, val); +	value_file.seekg(0); + +	if (val == "0") +		return false; +	else if (val == "1") +		return true; +	else +		throw uhd::os_error("Data read from value file|" + val + "|"); + +	return false; +} + +void gpio::set_value(bool state) +{ + +	if (state) +		value_file << "1" << std::endl; +	else +		value_file << "0" << std::endl; +} + +static void prepare_fpga_for_configuration(gpio &prog, gpio &)//init) +{ + +	prog.set_value(true); +	prog.set_value(false); +	prog.set_value(true); + +#if 0 +	bool ready_to_program(false); +	unsigned int count(0); +	do { +		ready_to_program = init.get_value(); +		count++; + +		sleep(1); +	} while (count < 10 && !ready_to_program); + +	if (count == 10) { +		throw uhd::os_error("FPGA not ready for programming."); +	} +#endif +} + +spidev::spidev(std::string fname) +{ +	int ret; +	int mode = 0; +	int speed = 12000000; +	int bits = 8; + +	fd = open(fname.c_str(), O_RDWR); + +	ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); +	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); +	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); +} +	 + +spidev::~spidev() +{ +	close(fd); +} + +void spidev::send(char *buf, char *rbuf, unsigned int nbytes) +{ +	int ret; + +	struct spi_ioc_transfer tr; +	tr.tx_buf = (unsigned long) buf; +	tr.rx_buf = (unsigned long) rbuf; +	tr.len = nbytes; +	tr.delay_usecs = 0; +	tr.speed_hz = 48000000; +	tr.bits_per_word = 8; + +	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);	 + +} + +static void send_file_to_fpga(const std::string &file_name, gpio &error, gpio &done) +{ +	std::ifstream bitstream; + +	bitstream.open(file_name.c_str(), std::ios::binary); +	if (!bitstream.is_open()) throw uhd::os_error( +		"Coult not open the file: " + file_name +	); + +	spidev spi("/dev/spidev1.0"); +	char buf[BUF_SIZE]; +	char rbuf[BUF_SIZE]; + +	do { +		bitstream.read(buf, BUF_SIZE); +		spi.send(buf, rbuf, bitstream.gcount()); + +		if (error.get_value()) +			throw uhd::os_error("INIT_B went high, error occured."); + +		if (!done.get_value()) +			UHD_MSG(status) << "Configuration complete." << std::endl; + +	} while (bitstream.gcount() == BUF_SIZE); +} + +}//namespace usrp_e_fpga_downloader_utility + +void e100_load_fpga(const std::string &bin_file){ +	using namespace usrp_e_fpga_downloader_utility; + +	gpio gpio_prog_b(PROG_B, OUT); +	gpio gpio_init_b(INIT_B, IN); +	gpio gpio_done  (DONE,   IN); + +	UHD_MSG(status) << "Loading FPGA image: " << bin_file << "... " << std::flush; + +//	if(std::system("/sbin/rmmod usrp_e") != 0){ +//		UHD_MSG(warning) << "USRP-E100 FPGA downloader: could not unload usrp_e module" << std::endl; +//	} + +	prepare_fpga_for_configuration(gpio_prog_b, gpio_init_b); + +	UHD_MSG(status) << "done = " << gpio_done.get_value() << std::endl; + +	send_file_to_fpga(bin_file, gpio_init_b, gpio_done); + +//	if(std::system("/sbin/modprobe usrp_e") != 0){ +//		UHD_MSG(warning) << "USRP-E100 FPGA downloader: could not load usrp_e module" << std::endl; +//	} + +} + diff --git a/host/lib/usrp/e100/include/linux/usrp_e.h b/host/lib/usrp/e100/include/linux/usrp_e.h new file mode 100644 index 000000000..f0512a40c --- /dev/null +++ b/host/lib/usrp/e100/include/linux/usrp_e.h @@ -0,0 +1,60 @@ + +/* + *  Copyright (C) 2010-2012 Ettus Research, LLC + * + *  Written by Philip Balister <philip@opensdr.com> + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + */ + +#ifndef __USRP_E_H +#define __USRP_E_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +struct usrp_e_ctl16 { +	__u32 offset; +	__u32 count; +	__u16 buf[20]; +}; + +struct usrp_e_ctl32 { +	__u32 offset; +	__u32 count; +	__u32 buf[10]; +}; + +#define USRP_E_IOC_MAGIC	'u' +#define USRP_E_WRITE_CTL16	_IOW(USRP_E_IOC_MAGIC, 0x20, struct usrp_e_ctl16) +#define USRP_E_READ_CTL16	_IOWR(USRP_E_IOC_MAGIC, 0x21, struct usrp_e_ctl16) +#define USRP_E_WRITE_CTL32	_IOW(USRP_E_IOC_MAGIC, 0x22, struct usrp_e_ctl32) +#define USRP_E_READ_CTL32	_IOWR(USRP_E_IOC_MAGIC, 0x23, struct usrp_e_ctl32) +#define USRP_E_GET_RB_INFO      _IOR(USRP_E_IOC_MAGIC, 0x27, struct usrp_e_ring_buffer_size_t) +#define USRP_E_GET_COMPAT_NUMBER _IO(USRP_E_IOC_MAGIC, 0x28) + +#define USRP_E_COMPAT_NUMBER 4 + +/* Flag defines */ +#define RB_USER (1<<0) +#define RB_KERNEL (1<<1) +#define RB_OVERRUN (1<<2) +#define RB_DMA_ACTIVE (1<<3) +#define RB_USER_PROCESS (1<<4) + +struct ring_buffer_info { +	int flags; +	int len; +}; + +struct usrp_e_ring_buffer_size_t { +	int num_pages_rx_flags; +	int num_rx_frames; +	int num_pages_tx_flags; +	int num_tx_frames; +}; + +#endif diff --git a/host/lib/usrp/e100/io_impl.cpp b/host/lib/usrp/e100/io_impl.cpp new file mode 100644 index 000000000..e34620444 --- /dev/null +++ b/host/lib/usrp/e100/io_impl.cpp @@ -0,0 +1,230 @@ +// +// Copyright 2010-2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "validate_subdev_spec.hpp" +#include "async_packet_handler.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "e100_impl.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/tasks.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const size_t vrt_send_header_offset_words32 = 0; + +void e100_impl::update_tick_rate(const double rate){ + +    //update the tick rate on all existing streamers -> thread safe +    for (size_t i = 0; i < _rx_streamers.size(); i++){ +        boost::shared_ptr<sph::recv_packet_streamer> my_streamer = +            boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); +        if (my_streamer.get() == NULL) continue; +        my_streamer->set_tick_rate(rate); +    } +    for (size_t i = 0; i < _tx_streamers.size(); i++){ +        boost::shared_ptr<sph::send_packet_streamer> my_streamer = +            boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[i].lock()); +        if (my_streamer.get() == NULL) continue; +        my_streamer->set_tick_rate(rate); +    } +} + +void e100_impl::update_rx_samp_rate(const size_t dspno, const double rate){ +    boost::shared_ptr<sph::recv_packet_streamer> my_streamer = +        boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[dspno].lock()); +    if (my_streamer.get() == NULL) return; + +    my_streamer->set_samp_rate(rate); +    const double adj = _rx_dsps[dspno]->get_scaling_adjustment(); +    my_streamer->set_scale_factor(adj); +} + +void e100_impl::update_tx_samp_rate(const size_t dspno, const double rate){ +    boost::shared_ptr<sph::send_packet_streamer> my_streamer = +        boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[dspno].lock()); +    if (my_streamer.get() == NULL) return; + +    my_streamer->set_samp_rate(rate); +    const double adj = _tx_dsp->get_scaling_adjustment(); +    my_streamer->set_scale_factor(adj); +} + +void e100_impl::update_rates(void){ +    const fs_path mb_path = "/mboards/0"; +    _tree->access<double>(mb_path / "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(mb_path / "rx_dsps")){ +        _tree->access<double>(mb_path / "rx_dsps" / name / "rate" / "value").update(); +    } +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ +        _tree->access<double>(mb_path / "tx_dsps" / name / "rate" / "value").update(); +    } +} + +void e100_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ +    fs_path root = "/mboards/0/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "rx"); + +    //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; +        _rx_dsps[i]->set_mux(conn, fe_swapped); +    } +    _rx_fe->set_mux(fe_swapped); +} + +void e100_impl::update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ +    fs_path root = "/mboards/0/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "tx"); + +    //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(); +    _tx_fe->set_mux(conn); +} + +/*********************************************************************** + * Async Recv + **********************************************************************/ +bool e100_impl::recv_async_msg( +    async_metadata_t &async_metadata, double timeout +){ +    return _fifo_ctrl->pop_async_msg(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr e100_impl::get_rx_stream(const uhd::stream_args_t &args_){ +    stream_args_t args = args_; + +    //setup defaults for unspecified values +    args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; +    args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + +    //calculate packet size +    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 +        - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used +    ; +    const size_t bpp = _data_transport->get_recv_frame_size() - hdr_size; +    const size_t bpi = convert::get_bytes_per_item(args.otw_format); +    const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + +    //make the new streamer given the samples per packet +    boost::shared_ptr<sph::recv_packet_streamer> my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + +    //init some streamer stuff +    my_streamer->resize(args.channels.size()); +    my_streamer->set_vrt_unpacker(&vrt::if_hdr_unpack_le); + +    //set the converter +    uhd::convert::id_type id; +    id.input_format = args.otw_format + "_item32_le"; +    id.num_inputs = 1; +    id.output_format = args.cpu_format; +    id.num_outputs = 1; +    my_streamer->set_converter(id); + +    //bind callbacks for the handler +    for (size_t chan_i = 0; chan_i < args.channels.size(); chan_i++){ +        const size_t dsp = args.channels[chan_i]; +        _rx_dsps[dsp]->set_nsamps_per_packet(spp); //seems to be a good place to set this +        _rx_dsps[dsp]->setup(args); +        my_streamer->set_xport_chan_get_buff(chan_i, boost::bind( +            &recv_packet_demuxer::get_recv_buff, _recv_demuxer, dsp, _1 +        ), true /*flush*/); +        my_streamer->set_overflow_handler(chan_i, boost::bind( +            &rx_dsp_core_200::handle_overflow, _rx_dsps[dsp] +        )); +        _rx_streamers[dsp] = my_streamer; //store weak pointer +    } + +    //sets all tick and samp rates on this streamer +    this->update_rates(); + +    return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr e100_impl::get_tx_stream(const uhd::stream_args_t &args_){ +    stream_args_t args = args_; + +    //setup defaults for unspecified values +    args.otw_format = args.otw_format.empty()? "sc16" : args.otw_format; +    args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + +    //calculate packet size +    static const size_t hdr_size = 0 +        + vrt_send_header_offset_words32*sizeof(boost::uint32_t) +        + 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().sid) //no stream id ever used +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +        - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used +    ; +    static const size_t bpp = _data_transport->get_send_frame_size() - hdr_size; +    const size_t spp = bpp/convert::get_bytes_per_item(args.otw_format); + +    //make the new streamer given the samples per packet +    boost::shared_ptr<sph::send_packet_streamer> my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + +    //init some streamer stuff +    my_streamer->resize(args.channels.size()); +    my_streamer->set_vrt_packer(&vrt::if_hdr_pack_le, vrt_send_header_offset_words32); + +    //set the converter +    uhd::convert::id_type id; +    id.input_format = args.cpu_format; +    id.num_inputs = 1; +    id.output_format = args.otw_format + "_item32_le"; +    id.num_outputs = 1; +    my_streamer->set_converter(id); + +    //bind callbacks for the handler +    for (size_t chan_i = 0; chan_i < args.channels.size(); chan_i++){ +        const size_t dsp = args.channels[chan_i]; +        UHD_ASSERT_THROW(dsp == 0); //always 0 +        _tx_dsp->setup(args); +        my_streamer->set_xport_chan_get_buff(chan_i, boost::bind( +            &zero_copy_if::get_send_buff, _data_transport, _1 +        )); +        _tx_streamers[dsp] = my_streamer; //store weak pointer +    } + +    //sets all tick and samp rates on this streamer +    this->update_rates(); + +    return my_streamer; +}  | 
