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 | 355 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_ctrl.hpp | 48 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_impl.cpp | 494 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_impl.hpp | 140 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_mmap_zero_copy.cpp | 269 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_regs.hpp | 137 | ||||
| -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 | 366 | 
15 files changed, 3482 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..6afc7bc48 --- /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, E100_REG_SR_ADDR(UE_SR_GPIO), E100_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..eb529c9c1 --- /dev/null +++ b/host/lib/usrp/e100/e100_ctrl.cpp @@ -0,0 +1,355 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#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 <boost/thread/thread.hpp> //sleep +#include <boost/thread/mutex.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <fstream> + +using namespace uhd; + +/*********************************************************************** + * 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; +}; + +/*********************************************************************** + * 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"){} + +    boost::uint32_t transact_spi( +        int, const spi_config_t &, //not used params +        boost::uint32_t bits, +        size_t num_bits, +        bool readback +    ){ +        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)); +} + +/*********************************************************************** + * 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)); +        } + +        //perform a global reset after opening +        this->poke32(E100_REG_GLOBAL_RESET, 0); +    } + +    ~e100_ctrl_impl(void){ +        ::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 +            )); +        } +    } +    /******************************************************************* +     * Peek and Poke +     ******************************************************************/ +    void poke32(wb_addr_type addr, boost::uint32_t value){ +        //load the data struct +        usrp_e_ctl32 data; +        data.offset = addr; +        data.count = 1; +        data.buf[0] = value; + +        //call the ioctl +        this->ioctl(USRP_E_WRITE_CTL32, &data); +    } + +    void poke16(wb_addr_type addr, boost::uint16_t value){ +        //load the data struct +        usrp_e_ctl16 data; +        data.offset = addr; +        data.count = 1; +        data.buf[0] = value; + +        //call the ioctl +        this->ioctl(USRP_E_WRITE_CTL16, &data); +    } + +    boost::uint32_t peek32(wb_addr_type addr){ +        //load the data struct +        usrp_e_ctl32 data; +        data.offset = addr; +        data.count = 1; + +        //call the ioctl +        this->ioctl(USRP_E_READ_CTL32, &data); + +        return data.buf[0]; +    } + +    boost::uint16_t peek16(wb_addr_type addr){ +        //load the data struct +        usrp_e_ctl16 data; +        data.offset = addr; +        data.count = 1; + +        //call the ioctl +        this->ioctl(USRP_E_READ_CTL16, &data); + +        return data.buf[0]; +    } + +private: +    int _node_fd; +    boost::mutex _ioctl_mutex; +}; + +/*********************************************************************** + * 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..fd66791d4 --- /dev/null +++ b/host/lib/usrp/e100/e100_ctrl.hpp @@ -0,0 +1,48 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CTRL_HPP +#define INCLUDED_B100_CTRL_HPP + +#include "wb_iface.hpp" +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class e100_ctrl : boost::noncopyable, public wb_iface{ +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 an i2c iface for the i2c device node +    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..bb135b646 --- /dev/null +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -0,0 +1,494 @@ +// +// 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> + +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, mboard_eeprom_t::MAP_E100); +            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 size_t hash_fpga_file(const std::string &file_path){ +    size_t hash = 0; +    std::ifstream file(file_path.c_str()); +    if (not file.good()) throw uhd::io_error("cannot open fpga file for read: " + file_path); +    while (file.good()) boost::hash_combine(hash, file.get()); +    file.close(); +    return hash; +} + +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(); + +    //setup the main interface into fpga +    const std::string node = device_addr["node"]; +    _fpga_ctrl = e100_ctrl::make(node); + +    //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, mboard_eeprom_t::MAP_E100); + +    //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]; +    const std::string e100_fpga_image = find_image_path(device_addr.get("fpga", default_fpga_file_name)); +    const boost::uint32_t file_hash = boost::uint32_t(hash_fpga_file(e100_fpga_image)); + +    //When the hash does not match: +    // - close the device node +    // - load the fpga bin file +    // - re-open the device node +    if (_fpga_ctrl->peek32(E100_REG_RB_MISC_TEST32) != file_hash){ +        _fpga_ctrl.reset(); +        e100_load_fpga(e100_fpga_image); +        _fpga_ctrl = e100_ctrl::make(node); +    } + +    //setup clock control here to ensure that the FPGA has a good clock before we continue +    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); + +    //Perform wishbone readback tests, these tests also write the hash +    bool test_fail = false; +    UHD_MSG(status) << "Performing wishbone readback test... " << std::flush; +    for (size_t i = 0; i < 100; i++){ +        _fpga_ctrl->poke32(E100_REG_SR_MISC_TEST32, file_hash); +        test_fail = _fpga_ctrl->peek32(E100_REG_RB_MISC_TEST32) != file_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_100::make(_fpga_ctrl, E100_REG_SLAVE(3)); +    _fpga_spi_ctrl = spi_core_100::make(_fpga_ctrl, E100_REG_SLAVE(2)); +    _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(str(boost::format("%s (euewanee)") % model)); + +    //////////////////////////////////////////////////////////////////// +    // 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(&e100_impl::update_tick_rate, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create codec control objects +    //////////////////////////////////////////////////////////////////// +    _codec_ctrl = e100_codec_ctrl::make(_fpga_spi_ctrl); +    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 +    //////////////////////////////////////////////////////////////////// +    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.get() != NULL and _gps->gps_detected()){ +        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)); +        } +    } + +    //////////////////////////////////////////////////////////////////// +    // create frontend control objects +    //////////////////////////////////////////////////////////////////// +    _rx_fe = rx_frontend_core_200::make(_fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_FRONT)); +    _tx_fe = tx_frontend_core_200::make(_fpga_ctrl, E100_REG_SR_ADDR(UE_SR_TX_FRONT)); + +    _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::polar<double>(1.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::polar<double>(1.0, 0.0)); + +    //////////////////////////////////////////////////////////////////// +    // create rx dsp control objects +    //////////////////////////////////////////////////////////////////// +    _rx_dsps.push_back(rx_dsp_core_200::make( +        _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_DSP0), E100_REG_SR_ADDR(UE_SR_RX_CTRL0), E100_RX_SID_BASE + 0 +    )); +    _rx_dsps.push_back(rx_dsp_core_200::make( +        _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_DSP1), E100_REG_SR_ADDR(UE_SR_RX_CTRL1), E100_RX_SID_BASE + 1 +    )); +    for (size_t dspno = 0; dspno < _rx_dsps.size(); 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( +        _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_TX_DSP), E100_REG_SR_ADDR(UE_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 = E100_REG_RB_TIME_NOW_HI; +    time64_rb_bases.rb_lo_now = E100_REG_RB_TIME_NOW_LO; +    time64_rb_bases.rb_hi_pps = E100_REG_RB_TIME_PPS_HI; +    time64_rb_bases.rb_lo_pps = E100_REG_RB_TIME_PPS_LO; +    _time64 = time64_core_200::make( +        _fpga_ctrl, E100_REG_SR_ADDR(UE_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)); +    static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); +    _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(_fpga_ctrl, E100_REG_SR_ADDR(UE_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); + +    //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(_fpga_ctrl, _fpga_i2c_ctrl, _fpga_spi_ctrl, _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 +    this->io_init(); + +    //////////////////////////////////////////////////////////////////// +    // 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.get() != NULL and _gps->gps_detected()){ +        UHD_MSG(status) << "Setting references to the internal GPSDO" << std::endl; +        _tree->access<std::string>(mb_path / "time_source/value").set("external"); +        _tree->access<std::string>(mb_path / "clock_source/value").set("external"); +        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, mboard_eeprom_t::MAP_E100); +} + +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 == "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 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 = _fpga_ctrl->peek32(E100_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)); +    } +    _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..1d36cb2ac --- /dev/null +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -0,0 +1,140 @@ +// +// 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 "spi_core_100.hpp" +#include "i2c_core_100.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 "user_settings_core_200.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/pimpl.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 = 0x09; +static const boost::uint32_t E100_RX_SID_BASE = 2; +static const boost::uint32_t E100_TX_ASYNC_SID = 1; +static const double          E100_DEFAULT_CLOCK_RATE = 64e6; + +//! 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 +    spi_core_100::sptr _fpga_spi_ctrl; +    i2c_core_100::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; + +    //dboard stuff +    uhd::usrp::dboard_manager::sptr _dboard_manager; +    uhd::usrp::dboard_iface::sptr _dboard_iface; + +    //handle io stuff +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void io_init(void); + +    //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..cdb7094f4 --- /dev/null +++ b/host/lib/usrp/e100/e100_mmap_zero_copy.cpp @@ -0,0 +1,269 @@ +// +// 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 "e100_ctrl.hpp" +#include <uhd/transport/zero_copy.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/exception.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 (_info->flags != RB_USER_PROCESS) return; +        if (fp_verbose) UHD_LOGV(always) << "recv buff: release" << std::endl; +        _info->flags = RB_KERNEL; //release the frame +    } + +    bool ready(void){return _info->flags & RB_USER;} + +    sptr get_new(void){ +        if (fp_verbose) UHD_LOGV(always) << "  make_recv_buff: " << get_size() << std::endl; +        _info->flags = RB_USER_PROCESS; //claim the frame +        return make_managed_buffer(this); +    } + +private: +    const void *get_buff(void) const{return _mem;} +    size_t get_size(void) const{return _info->len;} + +    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 commit(size_t len){ +        if (_info->flags != RB_USER_PROCESS) return; +        if (fp_verbose) UHD_LOGV(always) << "send buff: commit " << len << std::endl; +        _info->len = len; +        _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; +        } +    } + +    bool ready(void){return _info->flags & RB_KERNEL;} + +    sptr get_new(void){ +        if (fp_verbose) UHD_LOGV(always) << "  make_send_buff: " << get_size() << std::endl; +        _info->flags = RB_USER_PROCESS; //claim the frame +        return make_managed_buffer(this); +    } + +private: +    void *get_buff(void) const{return _mem;} +    size_t get_size(void) const{return _len;} + +    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(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(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<e100_mmap_zero_copy_mrb> _mrb_pool; +    std::vector<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..75be2cfbe --- /dev/null +++ b/host/lib/usrp/e100/e100_regs.hpp @@ -0,0 +1,137 @@ +// +// 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/>. +// + +//////////////////////////////////////////////////////////////// +// +//         Memory map for embedded wishbone bus +// +//////////////////////////////////////////////////////////////// + +// All addresses are byte addresses.  All accesses are word (16-bit) accesses. +//  This means that address bit 0 is usually 0. +//  There are 11 bits of address for the control. + +#ifndef INCLUDED_E100_REGS_HPP +#define INCLUDED_E100_REGS_HPP + +///////////////////////////////////////////////////// +// Slave pointers + +#define E100_REG_SLAVE(n) ((n)<<7) + +///////////////////////////////////////////////////// +// Slave 0 -- Misc Regs + +#define E100_REG_MISC_BASE E100_REG_SLAVE(0) + +#define E100_REG_MISC_LED        E100_REG_MISC_BASE + 0 +#define E100_REG_MISC_SW         E100_REG_MISC_BASE + 2 +#define E100_REG_MISC_CGEN_CTRL  E100_REG_MISC_BASE + 4 +#define E100_REG_MISC_CGEN_ST    E100_REG_MISC_BASE + 6 +#define E100_REG_MISC_TEST       E100_REG_MISC_BASE + 8 +#define E100_REG_MISC_RX_LEN     E100_REG_MISC_BASE + 10 +#define E100_REG_MISC_TX_LEN     E100_REG_MISC_BASE + 12 +#define E100_REG_MISC_XFER_RATE  E100_REG_MISC_BASE + 14 + +///////////////////////////////////////////////////// +// Slave 1 -- UART +//   CLKDIV is 16 bits, others are only 8 + +#define E100_REG_UART_BASE E100_REG_SLAVE(1) + +#define E100_REG_UART_CLKDIV  E100_REG_UART_BASE + 0 +#define E100_REG_UART_TXLEVEL E100_REG_UART_BASE + 2 +#define E100_REG_UART_RXLEVEL E100_REG_UART_BASE + 4 +#define E100_REG_UART_TXCHAR  E100_REG_UART_BASE + 6 +#define E100_REG_UART_RXCHAR  E100_REG_UART_BASE + 8 + +///////////////////////////////////////////////////// +// Slave 2 -- SPI Core +//these are 32-bit registers mapped onto the 16-bit Wishbone bus. +//Using peek32/poke32 should allow transparent use of these registers. +#define E100_REG_SPI_BASE E100_REG_SLAVE(2) + +//spi slave constants +#define UE_SPI_SS_AD9522    (1 << 3) +#define UE_SPI_SS_AD9862    (1 << 2) +#define UE_SPI_SS_TX_DB     (1 << 1) +#define UE_SPI_SS_RX_DB     (1 << 0) + +//////////////////////////////////////////////// +// Slave 3 -- I2C Core + +#define E100_REG_I2C_BASE E100_REG_SLAVE(3) + +//////////////////////////////////////////////// +// Slave 5 -- Error messages buffer + +#define E100_REG_ERR_BUFF E100_REG_SLAVE(5) + +/////////////////////////////////////////////////// +// Slave 7 -- Readback Mux 32 + +#define E100_REG_RB_MUX_32_BASE  E100_REG_SLAVE(7) + +#define E100_REG_RB_TIME_NOW_HI     E100_REG_RB_MUX_32_BASE + 0 +#define E100_REG_RB_TIME_NOW_LO     E100_REG_RB_MUX_32_BASE + 4 +#define E100_REG_RB_TIME_PPS_HI     E100_REG_RB_MUX_32_BASE + 8 +#define E100_REG_RB_TIME_PPS_LO     E100_REG_RB_MUX_32_BASE + 12 +#define E100_REG_RB_MISC_TEST32     E100_REG_RB_MUX_32_BASE + 16 +#define E100_REG_RB_ERR_STATUS      E100_REG_RB_MUX_32_BASE + 20 +#define E100_REG_RB_COMPAT          E100_REG_RB_MUX_32_BASE + 24 +#define E100_REG_RB_GPIO            E100_REG_RB_MUX_32_BASE + 28 + +//////////////////////////////////////////////////// +// Slave 8 -- Settings Bus +// +// Output-only, no readback, 64 registers total +//  Each register must be written 64 bits at a time +//  First the address xxx_xx00 and then xxx_xx10 + +// 64 total regs in address space +#define UE_SR_RX_CTRL0 0       // 9 regs (+0 to +8) +#define UE_SR_RX_DSP0 10       // 4 regs (+0 to +3) +#define UE_SR_RX_CTRL1 16      // 9 regs (+0 to +8) +#define UE_SR_RX_DSP1 26       // 4 regs (+0 to +3) +#define UE_SR_ERR_CTRL 30      // 1 reg +#define UE_SR_TX_CTRL 32       // 4 regs (+0 to +3) +#define UE_SR_TX_DSP 38        // 3 regs (+0 to +2) + +#define UE_SR_TIME64 42        // 6 regs (+0 to +5) +#define UE_SR_RX_FRONT 48      // 5 regs (+0 to +4) +#define UE_SR_TX_FRONT 54      // 5 regs (+0 to +4) + +#define UE_SR_REG_TEST32 60    // 1 reg +#define UE_SR_CLEAR_FIFO 61    // 1 reg +#define UE_SR_GLOBAL_RESET 63  // 1 reg +#define UE_SR_USER_REGS 64     // 2 regs + +#define UE_SR_GPIO 128 + +#define E100_REG_SR_ADDR(n) (E100_REG_SLAVE(8) + (4*(n))) + +#define E100_REG_SR_MISC_TEST32        E100_REG_SR_ADDR(UE_SR_REG_TEST32) +#define E100_REG_SR_ERR_CTRL           E100_REG_SR_ADDR(UE_SR_ERR_CTRL) + +///////////////////////////////////////////////// +// Magic reset regs +//////////////////////////////////////////////// +#define E100_REG_CLEAR_FIFO         E100_REG_SR_ADDR(UE_SR_CLEAR_FIFO) +#define E100_REG_GLOBAL_RESET       E100_REG_SR_ADDR(UE_SR_GLOBAL_RESET) + +#endif + 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..37e9ee31a --- /dev/null +++ b/host/lib/usrp/e100/include/linux/usrp_e.h @@ -0,0 +1,60 @@ + +/* + *  Copyright (C) 2010 Ettus Research, LLC + * + *  Written by Philip Balister <philip@opensdr.com> + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + */ + +#ifndef __USRP_E_H +#define __USRP_E_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +struct usrp_e_ctl16 { +	__u32 offset; +	__u32 count; +	__u16 buf[20]; +}; + +struct usrp_e_ctl32 { +	__u32 offset; +	__u32 count; +	__u32 buf[10]; +}; + +#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 3 + +/* 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..e9608125f --- /dev/null +++ b/host/lib/usrp/e100/io_impl.cpp @@ -0,0 +1,366 @@ +// +// 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 "recv_packet_demuxer.hpp" +#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 <linux/usrp_e.h> //ioctl structures and constants +#include "e100_impl.hpp" +#include "e100_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread/thread.hpp> +#include <poll.h> //poll +#include <fcntl.h> //open, close +#include <sstream> +#include <fstream> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew of 1 + * - bounded buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct e100_impl::io_impl{ +    io_impl(void): +        false_alarm(0), async_msg_fifo(1000/*messages deep*/) +    { /* NOP */ } + +    double tick_rate; //set by update tick rate method +    e100_ctrl::sptr iface; //so handle irq can peek and poke +    void handle_irq(void); +    size_t false_alarm; +    //The data transport is listed first so that it is deconstructed last, +    //which is after the states and booty which may hold managed buffers. +    recv_packet_demuxer::sptr demuxer; + +    //a pirate's life is the life for me! +    void recv_pirate_loop( +        spi_iface::sptr //keep a sptr to iface which shares gpio147 +    ){ +        //open the GPIO and set it up for an IRQ +        std::ofstream edge_file("/sys/class/gpio/gpio147/edge"); +        edge_file << "rising" << std::endl << std::flush; +        edge_file.close(); +        int fd = ::open("/sys/class/gpio/gpio147/value", O_RDONLY); +        if (fd < 0) UHD_MSG(error) << "Unable to open GPIO for IRQ\n"; + +        while (not boost::this_thread::interruption_requested()){ +            pollfd pfd; +            pfd.fd = fd; +            pfd.events = POLLPRI | POLLERR; +            ssize_t ret = ::poll(&pfd, 1, 100/*ms*/); +            if (ret > 0) this->handle_irq(); +        } + +        //cleanup before thread exit +        ::close(fd); +    } +    bounded_buffer<async_metadata_t> async_msg_fifo; +    task::sptr pirate_task; +}; + +void e100_impl::io_impl::handle_irq(void){ +    //check the status of the async msg buffer +    const boost::uint32_t status = iface->peek32(E100_REG_RB_ERR_STATUS); +    if ((status & 0x3) == 0){ //not done or error +        //This could be a false-alarm because spi readback is mixed in. +        //So we just sleep for a bit rather than interrupt continuously. +        if (false_alarm++ > 3) boost::this_thread::sleep(boost::posix_time::milliseconds(1)); +        return; +    } +    false_alarm = 0; //its a real message, reset the count... +    //std::cout << boost::format("status: 0x%x") % status << std::endl; + +    //load the data struct and call the ioctl +    usrp_e_ctl32 data; +    data.offset = E100_REG_ERR_BUFF; +    data.count = status >> 16; +    iface->ioctl(USRP_E_READ_CTL32, &data); +    //for (size_t i = 0; i < data.count; i++){ +        //data.buf[i] = iface->peek32(E100_REG_ERR_BUFF + i*sizeof(boost::uint32_t)); +        //std::cout << boost::format("    buff[%u] = 0x%08x\n") % i % data.buf[i]; +    //} + +    //unpack the vrt header and process below... +    vrt::if_packet_info_t if_packet_info; +    if_packet_info.num_packet_words32 = data.count; +    try{vrt::if_hdr_unpack_le(data.buf, if_packet_info);} +    catch(const std::exception &e){ +        UHD_MSG(error) << "Error unpacking vrt header:\n" << e.what() << std::endl; +        goto prepare; +    } + +    //handle a tx async report message +    if (if_packet_info.sid == E100_TX_ASYNC_SID and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + +        //fill in the async metadata +        async_metadata_t metadata; +        load_metadata_from_buff(uhd::wtohx<boost::uint32_t>, metadata, if_packet_info, data.buf, tick_rate); + +        //push the message onto the queue +        async_msg_fifo.push_with_pop_on_full(metadata); + +        //print some fastpath messages +        standard_async_msg_prints(metadata); +    } + +    //prepare for the next round +    prepare: +    iface->poke32(E100_REG_SR_ERR_CTRL, 1 << 0); //clear +    while ((iface->peek32(E100_REG_RB_ERR_STATUS) & (1 << 2)) == 0){} //wait for idle +    iface->poke32(E100_REG_SR_ERR_CTRL, 1 << 1); //start +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void e100_impl::io_init(void){ + +    //create new io impl +    _io_impl = UHD_PIMPL_MAKE(io_impl, ()); +    _io_impl->demuxer = recv_packet_demuxer::make(_data_transport, _rx_dsps.size(), E100_RX_SID_BASE); +    _io_impl->iface = _fpga_ctrl; + +    //clear fifo state machines +    _fpga_ctrl->poke32(E100_REG_CLEAR_FIFO, 0); + +    //allocate streamer weak ptrs containers +    _rx_streamers.resize(_rx_dsps.size()); +    _tx_streamers.resize(1/*known to be 1 dsp*/); + +    //prepare the async msg buffer for incoming messages +    _fpga_ctrl->poke32(E100_REG_SR_ERR_CTRL, 1 << 0); //clear +    while ((_fpga_ctrl->peek32(E100_REG_RB_ERR_STATUS) & (1 << 2)) == 0){} //wait for idle +    _fpga_ctrl->poke32(E100_REG_SR_ERR_CTRL, 1 << 1); //start + +    //spawn a pirate, yarrr! +    _io_impl->pirate_task = task::make(boost::bind( +        &e100_impl::io_impl::recv_pirate_loop, _io_impl.get(), _aux_spi_iface +    )); +} + +void e100_impl::update_tick_rate(const double rate){ +    _io_impl->tick_rate = 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 +){ +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return _io_impl->async_msg_fifo.pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * 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, _io_impl->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::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); + +    //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; +} | 
