diff options
Diffstat (limited to 'host/lib/usrp/usrp_e100')
19 files changed, 3637 insertions, 0 deletions
diff --git a/host/lib/usrp/usrp_e100/CMakeLists.txt b/host/lib/usrp/usrp_e100/CMakeLists.txt new file mode 100644 index 000000000..66c87e0d8 --- /dev/null +++ b/host/lib/usrp/usrp_e100/CMakeLists.txt @@ -0,0 +1,60 @@ +# +# Copyright 2010 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +#This file will be included by cmake, use absolute paths! + +######################################################################## +# Conditionally configure the USRP-E100 support +######################################################################## +MESSAGE(STATUS "Configuring USRP-E100 support...") + +IF(DEFINED ENABLE_USRP_E100) + IF(ENABLE_USRP_E100) + MESSAGE(STATUS "USRP-E100 support enabled by configure flag") + ELSE(ENABLE_USRP_E100) + MESSAGE(STATUS "USRP-E100 support disabled by configure flag") + ENDIF(ENABLE_USRP_E100) +ELSE(DEFINED ENABLE_USRP_E100) #not defined: automatic disabling of component + SET(ENABLE_USRP_E100 FALSE) +ENDIF(DEFINED ENABLE_USRP_E100) +SET(ENABLE_USRP_E100 ${ENABLE_USRP_E100} CACHE BOOL "enable USRP-E100 support") + +IF(ENABLE_USRP_E100) + MESSAGE(STATUS " Building USRP-E100 support.") + INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/include) + LIBUHD_APPEND_SOURCES( + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/clock_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/clock_ctrl.hpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/codec_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/codec_ctrl.hpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/codec_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/dboard_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/dboard_iface.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/dsp_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/fpga-downloader.cc + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/io_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/mboard_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_impl.hpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_iface.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_iface.hpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_mmap_zero_copy.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_e100/usrp_e100_regs.hpp + ) +ELSE(ENABLE_USRP_E100) + MESSAGE(STATUS " Skipping USRP-E100 support.") +ENDIF(ENABLE_USRP_E100) diff --git a/host/lib/usrp/usrp_e100/clock_ctrl.cpp b/host/lib/usrp/usrp_e100/clock_ctrl.cpp new file mode 100644 index 000000000..1fb1a7125 --- /dev/null +++ b/host/lib/usrp/usrp_e100/clock_ctrl.cpp @@ -0,0 +1,263 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9522_regs.hpp" +#include <uhd/utils/assert.hpp> +#include <boost/cstdint.hpp> +#include "usrp_e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <utility> +#include <iostream> + +using namespace uhd; + +template <typename div_type, typename bypass_type> static void set_clock_divider( + size_t divider, div_type &low, div_type &high, bypass_type &bypass +){ + high = divider/2 - 1; + low = divider - high - 2; + bypass = (divider == 1)? 1 : 0; +} + +/*********************************************************************** + * Constants + **********************************************************************/ +static const bool enable_test_clock = false; +static const size_t ref_clock_doubler = 2; //enabled below +static const double ref_clock_rate = 10e6 * ref_clock_doubler; + +static const size_t r_counter = 1; +static const size_t a_counter = 0; +static const size_t b_counter = 20 / ref_clock_doubler; +static const size_t prescaler = 8; //set below with enum, set to 8 when input is under 2400 MHz +static const size_t vco_divider = 5; //set below with enum + +static const size_t n_counter = prescaler * b_counter + a_counter; +static const size_t vco_clock_rate = ref_clock_rate/r_counter * n_counter; //between 1400 and 1800 MHz +static const double master_clock_rate = vco_clock_rate/vco_divider; + +static const size_t fpga_clock_divider = size_t(master_clock_rate/64e6); +static const size_t codec_clock_divider = size_t(master_clock_rate/64e6); + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class usrp_e100_clock_ctrl_impl : public usrp_e100_clock_ctrl{ +public: + usrp_e100_clock_ctrl_impl(usrp_e100_iface::sptr iface){ + _iface = iface; + + //init the clock gen registers + //Note: out0 should already be clocking the FPGA or this isnt going to work + _ad9522_regs.sdo_active = ad9522_regs_t::SDO_ACTIVE_SDO_SDIO; + _ad9522_regs.enable_clock_doubler = 1; //enable ref clock doubler + _ad9522_regs.enb_stat_eeprom_at_stat_pin = 0; //use status pin + _ad9522_regs.status_pin_control = 0x1; //n divider + _ad9522_regs.ld_pin_control = 0x00; //dld + _ad9522_regs.refmon_pin_control = 0x12; //show ref2 + + _ad9522_regs.enable_ref2 = 1; + _ad9522_regs.enable_ref1 = 0; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF2; + + _ad9522_regs.set_r_counter(r_counter); + _ad9522_regs.a_counter = a_counter; + _ad9522_regs.set_b_counter(b_counter); + _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV8_9; + + _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; + _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + + _ad9522_regs.vco_calibration_now = 1; //calibrate it! + _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV5; + _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_VCO; + + //setup fpga master clock + _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; + set_clock_divider(fpga_clock_divider, + _ad9522_regs.divider0_low_cycles, + _ad9522_regs.divider0_high_cycles, + _ad9522_regs.divider0_bypass + ); + + //setup codec clock + _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; + set_clock_divider(codec_clock_divider, + _ad9522_regs.divider1_low_cycles, + _ad9522_regs.divider1_high_cycles, + _ad9522_regs.divider1_bypass + ); + + //setup test clock (same divider as codec clock) + _ad9522_regs.out4_format = ad9522_regs_t::OUT4_FORMAT_CMOS; + _ad9522_regs.out4_cmos_configuration = (enable_test_clock)? + ad9522_regs_t::OUT4_CMOS_CONFIGURATION_A_ON : + ad9522_regs_t::OUT4_CMOS_CONFIGURATION_OFF; + + //setup a list of register ranges to write + typedef std::pair<boost::uint16_t, boost::uint16_t> range_t; + static const std::vector<range_t> ranges = boost::assign::list_of + (range_t(0x000, 0x000)) (range_t(0x010, 0x01F)) + (range_t(0x0F0, 0x0FD)) (range_t(0x190, 0x19B)) + (range_t(0x1E0, 0x1E1)) (range_t(0x230, 0x230)) + ; + + //write initial register values and latch/update + BOOST_FOREACH(const range_t &range, ranges){ + for(boost::uint16_t addr = range.first; addr <= range.second; addr++){ + this->send_reg(addr); + } + } + this->latch_regs(); + //test read: + //boost::uint32_t reg = _ad9522_regs.get_read_reg(0x01b); + //boost::uint32_t result = _iface->transact_spi( + // UE_SPI_SS_AD9522, + // spi_config_t::EDGE_RISE, + // reg, 24, true /*no*/ + //); + //std::cout << "result " << std::hex << result << std::endl; + this->enable_rx_dboard_clock(false); + this->enable_tx_dboard_clock(false); + } + + ~usrp_e100_clock_ctrl_impl(void){ + this->enable_rx_dboard_clock(false); + this->enable_tx_dboard_clock(false); + } + + double get_fpga_clock_rate(void){ + return master_clock_rate/fpga_clock_divider; + } + + /*********************************************************************** + * RX Dboard Clock Control (output 9, divider 3) + **********************************************************************/ + void enable_rx_dboard_clock(bool enb){ + _ad9522_regs.out9_format = ad9522_regs_t::OUT9_FORMAT_CMOS; + _ad9522_regs.out9_cmos_configuration = (enb)? + ad9522_regs_t::OUT9_CMOS_CONFIGURATION_B_ON : + ad9522_regs_t::OUT9_CMOS_CONFIGURATION_OFF; + this->send_reg(0x0F9); + this->latch_regs(); + } + + std::vector<double> get_rx_dboard_clock_rates(void){ + std::vector<double> rates; + for(size_t div = 1; div <= 16+16; div++) + rates.push_back(master_clock_rate/div); + return rates; + } + + void set_rx_dboard_clock_rate(double rate){ + assert_has(get_rx_dboard_clock_rates(), rate, "rx dboard clock rate"); + size_t divider = size_t(master_clock_rate/rate); + //set the divider registers + set_clock_divider(divider, + _ad9522_regs.divider3_low_cycles, + _ad9522_regs.divider3_high_cycles, + _ad9522_regs.divider3_bypass + ); + this->send_reg(0x199); + this->send_reg(0x19a); + this->latch_regs(); + } + + /*********************************************************************** + * TX Dboard Clock Control (output 6, divider 2) + **********************************************************************/ + void enable_tx_dboard_clock(bool enb){ + _ad9522_regs.out6_format = ad9522_regs_t::OUT6_FORMAT_CMOS; + _ad9522_regs.out6_cmos_configuration = (enb)? + ad9522_regs_t::OUT6_CMOS_CONFIGURATION_B_ON : + ad9522_regs_t::OUT6_CMOS_CONFIGURATION_OFF; + this->send_reg(0x0F6); + this->latch_regs(); + } + + std::vector<double> get_tx_dboard_clock_rates(void){ + return get_rx_dboard_clock_rates(); //same master clock, same dividers... + } + + void set_tx_dboard_clock_rate(double rate){ + assert_has(get_tx_dboard_clock_rates(), rate, "tx dboard clock rate"); + size_t divider = size_t(master_clock_rate/rate); + //set the divider registers + set_clock_divider(divider, + _ad9522_regs.divider2_low_cycles, + _ad9522_regs.divider2_high_cycles, + _ad9522_regs.divider2_bypass + ); + this->send_reg(0x196); + this->send_reg(0x197); + this->latch_regs(); + } + + /*********************************************************************** + * Clock reference control + **********************************************************************/ + void use_internal_ref(void) { + _ad9522_regs.enable_ref2 = 1; + _ad9522_regs.enable_ref1 = 0; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF2; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; + this->send_reg(0x01C); + } + + void use_external_ref(void) { + _ad9522_regs.enable_ref2 = 0; + _ad9522_regs.enable_ref1 = 1; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; + this->send_reg(0x01C); + } + + void use_auto_ref(void) { + _ad9522_regs.enable_ref2 = 1; + _ad9522_regs.enable_ref1 = 1; + _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; + _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_AUTO; + } + +private: + usrp_e100_iface::sptr _iface; + ad9522_regs_t _ad9522_regs; + + void latch_regs(void){ + _ad9522_regs.io_update = 1; + this->send_reg(0x232); + } + + void send_reg(boost::uint16_t addr){ + boost::uint32_t reg = _ad9522_regs.get_write_reg(addr); + //std::cout << "clock control write reg: " << std::hex << reg << std::endl; + _iface->transact_spi( + UE_SPI_SS_AD9522, + spi_config_t::EDGE_RISE, + reg, 24, false /*no rb*/ + ); + } +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +usrp_e100_clock_ctrl::sptr usrp_e100_clock_ctrl::make(usrp_e100_iface::sptr iface){ + return sptr(new usrp_e100_clock_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp_e100/clock_ctrl.hpp b/host/lib/usrp/usrp_e100/clock_ctrl.hpp new file mode 100644 index 000000000..d613d1473 --- /dev/null +++ b/host/lib/usrp/usrp_e100/clock_ctrl.hpp @@ -0,0 +1,103 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_CLOCK_CTRL_HPP +#define INCLUDED_USRP_E100_CLOCK_CTRL_HPP + +#include "usrp_e100_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp-e clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class usrp_e100_clock_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp_e100_clock_ctrl> sptr; + + /*! + * Make a new clock control object. + * \param iface the usrp_e100 iface object + * \return the clock control object + */ + static sptr make(usrp_e100_iface::sptr iface); + + /*! + * Get the rate of the fpga clock line. + * \return the fpga clock rate in Hz + */ + virtual double get_fpga_clock_rate(void) = 0; + + /*! + * Get the possible rates of the rx dboard clock. + * \return a vector of clock rates in Hz + */ + virtual std::vector<double> get_rx_dboard_clock_rates(void) = 0; + + /*! + * Get the possible rates of the tx dboard clock. + * \return a vector of clock rates in Hz + */ + virtual std::vector<double> get_tx_dboard_clock_rates(void) = 0; + + /*! + * Set the rx dboard clock rate to a possible rate. + * \param rate the new clock rate in Hz + * \throw exception when rate cannot be achieved + */ + virtual void set_rx_dboard_clock_rate(double rate) = 0; + + /*! + * Set the tx dboard clock rate to a possible rate. + * \param rate the new clock rate in Hz + * \throw exception when rate cannot be achieved + */ + virtual void set_tx_dboard_clock_rate(double rate) = 0; + + /*! + * Enable/disable the rx dboard clock. + * \param enb true to enable + */ + virtual void enable_rx_dboard_clock(bool enb) = 0; + + /*! + * Enable/disable the tx dboard clock. + * \param enb true to enable + */ + virtual void enable_tx_dboard_clock(bool enb) = 0; + + /*! + * Use the internal TCXO reference + */ + virtual void use_internal_ref(void) = 0; + + /*! + * Use the external SMA reference + */ + virtual void use_external_ref(void) = 0; + + /*! + * Use external if available, internal otherwise + */ + virtual void use_auto_ref(void) = 0; + +}; + +#endif /* INCLUDED_USRP_E100_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp_e100/codec_ctrl.cpp b/host/lib/usrp/usrp_e100/codec_ctrl.cpp new file mode 100644 index 000000000..e7fd9792e --- /dev/null +++ b/host/lib/usrp/usrp_e100/codec_ctrl.cpp @@ -0,0 +1,296 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/cstdint.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include "usrp_e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <iostream> + +using namespace uhd; + +static const bool codec_debug = false; + +const gain_range_t usrp_e100_codec_ctrl::tx_pga_gain_range(-20, 0, float(0.1)); +const gain_range_t usrp_e100_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class usrp_e100_codec_ctrl_impl : public usrp_e100_codec_ctrl{ +public: + //structors + usrp_e100_codec_ctrl_impl(usrp_e100_iface::sptr iface); + ~usrp_e100_codec_ctrl_impl(void); + + //aux adc and dac control + float read_aux_adc(aux_adc_t which); + void write_aux_dac(aux_dac_t which, float volts); + + //pga gain control + void set_tx_pga_gain(float); + float get_tx_pga_gain(void); + void set_rx_pga_gain(float, char); + float get_rx_pga_gain(char); + +private: + usrp_e100_iface::sptr _iface; + ad9862_regs_t _ad9862_regs; + aux_adc_t _last_aux_adc_a, _last_aux_adc_b; + void send_reg(boost::uint8_t addr); + void recv_reg(boost::uint8_t addr); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +usrp_e100_codec_ctrl_impl::usrp_e100_codec_ctrl_impl(usrp_e100_iface::sptr iface){ + _iface = iface; + + //soft reset + _ad9862_regs.soft_reset = 1; + this->send_reg(0); + + //initialize the codec register settings + _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; + _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; + _ad9862_regs.soft_reset = 0; + + //setup rx side of codec + _ad9862_regs.byp_buffer_a = 1; + _ad9862_regs.byp_buffer_b = 1; + _ad9862_regs.buffer_a_pd = 1; + _ad9862_regs.buffer_b_pd = 1; + _ad9862_regs.rx_pga_a = 0;//0x1f; //TODO bring under api control + _ad9862_regs.rx_pga_b = 0;//0x1f; //TODO bring under api control + _ad9862_regs.rx_twos_comp = 1; + _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; + + //setup tx side of codec + _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; + _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; + _ad9862_regs.tx_retime = ad9862_regs_t::TX_RETIME_CLKOUT2; + _ad9862_regs.tx_pga_gain = 199; //TODO bring under api control + _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; + _ad9862_regs.interp = ad9862_regs_t::INTERP_2; + _ad9862_regs.tx_twos_comp = 1; + _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_BYPASS; + _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; + _ad9862_regs.dac_a_coarse_gain = 0x3; + _ad9862_regs.dac_b_coarse_gain = 0x3; + _ad9862_regs.edges = ad9862_regs_t::EDGES_NORMAL; + + //setup the dll + _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; + _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; + _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; + + //write the register settings to the codec + for (uint8_t addr = 0; addr <= 25; addr++){ + this->send_reg(addr); + } + + //aux adc clock + _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; + this->send_reg(34); +} + +usrp_e100_codec_ctrl_impl::~usrp_e100_codec_ctrl_impl(void){ + //set aux dacs to zero + this->write_aux_dac(AUX_DAC_A, 0); + this->write_aux_dac(AUX_DAC_B, 0); + this->write_aux_dac(AUX_DAC_C, 0); + this->write_aux_dac(AUX_DAC_D, 0); + + //power down + _ad9862_regs.all_rx_pd = 1; + this->send_reg(1); + _ad9862_regs.tx_digital_pd = 1; + _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; + this->send_reg(8); +} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void usrp_e100_codec_ctrl_impl::set_tx_pga_gain(float gain){ + int gain_word = int(mtpgw*(gain - tx_pga_gain_range.min)/(tx_pga_gain_range.max - tx_pga_gain_range.min)); + _ad9862_regs.tx_pga_gain = std::clip(gain_word, 0, mtpgw); + this->send_reg(16); +} + +float usrp_e100_codec_ctrl_impl::get_tx_pga_gain(void){ + return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.max - tx_pga_gain_range.min)/mtpgw) + tx_pga_gain_range.min; +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void usrp_e100_codec_ctrl_impl::set_rx_pga_gain(float gain, char which){ + int gain_word = int(mrpgw*(gain - rx_pga_gain_range.min)/(rx_pga_gain_range.max - rx_pga_gain_range.min)); + gain_word = std::clip(gain_word, 0, mrpgw); + switch(which){ + case 'A': + _ad9862_regs.rx_pga_a = gain_word; + this->send_reg(2); + return; + case 'B': + _ad9862_regs.rx_pga_b = gain_word; + this->send_reg(3); + return; + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +float usrp_e100_codec_ctrl_impl::get_rx_pga_gain(char which){ + int gain_word; + switch(which){ + case 'A': gain_word = _ad9862_regs.rx_pga_a; break; + case 'B': gain_word = _ad9862_regs.rx_pga_b; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + return (gain_word*(rx_pga_gain_range.max - rx_pga_gain_range.min)/mrpgw) + rx_pga_gain_range.min; +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static float aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low){ + return float((boost::uint16_t(high) << 2) | low)*3.3/0x3ff; +} + +float usrp_e100_codec_ctrl_impl::read_aux_adc(aux_adc_t which){ + //check to see if the switch needs to be set + bool write_switch = false; + switch(which){ + + case AUX_ADC_A1: + case AUX_ADC_A2: + if (which != _last_aux_adc_a){ + _ad9862_regs.select_a = (which == AUX_ADC_A1)? + ad9862_regs_t::SELECT_A_AUX_ADC1: ad9862_regs_t::SELECT_A_AUX_ADC2; + _last_aux_adc_a = which; + write_switch = true; + } + break; + + case AUX_ADC_B1: + case AUX_ADC_B2: + if (which != _last_aux_adc_b){ + _ad9862_regs.select_b = (which == AUX_ADC_B1)? + ad9862_regs_t::SELECT_B_AUX_ADC1: ad9862_regs_t::SELECT_B_AUX_ADC2; + _last_aux_adc_b = which; + write_switch = true; + } + break; + + } + + //write the switch if it changed + if(write_switch) this->send_reg(34); + + //map aux adcs to register values to read + static const uhd::dict<aux_adc_t, boost::uint8_t> aux_dac_to_addr = boost::assign::map_list_of + (AUX_ADC_A2, 26) (AUX_ADC_A1, 28) + (AUX_ADC_B2, 30) (AUX_ADC_B1, 32) + ; + + //read the value + this->recv_reg(aux_dac_to_addr[which]+0); + this->recv_reg(aux_dac_to_addr[which]+1); + + //return the value scaled to volts + switch(which){ + case AUX_ADC_A1: return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); + case AUX_ADC_A2: return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); + case AUX_ADC_B1: return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); + case AUX_ADC_B2: return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); + } + UHD_ASSERT_THROW(false); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void usrp_e100_codec_ctrl_impl::write_aux_dac(aux_dac_t which, float volts){ + //special case for aux dac d (aka sigma delta word) + if (which == AUX_DAC_D){ + boost::uint16_t dac_word = std::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); + _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); + _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); + this->send_reg(42); + this->send_reg(43); + return; + } + + //calculate the dac word for aux dac a, b, c + boost::uint8_t dac_word = std::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + + //setup a lookup table for the aux dac params (reg ref, reg addr) + typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; + uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of + (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) + (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) + (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) + ; + + //set the aux dac register + UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); + boost::uint8_t *reg_ref, reg_addr; + boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; + *reg_ref = dac_word; + this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void usrp_e100_codec_ctrl_impl::send_reg(boost::uint8_t addr){ + boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); + if (codec_debug) std::cout << "codec control write reg: " << std::hex << reg << std::endl; + _iface->transact_spi( + UE_SPI_SS_AD9862, + spi_config_t::EDGE_RISE, + reg, 16, false /*no rb*/ + ); +} + +void usrp_e100_codec_ctrl_impl::recv_reg(boost::uint8_t addr){ + boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); + if (codec_debug) std::cout << "codec control read reg: " << std::hex << reg << std::endl; + boost::uint32_t ret = _iface->transact_spi( + UE_SPI_SS_AD9862, + spi_config_t::EDGE_RISE, + reg, 16, true /*rb*/ + ); + if (codec_debug) std::cout << "codec control read ret: " << std::hex << ret << std::endl; + _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +usrp_e100_codec_ctrl::sptr usrp_e100_codec_ctrl::make(usrp_e100_iface::sptr iface){ + return sptr(new usrp_e100_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp_e100/codec_ctrl.hpp b/host/lib/usrp/usrp_e100/codec_ctrl.hpp new file mode 100644 index 000000000..74ce9bd9a --- /dev/null +++ b/host/lib/usrp/usrp_e100/codec_ctrl.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_CODEC_CTRL_HPP +#define INCLUDED_USRP_E100_CODEC_CTRL_HPP + +#include "usrp_e100_iface.hpp" +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp-e codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class usrp_e100_codec_ctrl : boost::noncopyable{ +public: + typedef boost::shared_ptr<usrp_e100_codec_ctrl> sptr; + + static const uhd::gain_range_t tx_pga_gain_range; + static const uhd::gain_range_t rx_pga_gain_range; + + /*! + * Make a new codec control object. + * \param iface the usrp_e100 iface object + * \return the codec control object + */ + static sptr make(usrp_e100_iface::sptr iface); + + //! aux adc identifier constants + enum aux_adc_t{ + AUX_ADC_A2 = 0xA2, + AUX_ADC_A1 = 0xA1, + AUX_ADC_B2 = 0xB2, + AUX_ADC_B1 = 0xB1 + }; + + /*! + * Read an auxiliary adc: + * The internals remember which aux adc was read last. + * Therefore, the aux adc switch is only changed as needed. + * \param which which of the 4 adcs + * \return a value in volts + */ + virtual float read_aux_adc(aux_adc_t which) = 0; + + //! aux dac identifier constants + enum aux_dac_t{ + AUX_DAC_A = 0xA, + AUX_DAC_B = 0xB, + AUX_DAC_C = 0xC, + AUX_DAC_D = 0xD //really the sigma delta output + }; + + /*! + * Write an auxiliary dac. + * \param which which of the 4 dacs + * \param volts the level in in volts + */ + virtual void write_aux_dac(aux_dac_t which, float volts) = 0; + + //! Set the TX PGA gain + virtual void set_tx_pga_gain(float gain) = 0; + + //! Get the TX PGA gain + virtual float get_tx_pga_gain(void) = 0; + + //! Set the RX PGA gain ('A' or 'B') + virtual void set_rx_pga_gain(float gain, char which) = 0; + + //! Get the RX PGA gain ('A' or 'B') + virtual float get_rx_pga_gain(char which) = 0; +}; + +#endif /* INCLUDED_USRP_E100_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp_e100/codec_impl.cpp b/host/lib/usrp/usrp_e100/codec_impl.cpp new file mode 100644 index 000000000..6fd44bad3 --- /dev/null +++ b/host/lib/usrp/usrp_e100/codec_impl.cpp @@ -0,0 +1,149 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/codec_props.hpp> +#include <boost/bind.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Helper Methods + **********************************************************************/ +void usrp_e100_impl::codec_init(void){ + //make proxies + _rx_codec_proxy = wax_obj_proxy::make( + boost::bind(&usrp_e100_impl::rx_codec_get, this, _1, _2), + boost::bind(&usrp_e100_impl::rx_codec_set, this, _1, _2) + ); + _tx_codec_proxy = wax_obj_proxy::make( + boost::bind(&usrp_e100_impl::tx_codec_get, this, _1, _2), + boost::bind(&usrp_e100_impl::tx_codec_set, this, _1, _2) + ); +} + +/*********************************************************************** + * RX Codec Properties + **********************************************************************/ +static const std::string ad9862_pga_gain_name = "ad9862 pga"; + +void usrp_e100_impl::rx_codec_get(const wax::obj &key_, wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<codec_prop_t>()){ + case CODEC_PROP_NAME: + val = std::string("usrp-e adc - ad9522"); + return; + + case CODEC_PROP_OTHERS: + val = prop_names_t(); + return; + + case CODEC_PROP_GAIN_NAMES: + val = prop_names_t(1, ad9862_pga_gain_name); + return; + + case CODEC_PROP_GAIN_RANGE: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = usrp_e100_codec_ctrl::rx_pga_gain_range; + return; + + case CODEC_PROP_GAIN_I: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = _codec_ctrl->get_rx_pga_gain('A'); + return; + + case CODEC_PROP_GAIN_Q: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = _codec_ctrl->get_rx_pga_gain('B'); + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +void usrp_e100_impl::rx_codec_set(const wax::obj &key_, const wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + //handle the set request conditioned on the key + switch(key.as<codec_prop_t>()){ + case CODEC_PROP_GAIN_I: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + _codec_ctrl->set_rx_pga_gain(val.as<float>(), 'A'); + return; + + case CODEC_PROP_GAIN_Q: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + _codec_ctrl->set_rx_pga_gain(val.as<float>(), 'B'); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} + +/*********************************************************************** + * TX Codec Properties + **********************************************************************/ +void usrp_e100_impl::tx_codec_get(const wax::obj &key_, wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<codec_prop_t>()){ + case CODEC_PROP_NAME: + val = std::string("usrp-e dac - ad9522"); + return; + + case CODEC_PROP_OTHERS: + val = prop_names_t(); + return; + + case CODEC_PROP_GAIN_NAMES: + val = prop_names_t(1, ad9862_pga_gain_name); + return; + + case CODEC_PROP_GAIN_RANGE: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = usrp_e100_codec_ctrl::tx_pga_gain_range; + return; + + case CODEC_PROP_GAIN_I: //only one gain for I and Q + case CODEC_PROP_GAIN_Q: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + val = _codec_ctrl->get_tx_pga_gain(); + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +void usrp_e100_impl::tx_codec_set(const wax::obj &key_, const wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + //handle the set request conditioned on the key + switch(key.as<codec_prop_t>()){ + case CODEC_PROP_GAIN_I: //only one gain for I and Q + case CODEC_PROP_GAIN_Q: + UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); + _codec_ctrl->set_tx_pga_gain(val.as<float>()); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} diff --git a/host/lib/usrp/usrp_e100/dboard_iface.cpp b/host/lib/usrp/usrp_e100/dboard_iface.cpp new file mode 100644 index 000000000..aa96171d6 --- /dev/null +++ b/host/lib/usrp/usrp_e100/dboard_iface.cpp @@ -0,0 +1,298 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_iface.hpp" +#include "usrp_e100_regs.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/assign/list_of.hpp> +#include <linux/usrp_e.h> //i2c and spi constants + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class usrp_e100_dboard_iface : public dboard_iface{ +public: + + usrp_e100_dboard_iface( + usrp_e100_iface::sptr iface, + usrp_e100_clock_ctrl::sptr clock, + usrp_e100_codec_ctrl::sptr codec + ){ + _iface = iface; + _clock = clock; + _codec = codec; + + //init the clock rate shadows + this->set_clock_rate(UNIT_RX, _clock->get_fpga_clock_rate()); + this->set_clock_rate(UNIT_TX, _clock->get_fpga_clock_rate()); + + _iface->poke16(UE_REG_GPIO_RX_DBG, 0); + _iface->poke16(UE_REG_GPIO_TX_DBG, 0); + } + + ~usrp_e100_dboard_iface(void){ + /* NOP */ + } + + special_props_t get_special_props(void){ + special_props_t props; + props.soft_clock_divider = false; + props.mangle_i2c_addrs = false; + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, float); + float read_aux_adc(unit_t, aux_adc_t); + + void set_pin_ctrl(unit_t, boost::uint16_t); + void set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); + void set_gpio_ddr(unit_t, boost::uint16_t); + void write_gpio(unit_t, boost::uint16_t); + void set_gpio_debug(unit_t, int); + boost::uint16_t read_gpio(unit_t); + + void write_i2c(boost::uint8_t, const byte_vector_t &); + byte_vector_t read_i2c(boost::uint8_t, size_t); + + void write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + boost::uint32_t read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + void set_clock_rate(unit_t, double); + std::vector<double> get_clock_rates(unit_t); + double get_clock_rate(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + +private: + usrp_e100_iface::sptr _iface; + usrp_e100_clock_ctrl::sptr _clock; + usrp_e100_codec_ctrl::sptr _codec; + uhd::dict<unit_t, double> _clock_rates; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_usrp_e100_dboard_iface( + usrp_e100_iface::sptr iface, + usrp_e100_clock_ctrl::sptr clock, + usrp_e100_codec_ctrl::sptr codec +){ + return dboard_iface::sptr(new usrp_e100_dboard_iface(iface, clock, codec)); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +void usrp_e100_dboard_iface::set_clock_rate(unit_t unit, double rate){ + _clock_rates[unit] = rate; + switch(unit){ + case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); + case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + } +} + +std::vector<double> usrp_e100_dboard_iface::get_clock_rates(unit_t unit){ + switch(unit){ + case UNIT_RX: return _clock->get_rx_dboard_clock_rates(); + case UNIT_TX: return _clock->get_tx_dboard_clock_rates(); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +double usrp_e100_dboard_iface::get_clock_rate(unit_t unit){ + return _clock_rates[unit]; +} + +void usrp_e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ + switch(unit){ + case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); + case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + } +} + +double usrp_e100_dboard_iface::get_codec_rate(unit_t){ + return _clock->get_fpga_clock_rate(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void usrp_e100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint16_t value){ + UHD_ASSERT_THROW(GPIO_SEL_ATR == 1); //make this assumption + switch(unit){ + case UNIT_RX: _iface->poke16(UE_REG_GPIO_RX_SEL, value); return; + case UNIT_TX: _iface->poke16(UE_REG_GPIO_TX_SEL, value); return; + } +} + +void usrp_e100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint16_t value){ + switch(unit){ + case UNIT_RX: _iface->poke16(UE_REG_GPIO_RX_DDR, value); return; + case UNIT_TX: _iface->poke16(UE_REG_GPIO_TX_DDR, value); return; + } +} + +void usrp_e100_dboard_iface::write_gpio(unit_t unit, boost::uint16_t value){ + switch(unit){ + case UNIT_RX: _iface->poke16(UE_REG_GPIO_RX_IO, value); return; + case UNIT_TX: _iface->poke16(UE_REG_GPIO_TX_IO, value); return; + } +} + +boost::uint16_t usrp_e100_dboard_iface::read_gpio(unit_t unit){ + switch(unit){ + case UNIT_RX: return _iface->peek16(UE_REG_GPIO_RX_IO); + case UNIT_TX: return _iface->peek16(UE_REG_GPIO_TX_IO); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +void usrp_e100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ + //define mapping of unit to atr regs to register address + static const uhd::dict< + unit_t, uhd::dict<atr_reg_t, boost::uint32_t> + > unit_to_atr_to_addr = map_list_of + (UNIT_RX, map_list_of + (ATR_REG_IDLE, UE_REG_ATR_IDLE_RXSIDE) + (ATR_REG_TX_ONLY, UE_REG_ATR_INTX_RXSIDE) + (ATR_REG_RX_ONLY, UE_REG_ATR_INRX_RXSIDE) + (ATR_REG_FULL_DUPLEX, UE_REG_ATR_FULL_RXSIDE) + ) + (UNIT_TX, map_list_of + (ATR_REG_IDLE, UE_REG_ATR_IDLE_TXSIDE) + (ATR_REG_TX_ONLY, UE_REG_ATR_INTX_TXSIDE) + (ATR_REG_RX_ONLY, UE_REG_ATR_INRX_TXSIDE) + (ATR_REG_FULL_DUPLEX, UE_REG_ATR_FULL_TXSIDE) + ) + ; + _iface->poke16(unit_to_atr_to_addr[unit][atr], value); +} + +void usrp_e100_dboard_iface::set_gpio_debug(unit_t unit, int which){ + //set this unit to all outputs + this->set_gpio_ddr(unit, 0xffff); + + //calculate the debug selections + boost::uint32_t dbg_sels = 0x0; + int sel = (which == 0)? GPIO_SEL_DEBUG_0 : GPIO_SEL_DEBUG_1; + for(size_t i = 0; i < 16; i++) dbg_sels |= sel << i; + + //set the debug on and which debug selection + switch(unit){ + case UNIT_RX: + _iface->poke16(UE_REG_GPIO_RX_DBG, 0xffff); + _iface->poke16(UE_REG_GPIO_RX_SEL, dbg_sels); + return; + + case UNIT_TX: + _iface->poke16(UE_REG_GPIO_TX_DBG, 0xffff); + _iface->poke16(UE_REG_GPIO_TX_SEL, dbg_sels); + return; + } +} + +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ + switch(unit){ + case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB; + case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB; + } + throw std::invalid_argument("unknown unit type"); +} + +void usrp_e100_dboard_iface::write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + _iface->transact_spi(unit_to_otw_spi_dev(unit), config, data, num_bits, false /*no rb*/); +} + +boost::uint32_t usrp_e100_dboard_iface::read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + return _iface->transact_spi(unit_to_otw_spi_dev(unit), config, data, num_bits, true /*rb*/); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp_e100_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ + return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp_e100_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ + return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp_e100_dboard_iface::write_aux_dac(dboard_iface::unit_t, aux_dac_t which, float value){ + //same aux dacs for each unit + static const uhd::dict<aux_dac_t, usrp_e100_codec_ctrl::aux_dac_t> which_to_aux_dac = map_list_of + (AUX_DAC_A, usrp_e100_codec_ctrl::AUX_DAC_A) + (AUX_DAC_B, usrp_e100_codec_ctrl::AUX_DAC_B) + (AUX_DAC_C, usrp_e100_codec_ctrl::AUX_DAC_C) + (AUX_DAC_D, usrp_e100_codec_ctrl::AUX_DAC_D) + ; + _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +float usrp_e100_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, aux_adc_t which){ + static const uhd::dict< + unit_t, uhd::dict<aux_adc_t, usrp_e100_codec_ctrl::aux_adc_t> + > unit_to_which_to_aux_adc = map_list_of + (UNIT_RX, map_list_of + (AUX_ADC_A, usrp_e100_codec_ctrl::AUX_ADC_A1) + (AUX_ADC_B, usrp_e100_codec_ctrl::AUX_ADC_B1) + ) + (UNIT_TX, map_list_of + (AUX_ADC_A, usrp_e100_codec_ctrl::AUX_ADC_A2) + (AUX_ADC_B, usrp_e100_codec_ctrl::AUX_ADC_B2) + ) + ; + return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/usrp_e100/dboard_impl.cpp b/host/lib/usrp/usrp_e100/dboard_impl.cpp new file mode 100644 index 000000000..9f2bfb8ae --- /dev/null +++ b/host/lib/usrp/usrp_e100/dboard_impl.cpp @@ -0,0 +1,172 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/misc_utils.hpp> +#include <boost/bind.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Dboard Initialization + **********************************************************************/ +void usrp_e100_impl::dboard_init(void){ + _rx_db_eeprom = dboard_eeprom_t(_iface->read_eeprom(I2C_ADDR_RX_DB, 0, dboard_eeprom_t::num_bytes())); + _tx_db_eeprom = dboard_eeprom_t(_iface->read_eeprom(I2C_ADDR_TX_DB, 0, dboard_eeprom_t::num_bytes())); + + //create a new dboard interface and manager + _dboard_iface = make_usrp_e100_dboard_iface( + _iface, _clock_ctrl, _codec_ctrl + ); + _dboard_manager = dboard_manager::make( + _rx_db_eeprom.id, _tx_db_eeprom.id, _dboard_iface + ); + + //setup the dboard proxies + _rx_dboard_proxy = wax_obj_proxy::make( + boost::bind(&usrp_e100_impl::rx_dboard_get, this, _1, _2), + boost::bind(&usrp_e100_impl::rx_dboard_set, this, _1, _2) + ); + _tx_dboard_proxy = wax_obj_proxy::make( + boost::bind(&usrp_e100_impl::tx_dboard_get, this, _1, _2), + boost::bind(&usrp_e100_impl::tx_dboard_set, this, _1, _2) + ); +} + +/*********************************************************************** + * RX Dboard Get + **********************************************************************/ +void usrp_e100_impl::rx_dboard_get(const wax::obj &key_, wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<dboard_prop_t>()){ + case DBOARD_PROP_NAME: + val = std::string("usrp-e dboard (rx unit)"); + return; + + case DBOARD_PROP_SUBDEV: + val = _dboard_manager->get_rx_subdev(key.name); + return; + + case DBOARD_PROP_SUBDEV_NAMES: + val = _dboard_manager->get_rx_subdev_names(); + return; + + case DBOARD_PROP_DBOARD_ID: + val = _rx_db_eeprom.id; + return; + + case DBOARD_PROP_DBOARD_IFACE: + val = _dboard_iface; + return; + + case DBOARD_PROP_CODEC: + val = _rx_codec_proxy->get_link(); + return; + + case DBOARD_PROP_GAIN_GROUP: + val = make_gain_group( + _rx_db_eeprom.id, + _dboard_manager->get_rx_subdev(key.name), + _rx_codec_proxy->get_link(), + GAIN_GROUP_POLICY_RX + ); + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * RX Dboard Set + **********************************************************************/ +void usrp_e100_impl::rx_dboard_set(const wax::obj &key, const wax::obj &val){ + switch(key.as<dboard_prop_t>()){ + case DBOARD_PROP_DBOARD_ID: + _rx_db_eeprom.id = val.as<dboard_id_t>(); + _iface->write_eeprom(I2C_ADDR_RX_DB, 0, _rx_db_eeprom.get_eeprom_bytes()); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} + +/*********************************************************************** + * TX Dboard Get + **********************************************************************/ +void usrp_e100_impl::tx_dboard_get(const wax::obj &key_, wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<dboard_prop_t>()){ + case DBOARD_PROP_NAME: + val = std::string("usrp-e dboard (tx unit)"); + return; + + case DBOARD_PROP_SUBDEV: + val = _dboard_manager->get_tx_subdev(key.name); + return; + + case DBOARD_PROP_SUBDEV_NAMES: + val = _dboard_manager->get_tx_subdev_names(); + return; + + case DBOARD_PROP_DBOARD_ID: + val = _tx_db_eeprom.id; + return; + + case DBOARD_PROP_DBOARD_IFACE: + val = _dboard_iface; + return; + + case DBOARD_PROP_CODEC: + val = _tx_codec_proxy->get_link(); + return; + + case DBOARD_PROP_GAIN_GROUP: + val = make_gain_group( + _tx_db_eeprom.id, + _dboard_manager->get_tx_subdev(key.name), + _tx_codec_proxy->get_link(), + GAIN_GROUP_POLICY_TX + ); + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * TX Dboard Set + **********************************************************************/ +void usrp_e100_impl::tx_dboard_set(const wax::obj &key, const wax::obj &val){ + switch(key.as<dboard_prop_t>()){ + case DBOARD_PROP_DBOARD_ID: + _tx_db_eeprom.id = val.as<dboard_id_t>(); + _iface->write_eeprom(I2C_ADDR_TX_DB, 0, _tx_db_eeprom.get_eeprom_bytes()); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} diff --git a/host/lib/usrp/usrp_e100/dsp_impl.cpp b/host/lib/usrp/usrp_e100/dsp_impl.cpp new file mode 100644 index 000000000..43a3bd3be --- /dev/null +++ b/host/lib/usrp/usrp_e100/dsp_impl.cpp @@ -0,0 +1,192 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/bind.hpp> + +#define rint boost::math::iround + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * RX DDC Initialization + **********************************************************************/ +void usrp_e100_impl::rx_ddc_init(void){ + _rx_ddc_proxy = wax_obj_proxy::make( + boost::bind(&usrp_e100_impl::rx_ddc_get, this, _1, _2), + boost::bind(&usrp_e100_impl::rx_ddc_set, this, _1, _2) + ); + + //initial config and update + rx_ddc_set(DSP_PROP_FREQ_SHIFT, double(0)); + rx_ddc_set(DSP_PROP_HOST_RATE, double(64e6/10)); +} + +/*********************************************************************** + * RX DDC Get + **********************************************************************/ +void usrp_e100_impl::rx_ddc_get(const wax::obj &key_, wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + switch(key.as<dsp_prop_t>()){ + case DSP_PROP_NAME: + val = std::string("usrp-e ddc0"); + return; + + case DSP_PROP_OTHERS: + val = prop_names_t(); //empty + return; + + case DSP_PROP_FREQ_SHIFT: + val = _ddc_freq; + return; + + case DSP_PROP_FREQ_SHIFT_NAMES: + val = prop_names_t(1, ""); + return; + + case DSP_PROP_CODEC_RATE: + val = _clock_ctrl->get_fpga_clock_rate(); + return; + + case DSP_PROP_HOST_RATE: + val = _clock_ctrl->get_fpga_clock_rate()/_ddc_decim; + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * RX DDC Set + **********************************************************************/ +void usrp_e100_impl::rx_ddc_set(const wax::obj &key_, const wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + switch(key.as<dsp_prop_t>()){ + + case DSP_PROP_FREQ_SHIFT:{ + double new_freq = val.as<double>(); + _iface->poke32(UE_REG_DSP_RX_FREQ, + dsp_type1::calc_cordic_word_and_update(new_freq, _clock_ctrl->get_fpga_clock_rate()) + ); + _ddc_freq = new_freq; //shadow + } + return; + + case DSP_PROP_HOST_RATE:{ + //set the decimation + _ddc_decim = rint(_clock_ctrl->get_fpga_clock_rate()/val.as<double>()); + _iface->poke32(UE_REG_DSP_RX_DECIM_RATE, dsp_type1::calc_cic_filter_word(_ddc_decim)); + + //set the scaling + static const boost::int16_t default_rx_scale_iq = 1024; + _iface->poke32(UE_REG_DSP_RX_SCALE_IQ, + dsp_type1::calc_iq_scale_word(default_rx_scale_iq, default_rx_scale_iq) + ); + } + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} + +/*********************************************************************** + * TX DUC Initialization + **********************************************************************/ +void usrp_e100_impl::tx_duc_init(void){ + _tx_duc_proxy = wax_obj_proxy::make( + boost::bind(&usrp_e100_impl::tx_duc_get, this, _1, _2), + boost::bind(&usrp_e100_impl::tx_duc_set, this, _1, _2) + ); + + //initial config and update + tx_duc_set(DSP_PROP_FREQ_SHIFT, double(0)); + tx_duc_set(DSP_PROP_HOST_RATE, double(64e6/10)); +} + +/*********************************************************************** + * TX DUC Get + **********************************************************************/ +void usrp_e100_impl::tx_duc_get(const wax::obj &key_, wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + switch(key.as<dsp_prop_t>()){ + case DSP_PROP_NAME: + val = std::string("usrp-e duc0"); + return; + + case DSP_PROP_OTHERS: + val = prop_names_t(); //empty + return; + + case DSP_PROP_FREQ_SHIFT: + val = _duc_freq; + return; + + case DSP_PROP_FREQ_SHIFT_NAMES: + val = prop_names_t(1, ""); + return; + + case DSP_PROP_CODEC_RATE: + val = _clock_ctrl->get_fpga_clock_rate(); + return; + + case DSP_PROP_HOST_RATE: + val = _clock_ctrl->get_fpga_clock_rate()/_duc_interp; + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * TX DUC Set + **********************************************************************/ +void usrp_e100_impl::tx_duc_set(const wax::obj &key_, const wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + switch(key.as<dsp_prop_t>()){ + + case DSP_PROP_FREQ_SHIFT:{ + double new_freq = val.as<double>(); + _iface->poke32(UE_REG_DSP_TX_FREQ, + dsp_type1::calc_cordic_word_and_update(new_freq, _clock_ctrl->get_fpga_clock_rate()) + ); + _duc_freq = new_freq; //shadow + } + return; + + case DSP_PROP_HOST_RATE:{ + _duc_interp = rint(_clock_ctrl->get_fpga_clock_rate()/val.as<double>()); + + //set the interpolation + _iface->poke32(UE_REG_DSP_TX_INTERP_RATE, dsp_type1::calc_cic_filter_word(_duc_interp)); + + //set the scaling + _iface->poke32(UE_REG_DSP_TX_SCALE_IQ, dsp_type1::calc_iq_scale_word(_duc_interp)); + } + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} diff --git a/host/lib/usrp/usrp_e100/fpga-downloader.cc b/host/lib/usrp/usrp_e100/fpga-downloader.cc new file mode 100644 index 000000000..4a3d3b9af --- /dev/null +++ b/host/lib/usrp/usrp_e100/fpga-downloader.cc @@ -0,0 +1,274 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#include <uhd/utils/assert.hpp> + +#include <iostream> +#include <sstream> +#include <fstream> +#include <string> +#include <cstdlib> +#include <stdexcept> + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <linux/spi/spidev.h> + +/* + * Configuration connections + * + * CCK - MCSPI1_CLK + * DIN - MCSPI1_MOSI + * PROG_B - GPIO_175 - output (change mux) + * DONE - GPIO_173 - input (change mux) + * INIT_B - GPIO_114 - input (change mux) + * +*/ + +const unsigned int PROG_B = 175; +const unsigned int DONE = 173; +const unsigned int INIT_B = 114; + +//static std::string bit_file = "safe_u1e.bin"; + +const int BUF_SIZE = 4096; + +enum gpio_direction {IN, OUT}; + +class gpio { + public: + + gpio(unsigned int gpio_num, gpio_direction pin_direction); + + bool get_value(); + void set_value(bool state); + + private: + + std::stringstream base_path; + std::fstream value_file; +}; + +class spidev { + public: + + spidev(std::string dev_name); + ~spidev(); + + void send(char *wbuf, char *rbuf, unsigned int nbytes); + + private: + + int fd; + +}; + +gpio::gpio(unsigned int gpio_num, gpio_direction pin_direction) +{ + std::fstream export_file; + + export_file.open("/sys/class/gpio/export", std::ios::out); + if (not export_file.is_open()) throw std::runtime_error( + "Failed to open gpio export file." + ); + + export_file << gpio_num << std::endl; + + base_path << "/sys/class/gpio/gpio" << gpio_num << std::flush; + + std::fstream direction_file; + std::string direction_file_name; + + if (gpio_num != 114) { + direction_file_name = base_path.str() + "/direction"; + + direction_file.open(direction_file_name.c_str()); + if (!direction_file.is_open()) + std::cout << "Failed to open direction file." << std::endl; + if (pin_direction == OUT) + direction_file << "out" << std::endl; + else + direction_file << "in" << std::endl; + } + + std::string value_file_name; + + value_file_name = base_path.str() + "/value"; + + value_file.open(value_file_name.c_str(), std::ios_base::in | std::ios_base::out); + if (!value_file.is_open()) + std::cout << "Failed to open value file." << std::endl; +} + +bool gpio::get_value() +{ + + std::string val; + + std::getline(value_file, val); + value_file.seekg(0); + + if (val == "0") + return false; + else if (val == "1") + return true; + else + std::cout << "Data read from value file|" << val << "|" << std::endl; + + return false; +} + +void gpio::set_value(bool state) +{ + + if (state) + value_file << "1" << std::endl; + else + value_file << "0" << std::endl; +} + +static void prepare_fpga_for_configuration(gpio &prog, gpio &)//init) +{ + + prog.set_value(true); + prog.set_value(false); + prog.set_value(true); + +#if 0 + bool ready_to_program(false); + unsigned int count(0); + do { + ready_to_program = init.get_value(); + count++; + + sleep(1); + } while (count < 10 && !ready_to_program); + + if (count == 10) { + std::cout << "FPGA not ready for programming." << std::endl; + exit(-1); + } +#endif +} + +spidev::spidev(std::string fname) +{ + int ret; + int mode = 0; + int speed = 12000000; + int bits = 8; + + fd = open(fname.c_str(), O_RDWR); + + ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); + ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); + ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); +} + + +spidev::~spidev() +{ + close(fd); +} + +void spidev::send(char *buf, char *rbuf, unsigned int nbytes) +{ + int ret; + + struct spi_ioc_transfer tr; + tr.tx_buf = (unsigned long) buf; + tr.rx_buf = (unsigned long) rbuf; + tr.len = nbytes; + tr.delay_usecs = 0; + tr.speed_hz = 48000000; + tr.bits_per_word = 8; + + ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); + +} + +static void send_file_to_fpga(const std::string &file_name, gpio &error, gpio &done) +{ + std::ifstream bitstream; + + std::cout << "File name - " << file_name.c_str() << std::endl; + + bitstream.open(file_name.c_str(), std::ios::binary); + if (!bitstream.is_open()) + std::cout << "File " << file_name << " not opened succesfully." << std::endl; + + spidev spi("/dev/spidev1.0"); + char buf[BUF_SIZE]; + char rbuf[BUF_SIZE]; + + do { + bitstream.read(buf, BUF_SIZE); + spi.send(buf, rbuf, bitstream.gcount()); + + if (error.get_value()) + std::cout << "INIT_B went high, error occured." << std::endl; + + if (!done.get_value()) + std::cout << "Configuration complete." << std::endl; + + } while (bitstream.gcount() == BUF_SIZE); +} + +/* +int main(int argc, char *argv[]) +{ + + gpio gpio_prog_b(PROG_B, OUT); + gpio gpio_init_b(INIT_B, IN); + gpio gpio_done (DONE, IN); + + if (argc == 2) + bit_file = argv[1]; + + std::cout << "FPGA config file: " << bit_file << std::endl; + + prepare_fpga_for_configuration(gpio_prog_b, gpio_init_b); + + std::cout << "Done = " << gpio_done.get_value() << std::endl; + + send_file_to_fpga(bit_file, gpio_init_b, gpio_done); +} +*/ + +void usrp_e100_load_fpga(const std::string &bin_file){ + gpio gpio_prog_b(PROG_B, OUT); + gpio gpio_init_b(INIT_B, IN); + gpio gpio_done (DONE, IN); + + std::cout << "Loading FPGA image: " << bin_file << "... " << std::flush; + + UHD_ASSERT_THROW(std::system("/sbin/rmmod usrp_e") == 0); + + prepare_fpga_for_configuration(gpio_prog_b, gpio_init_b); + + std::cout << "done = " << gpio_done.get_value() << std::endl; + + send_file_to_fpga(bin_file, gpio_init_b, gpio_done); + + UHD_ASSERT_THROW(std::system("/sbin/modprobe usrp_e") == 0); + +} + diff --git a/host/lib/usrp/usrp_e100/include/linux/usrp_e.h b/host/lib/usrp/usrp_e100/include/linux/usrp_e.h new file mode 100644 index 000000000..4c6a5dd89 --- /dev/null +++ b/host/lib/usrp/usrp_e100/include/linux/usrp_e.h @@ -0,0 +1,91 @@ + +/* + * Copyright (C) 2010 Ettus Research, LLC + * + * Written by Philip Balister <philip@opensdr.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __USRP_E_H +#define __USRP_E_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +struct usrp_e_ctl16 { + __u32 offset; + __u32 count; + __u16 buf[20]; +}; + +struct usrp_e_ctl32 { + __u32 offset; + __u32 count; + __u32 buf[10]; +}; + +/* SPI interface */ + +#define UE_SPI_TXONLY 0 +#define UE_SPI_TXRX 1 + +/* Defines for spi ctrl register */ +#define UE_SPI_CTRL_TXNEG (1<<10) +#define UE_SPI_CTRL_RXNEG (1<<9) + +#define UE_SPI_PUSH_RISE 0 +#define UE_SPI_PUSH_FALL UE_SPI_CTRL_TXNEG +#define UE_SPI_LATCH_RISE 0 +#define UE_SPI_LATCH_FALL UE_SPI_CTRL_RXNEG + +struct usrp_e_spi { + __u8 readback; + __u32 slave; + __u32 data; + __u32 length; + __u32 flags; +}; + +struct usrp_e_i2c { + __u8 addr; + __u32 len; + __u8 data[]; +}; + +#define USRP_E_IOC_MAGIC 'u' +#define USRP_E_WRITE_CTL16 _IOW(USRP_E_IOC_MAGIC, 0x20, struct usrp_e_ctl16) +#define USRP_E_READ_CTL16 _IOWR(USRP_E_IOC_MAGIC, 0x21, struct usrp_e_ctl16) +#define USRP_E_WRITE_CTL32 _IOW(USRP_E_IOC_MAGIC, 0x22, struct usrp_e_ctl32) +#define USRP_E_READ_CTL32 _IOWR(USRP_E_IOC_MAGIC, 0x23, struct usrp_e_ctl32) +#define USRP_E_SPI _IOWR(USRP_E_IOC_MAGIC, 0x24, struct usrp_e_spi) +#define USRP_E_I2C_READ _IOWR(USRP_E_IOC_MAGIC, 0x25, struct usrp_e_i2c) +#define USRP_E_I2C_WRITE _IOW(USRP_E_IOC_MAGIC, 0x26, struct usrp_e_i2c) +#define USRP_E_GET_RB_INFO _IOR(USRP_E_IOC_MAGIC, 0x27, struct usrp_e_ring_buffer_size_t) +#define USRP_E_GET_COMPAT_NUMBER _IO(USRP_E_IOC_MAGIC, 0x28) + +#define USRP_E_COMPAT_NUMBER 1 + +/* Flag defines */ +#define RB_USER (1<<0) +#define RB_KERNEL (1<<1) +#define RB_OVERRUN (1<<2) +#define RB_DMA_ACTIVE (1<<3) +#define RB_USER_PROCESS (1<<4) + +struct ring_buffer_info { + int flags; + int len; +}; + +struct usrp_e_ring_buffer_size_t { + int num_pages_rx_flags; + int num_rx_frames; + int num_pages_tx_flags; + int num_tx_frames; +}; + +#endif diff --git a/host/lib/usrp/usrp_e100/io_impl.cpp b/host/lib/usrp/usrp_e100/io_impl.cpp new file mode 100644 index 000000000..7cb3e25e5 --- /dev/null +++ b/host/lib/usrp/usrp_e100/io_impl.cpp @@ -0,0 +1,272 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include "../../transport/vrt_packet_handler.hpp" +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +zero_copy_if::sptr usrp_e100_make_mmap_zero_copy(usrp_e100_iface::sptr iface); + +/*********************************************************************** + * Constants + **********************************************************************/ +static const size_t tx_async_report_sid = 1; +static const int underflow_flags = async_metadata_t::EVENT_CODE_UNDERFLOW | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET; +static const bool recv_debug = false; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew of 1 + * - bounded buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct usrp_e100_impl::io_impl{ + //state management for the vrt packet handler code + vrt_packet_handler::recv_state packet_handler_recv_state; + vrt_packet_handler::send_state packet_handler_send_state; + zero_copy_if::sptr data_xport; + bool continuous_streaming; + io_impl(usrp_e100_iface::sptr iface): + data_xport(usrp_e100_make_mmap_zero_copy(iface)), + recv_pirate_booty(recv_booty_type::make(data_xport->get_num_recv_frames())), + async_msg_fifo(bounded_buffer<async_metadata_t>::make(100/*messages deep*/)) + { + /* NOP */ + } + + ~io_impl(void){ + recv_pirate_crew_raiding = false; + recv_pirate_crew.interrupt_all(); + recv_pirate_crew.join_all(); + } + + bool get_recv_buffs(vrt_packet_handler::managed_recv_buffs_t &buffs, double timeout){ + UHD_ASSERT_THROW(buffs.size() == 1); + boost::this_thread::disable_interruption di; //disable because the wait can throw + return recv_pirate_booty->pop_with_timed_wait(buffs.front(), timeout); + } + + //a pirate's life is the life for me! + void recv_pirate_loop(usrp_e100_clock_ctrl::sptr); + typedef bounded_buffer<managed_recv_buffer::sptr> recv_booty_type; + recv_booty_type::sptr recv_pirate_booty; + bounded_buffer<async_metadata_t>::sptr async_msg_fifo; + boost::thread_group recv_pirate_crew; + bool recv_pirate_crew_raiding; +}; + +/*********************************************************************** + * Receive Pirate Loop + * - while raiding, loot for recv buffers + * - put booty into the alignment buffer + **********************************************************************/ +void usrp_e100_impl::io_impl::recv_pirate_loop(usrp_e100_clock_ctrl::sptr clock_ctrl) +{ + set_thread_priority_safe(); + recv_pirate_crew_raiding = true; + + while(recv_pirate_crew_raiding){ + managed_recv_buffer::sptr buff = this->data_xport->get_recv_buff(); + if (not buff.get()) continue; //ignore timeout/error buffers + + if (recv_debug){ + std::cout << "len " << buff->size() << std::endl; + for (size_t i = 0; i < 9; i++){ + std::cout << boost::format(" 0x%08x") % buff->cast<const boost::uint32_t *>()[i] << std::endl; + } + std::cout << std::endl << std::endl; + } + + try{ + //extract the vrt header packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *vrt_hdr = buff->cast<const boost::uint32_t *>(); + vrt::if_hdr_unpack_le(vrt_hdr, if_packet_info); + + //handle a tx async report message + if (if_packet_info.sid == tx_async_report_sid and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + + //fill in the async metadata + async_metadata_t metadata; + metadata.channel = 0; + metadata.has_time_spec = if_packet_info.has_tsi and if_packet_info.has_tsf; + metadata.time_spec = time_spec_t( + time_t(if_packet_info.tsi), size_t(if_packet_info.tsf), clock_ctrl->get_fpga_clock_rate() + ); + metadata.event_code = vrt_packet_handler::get_context_code<async_metadata_t::event_code_t>(vrt_hdr, if_packet_info); + + //print the famous U, and push the metadata into the message queue + if (metadata.event_code & underflow_flags) std::cerr << "U" << std::flush; + async_msg_fifo->push_with_pop_on_full(metadata); + continue; + } + + //same number of frames as the data transport -> always immediate + recv_pirate_booty->push_with_wait(buff); + + }catch(const std::exception &e){ + std::cerr << "Error (usrp-e recv pirate loop): " << e.what() << std::endl; + } + } +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void usrp_e100_impl::io_init(void){ + //setup otw types + _send_otw_type.width = 16; + _send_otw_type.shift = 0; + _send_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + + _recv_otw_type.width = 16; + _recv_otw_type.shift = 0; + _recv_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + + //setup before the registers (transport called to calculate max spp) + _io_impl = UHD_PIMPL_MAKE(io_impl, (_iface)); + + //setup rx data path + _iface->poke32(UE_REG_CTRL_RX_NSAMPS_PER_PKT, get_max_recv_samps_per_packet()); + _iface->poke32(UE_REG_CTRL_RX_NCHANNELS, 1); + _iface->poke32(UE_REG_CTRL_RX_CLEAR_OVERRUN, 1); //reset + _iface->poke32(UE_REG_CTRL_RX_VRT_HEADER, 0 + | (0x1 << 28) //if data with stream id + | (0x1 << 26) //has trailer + | (0x3 << 22) //integer time other + | (0x1 << 20) //fractional time sample count + ); + _iface->poke32(UE_REG_CTRL_RX_VRT_STREAM_ID, 0); + _iface->poke32(UE_REG_CTRL_RX_VRT_TRAILER, 0); + + //setup the tx policy + _iface->poke32(UE_REG_CTRL_TX_REPORT_SID, tx_async_report_sid); + _iface->poke32(UE_REG_CTRL_TX_POLICY, UE_FLAG_CTRL_TX_POLICY_NEXT_PACKET); + + //spawn a pirate, yarrr! + _io_impl->recv_pirate_crew.create_thread(boost::bind( + &usrp_e100_impl::io_impl::recv_pirate_loop, _io_impl.get(), _clock_ctrl + )); +} + +void usrp_e100_impl::issue_stream_cmd(const stream_cmd_t &stream_cmd){ + _io_impl->continuous_streaming = (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + _iface->poke32(UE_REG_CTRL_RX_STREAM_CMD, dsp_type1::calc_stream_cmd_word( + stream_cmd, get_max_recv_samps_per_packet() + )); + _iface->poke32(UE_REG_CTRL_RX_TIME_SECS, boost::uint32_t(stream_cmd.time_spec.get_full_secs())); + _iface->poke32(UE_REG_CTRL_RX_TIME_TICKS, stream_cmd.time_spec.get_tick_count(_clock_ctrl->get_fpga_clock_rate())); +} + +void usrp_e100_impl::handle_overrun(size_t){ + std::cerr << "O"; //the famous OOOOOOOOOOO + _iface->poke32(UE_REG_CTRL_RX_CLEAR_OVERRUN, 0); + if (_io_impl->continuous_streaming){ + this->issue_stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + } +} + +/*********************************************************************** + * Data Send + **********************************************************************/ +bool get_send_buffs( + zero_copy_if::sptr trans, double timeout, + vrt_packet_handler::managed_send_buffs_t &buffs +){ + UHD_ASSERT_THROW(buffs.size() == 1); + buffs[0] = trans->get_send_buff(timeout); + return buffs[0].get() != NULL; +} + +size_t usrp_e100_impl::get_max_send_samps_per_packet(void) const{ + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + ; + size_t bpp = _io_impl->data_xport->get_send_frame_size() - hdr_size; + return bpp/_send_otw_type.get_sample_size(); +} + +size_t usrp_e100_impl::send( + const std::vector<const void *> &buffs, size_t num_samps, + const tx_metadata_t &metadata, const io_type_t &io_type, + send_mode_t send_mode, double timeout +){ + return vrt_packet_handler::send( + _io_impl->packet_handler_send_state, //last state of the send handler + buffs, num_samps, //buffer to fill + metadata, send_mode, //samples metadata + io_type, _send_otw_type, //input and output types to convert + _clock_ctrl->get_fpga_clock_rate(), //master clock tick rate + uhd::transport::vrt::if_hdr_pack_le, + boost::bind(&get_send_buffs, _io_impl->data_xport, timeout, _1), + get_max_send_samps_per_packet() + ); +} + +/*********************************************************************** + * Data Recv + **********************************************************************/ +size_t usrp_e100_impl::get_max_recv_samps_per_packet(void) const{ + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + ; + size_t bpp = _io_impl->data_xport->get_recv_frame_size() - hdr_size; + return bpp/_recv_otw_type.get_sample_size(); +} + +size_t usrp_e100_impl::recv( + const std::vector<void *> &buffs, size_t num_samps, + rx_metadata_t &metadata, const io_type_t &io_type, + recv_mode_t recv_mode, double timeout +){ + return vrt_packet_handler::recv( + _io_impl->packet_handler_recv_state, //last state of the recv handler + buffs, num_samps, //buffer to fill + metadata, recv_mode, //samples metadata + io_type, _recv_otw_type, //input and output types to convert + _clock_ctrl->get_fpga_clock_rate(), //master clock tick rate + uhd::transport::vrt::if_hdr_unpack_le, + boost::bind(&usrp_e100_impl::io_impl::get_recv_buffs, _io_impl.get(), _1, timeout), + boost::bind(&usrp_e100_impl::handle_overrun, this, _1) + ); +} + +/*********************************************************************** + * Async Recv + **********************************************************************/ +bool usrp_e100_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +){ + boost::this_thread::disable_interruption di; //disable because the wait can throw + return _io_impl->async_msg_fifo->pop_with_timed_wait(async_metadata, timeout); +} diff --git a/host/lib/usrp/usrp_e100/mboard_impl.cpp b/host/lib/usrp/usrp_e100/mboard_impl.cpp new file mode 100644 index 000000000..03c4385aa --- /dev/null +++ b/host/lib/usrp/usrp_e100/mboard_impl.cpp @@ -0,0 +1,198 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <boost/bind.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Mboard Initialization + **********************************************************************/ +void usrp_e100_impl::mboard_init(void){ + _mboard_proxy = wax_obj_proxy::make( + boost::bind(&usrp_e100_impl::mboard_get, this, _1, _2), + boost::bind(&usrp_e100_impl::mboard_set, this, _1, _2) + ); + + //init the clock config + _clock_config.ref_source = clock_config_t::REF_AUTO; + _clock_config.pps_source = clock_config_t::PPS_SMA; + _clock_config.pps_polarity = clock_config_t::PPS_NEG; + + update_clock_config(); +} + +void usrp_e100_impl::update_clock_config(void){ + boost::uint32_t pps_flags = 0; + + //translate pps polarity enums + switch(_clock_config.pps_polarity){ + case clock_config_t::PPS_POS: pps_flags |= UE_FLAG_TIME64_PPS_POSEDGE; break; + case clock_config_t::PPS_NEG: pps_flags |= UE_FLAG_TIME64_PPS_NEGEDGE; break; + default: throw std::runtime_error("unhandled clock configuration pps polarity"); + } + + //set the pps flags + _iface->poke32(UE_REG_TIME64_FLAGS, pps_flags); + + //clock source ref 10mhz + switch(_clock_config.ref_source){ + case clock_config_t::REF_AUTO: _clock_ctrl->use_auto_ref(); + case clock_config_t::REF_INT: _clock_ctrl->use_internal_ref(); + case clock_config_t::REF_SMA: _clock_ctrl->use_external_ref(); + default: throw std::runtime_error("unhandled clock configuration ref source"); + } +} + +/*********************************************************************** + * Mboard Get + **********************************************************************/ +void usrp_e100_impl::mboard_get(const wax::obj &key_, wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<mboard_prop_t>()){ + case MBOARD_PROP_NAME: + val = std::string("usrp-e mboard"); + return; + + case MBOARD_PROP_OTHERS: + val = prop_names_t(); + return; + + case MBOARD_PROP_RX_DBOARD: + UHD_ASSERT_THROW(key.name == ""); + val = _rx_dboard_proxy->get_link(); + return; + + case MBOARD_PROP_RX_DBOARD_NAMES: + val = prop_names_t(1, ""); //vector of size 1 with empty string + return; + + case MBOARD_PROP_TX_DBOARD: + UHD_ASSERT_THROW(key.name == ""); + val = _tx_dboard_proxy->get_link(); + return; + + case MBOARD_PROP_TX_DBOARD_NAMES: + val = prop_names_t(1, ""); //vector of size 1 with empty string + return; + + case MBOARD_PROP_RX_DSP: + UHD_ASSERT_THROW(key.name == ""); + val = _rx_ddc_proxy->get_link(); + return; + + case MBOARD_PROP_RX_DSP_NAMES: + val = prop_names_t(1, ""); + return; + + case MBOARD_PROP_TX_DSP: + UHD_ASSERT_THROW(key.name == ""); + val = _tx_duc_proxy->get_link(); + return; + + case MBOARD_PROP_TX_DSP_NAMES: + val = prop_names_t(1, ""); + return; + + case MBOARD_PROP_CLOCK_CONFIG: + val = _clock_config; + return; + + case MBOARD_PROP_RX_SUBDEV_SPEC: + val = _rx_subdev_spec; + return; + + case MBOARD_PROP_TX_SUBDEV_SPEC: + val = _tx_subdev_spec; + return; + + case MBOARD_PROP_EEPROM_MAP: + val = _iface->mb_eeprom; + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * Mboard Set + **********************************************************************/ +void usrp_e100_impl::mboard_set(const wax::obj &key, const wax::obj &val){ + //handle the get request conditioned on the key + switch(key.as<mboard_prop_t>()){ + + case MBOARD_PROP_STREAM_CMD: + issue_stream_cmd(val.as<stream_cmd_t>()); + return; + + case MBOARD_PROP_TIME_NOW: + case MBOARD_PROP_TIME_NEXT_PPS:{ + time_spec_t time_spec = val.as<time_spec_t>(); + _iface->poke32(UE_REG_TIME64_TICKS, time_spec.get_tick_count(_clock_ctrl->get_fpga_clock_rate())); + boost::uint32_t imm_flags = (key.as<mboard_prop_t>() == MBOARD_PROP_TIME_NOW)? 1 : 0; + _iface->poke32(UE_REG_TIME64_IMM, imm_flags); + _iface->poke32(UE_REG_TIME64_SECS, time_spec.get_full_secs()); + } + return; + + case MBOARD_PROP_RX_SUBDEV_SPEC: + _rx_subdev_spec = val.as<subdev_spec_t>(); + verify_rx_subdev_spec(_rx_subdev_spec, _mboard_proxy->get_link()); + //sanity check + UHD_ASSERT_THROW(_rx_subdev_spec.size() == 1); + //set the mux + _iface->poke32(UE_REG_DSP_RX_MUX, dsp_type1::calc_rx_mux_word( + _dboard_manager->get_rx_subdev(_rx_subdev_spec.front().sd_name)[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>() + )); + return; + + case MBOARD_PROP_TX_SUBDEV_SPEC: + _tx_subdev_spec = val.as<subdev_spec_t>(); + verify_tx_subdev_spec(_tx_subdev_spec, _mboard_proxy->get_link()); + //sanity check + UHD_ASSERT_THROW(_tx_subdev_spec.size() == 1); + //set the mux + _iface->poke32(UE_REG_DSP_TX_MUX, dsp_type1::calc_tx_mux_word( + _dboard_manager->get_tx_subdev(_tx_subdev_spec.front().sd_name)[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>() + )); + return; + + case MBOARD_PROP_EEPROM_MAP: + // Step1: commit the map, writing only those values set. + // Step2: readback the entire eeprom map into the iface. + val.as<mboard_eeprom_t>().commit(_iface->get_i2c_dev_iface(), mboard_eeprom_t::MAP_E100); + _iface->mb_eeprom = mboard_eeprom_t(_iface->get_i2c_dev_iface(), mboard_eeprom_t::MAP_E100); + return; + + case MBOARD_PROP_CLOCK_CONFIG: + _clock_config = val.as<clock_config_t>(); + update_clock_config(); + return; + + default: UHD_THROW_PROP_SET_ERROR(); + } +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_iface.cpp b/host/lib/usrp/usrp_e100/usrp_e100_iface.cpp new file mode 100644 index 000000000..40c7afabb --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_iface.cpp @@ -0,0 +1,268 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_iface.hpp" +#include <uhd/utils/assert.hpp> +#include <sys/ioctl.h> //ioctl +#include <fcntl.h> //open, close +#include <linux/usrp_e.h> //ioctl structures and constants +#include <boost/format.hpp> +#include <boost/thread.hpp> //mutex +#include <linux/i2c-dev.h> +#include <linux/i2c.h> +#include <stdexcept> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * I2C device node implementation wrapper + **********************************************************************/ +class i2c_dev_iface : public i2c_iface{ +public: + i2c_dev_iface(const std::string &node){ + if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ + throw std::runtime_error("Failed to open " + node); + } + } + + ~i2c_dev_iface(void){ + ::close(_node_fd); + } + + void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ + byte_vector_t rw_bytes(bytes); + + //setup the message + i2c_msg msg; + msg.addr = addr; + msg.flags = 0; + msg.len = bytes.size(); + msg.buf = &rw_bytes.front(); + + //setup the data + i2c_rdwr_ioctl_data data; + data.msgs = &msg; + data.nmsgs = 1; + + //call the ioctl + UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); + } + + byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ + byte_vector_t bytes(num_bytes); + + //setup the message + i2c_msg msg; + msg.addr = addr; + msg.flags = I2C_M_RD; + msg.len = bytes.size(); + msg.buf = &bytes.front(); + + //setup the data + i2c_rdwr_ioctl_data data; + data.msgs = &msg; + data.nmsgs = 1; + + //call the ioctl + UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); + + return bytes; + } + +private: int _node_fd; +}; + +/*********************************************************************** + * USRP-E100 interface implementation + **********************************************************************/ +class usrp_e100_iface_impl : public usrp_e100_iface{ +public: + + int get_file_descriptor(void){ + return _node_fd; + } + + /******************************************************************* + * Structors + ******************************************************************/ + usrp_e100_iface_impl(const std::string &node): + _i2c_dev_iface(i2c_dev_iface("/dev/i2c-3")) + { + //open the device node and check file descriptor + if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ + throw std::runtime_error("Failed to open " + node); + } + + mb_eeprom = mboard_eeprom_t(get_i2c_dev_iface(), mboard_eeprom_t::MAP_E100); + } + + ~usrp_e100_iface_impl(void){ + //close the device node file descriptor + ::close(_node_fd); + } + + /******************************************************************* + * IOCTL: provides the communication base for all other calls + ******************************************************************/ + void ioctl(int request, void *mem){ + boost::mutex::scoped_lock lock(_ctrl_mutex); + + if (::ioctl(_node_fd, request, mem) < 0){ + throw std::runtime_error(str( + boost::format("ioctl failed with request %d") % request + )); + } + } + + /******************************************************************* + * I2C device node interface + ******************************************************************/ + i2c_iface &get_i2c_dev_iface(void){ + return _i2c_dev_iface; + } + + /******************************************************************* + * Peek and Poke + ******************************************************************/ + void poke32(boost::uint32_t addr, boost::uint32_t value){ + //load the data struct + usrp_e_ctl32 data; + data.offset = addr; + data.count = 1; + data.buf[0] = value; + + //call the ioctl + this->ioctl(USRP_E_WRITE_CTL32, &data); + } + + void poke16(boost::uint32_t addr, boost::uint16_t value){ + //load the data struct + usrp_e_ctl16 data; + data.offset = addr; + data.count = 1; + data.buf[0] = value; + + //call the ioctl + this->ioctl(USRP_E_WRITE_CTL16, &data); + } + + boost::uint32_t peek32(boost::uint32_t addr){ + //load the data struct + usrp_e_ctl32 data; + data.offset = addr; + data.count = 1; + + //call the ioctl + this->ioctl(USRP_E_READ_CTL32, &data); + + return data.buf[0]; + } + + boost::uint16_t peek16(boost::uint32_t addr){ + //load the data struct + usrp_e_ctl16 data; + data.offset = addr; + data.count = 1; + + //call the ioctl + this->ioctl(USRP_E_READ_CTL16, &data); + + return data.buf[0]; + } + + /******************************************************************* + * I2C + ******************************************************************/ + static const size_t max_i2c_data_bytes = 10; + + void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ + //allocate some memory for this transaction + UHD_ASSERT_THROW(bytes.size() <= max_i2c_data_bytes); + boost::uint8_t mem[sizeof(usrp_e_i2c) + max_i2c_data_bytes]; + + //load the data struct + usrp_e_i2c *data = reinterpret_cast<usrp_e_i2c*>(mem); + data->addr = addr; + data->len = bytes.size(); + std::copy(bytes.begin(), bytes.end(), data->data); + + //call the spi ioctl + this->ioctl(USRP_E_I2C_WRITE, data); + } + + byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ + //allocate some memory for this transaction + UHD_ASSERT_THROW(num_bytes <= max_i2c_data_bytes); + boost::uint8_t mem[sizeof(usrp_e_i2c) + max_i2c_data_bytes]; + + //load the data struct + usrp_e_i2c *data = reinterpret_cast<usrp_e_i2c*>(mem); + data->addr = addr; + data->len = num_bytes; + + //call the spi ioctl + this->ioctl(USRP_E_I2C_READ, data); + + //unload the data + byte_vector_t bytes(data->len); + UHD_ASSERT_THROW(bytes.size() == num_bytes); + std::copy(data->data, data->data+bytes.size(), bytes.begin()); + return bytes; + } + + /******************************************************************* + * SPI + ******************************************************************/ + boost::uint32_t transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t bits, + size_t num_bits, + bool readback + ){ + //load data struct + usrp_e_spi data; + data.readback = (readback)? UE_SPI_TXRX : UE_SPI_TXONLY; + data.slave = which_slave; + data.length = num_bits; + data.data = bits; + + //load the flags + data.flags = 0; + data.flags |= (config.miso_edge == spi_config_t::EDGE_RISE)? UE_SPI_LATCH_RISE : UE_SPI_LATCH_FALL; + data.flags |= (config.mosi_edge == spi_config_t::EDGE_RISE)? UE_SPI_PUSH_FALL : UE_SPI_PUSH_RISE; + + //call the spi ioctl + this->ioctl(USRP_E_SPI, &data); + + //unload the data + return data.data; + } + +private: + int _node_fd; + i2c_dev_iface _i2c_dev_iface; + boost::mutex _ctrl_mutex; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +usrp_e100_iface::sptr usrp_e100_iface::make(const std::string &node){ + return sptr(new usrp_e100_iface_impl(node)); +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_iface.hpp b/host/lib/usrp/usrp_e100/usrp_e100_iface.hpp new file mode 100644 index 000000000..12283fb52 --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_iface.hpp @@ -0,0 +1,119 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_IFACE_HPP +#define INCLUDED_USRP_E100_IFACE_HPP + +#include <uhd/transport/udp_simple.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/cstdint.hpp> + +//////////////////////////////////////////////////////////////////////// +// I2C addresses +//////////////////////////////////////////////////////////////////////// +#define I2C_DEV_EEPROM 0x50 // 24LC02[45]: 7-bits 1010xxx +#define I2C_ADDR_MBOARD (I2C_DEV_EEPROM | 0x0) +#define I2C_ADDR_TX_DB (I2C_DEV_EEPROM | 0x4) +#define I2C_ADDR_RX_DB (I2C_DEV_EEPROM | 0x5) +//////////////////////////////////////////////////////////////////////// + +/*! + * The usrp-e interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp_e100_iface : boost::noncopyable, public uhd::i2c_iface{ +public: + typedef boost::shared_ptr<usrp_e100_iface> sptr; + + /*! + * Make a new usrp-e interface with the control transport. + * \param node the device node name + * \return a new usrp-e interface object + */ + static sptr make(const std::string &node); + + /*! + * Get the underlying file descriptor. + * \return the file descriptor + */ + virtual int get_file_descriptor(void) = 0; + + /*! + * Perform an ioctl call on the device node file descriptor. + * This will throw when the internal ioctl call fails. + * \param request the control word + * \param mem pointer to some memory + */ + virtual void ioctl(int request, void *mem) = 0; + + //! Get the I2C interface for the I2C device node + virtual uhd::i2c_iface &get_i2c_dev_iface(void) = 0; + + /*! + * Write a register (32 bits) + * \param addr the address + * \param data the 32bit data + */ + virtual void poke32(boost::uint32_t addr, boost::uint32_t data) = 0; + + /*! + * Read a register (32 bits) + * \param addr the address + * \return the 32bit data + */ + virtual boost::uint32_t peek32(boost::uint32_t addr) = 0; + + /*! + * Write a register (16 bits) + * \param addr the address + * \param data the 16bit data + */ + virtual void poke16(boost::uint32_t addr, boost::uint16_t data) = 0; + + /*! + * Read a register (16 bits) + * \param addr the address + * \return the 16bit data + */ + virtual boost::uint16_t peek16(boost::uint32_t addr) = 0; + + /*! + * Perform an spi transaction. + * \param which_slave the slave device number + * \param config spi config args + * \param data the bits to write + * \param num_bits how many bits in data + * \param readback true to readback a value + * \return spi data if readback set + */ + virtual boost::uint32_t transact_spi( + int which_slave, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback + ) = 0; + + //motherboard eeprom map structure + uhd::usrp::mboard_eeprom_t mb_eeprom; +}; + +#endif /* INCLUDED_USRP_E100_IFACE_HPP */ diff --git a/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp b/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp new file mode 100644 index 000000000..40ea56466 --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp @@ -0,0 +1,212 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_impl.hpp" +#include "usrp_e100_regs.hpp" +#include <uhd/usrp/device_props.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/warning.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/functional/hash.hpp> +#include <iostream> +#include <fstream> + +using namespace uhd; +using namespace uhd::usrp; +namespace fs = boost::filesystem; + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t usrp_e100_find(const device_addr_t &hint){ + device_addrs_t usrp_e100_addrs; + + //return an empty list of addresses when type is set to non-usrp-e + if (hint.has_key("type") and hint["type"] != "usrp-e") return usrp_e100_addrs; + + //device node not provided, assume its 0 + if (not hint.has_key("node")){ + device_addr_t new_addr = hint; + new_addr["node"] = "/dev/usrp_e0"; + return usrp_e100_find(new_addr); + } + + //use the given device node name + if (fs::exists(hint["node"])){ + device_addr_t new_addr; + new_addr["type"] = "usrp-e"; + new_addr["node"] = fs::system_complete(fs::path(hint["node"])).file_string(); + try{ + usrp_e100_iface::sptr iface = usrp_e100_iface::make(new_addr["node"]); + new_addr["name"] = iface->mb_eeprom["name"]; + new_addr["serial"] = iface->mb_eeprom["serial"]; + if ( + (not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) + ){ + usrp_e100_addrs.push_back(new_addr); + } + } + catch(const std::exception &e){ + uhd::warning::post( + std::string("Ignoring discovered device\n") + + e.what() + ); + } + } + + return usrp_e100_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp_e100_make(const device_addr_t &device_addr){ + + //setup the main interface into fpga + std::string node = device_addr["node"]; + std::cout << boost::format("Opening USRP-E on %s") % node << std::endl; + usrp_e100_iface::sptr iface = usrp_e100_iface::make(node); + + //------------------------------------------------------------------ + //-- Handle the FPGA loading... + //-- The image can be confimed as already loaded when: + //-- 1) The compatibility number matches. + //-- 2) The hash in the hash-file matches. + //------------------------------------------------------------------ + static const char *hash_file_path = "/tmp/usrp_e100_hash"; + + //extract the fpga path for usrp-e + std::string usrp_e100_fpga_image = find_image_path( + device_addr.has_key("fpga")? device_addr["fpga"] : "usrp_e100_fpga.bin" + ); + + //calculate a hash of the fpga file + size_t fpga_hash = 0; + { + std::ifstream file(usrp_e100_fpga_image.c_str()); + if (not file.good()) throw std::runtime_error( + "cannot open fpga file for read: " + usrp_e100_fpga_image + ); + do{ + boost::hash_combine(fpga_hash, file.get()); + } while (file.good()); + file.close(); + } + + //read the compatibility number + boost::uint16_t fpga_compat_num = iface->peek16(UE_REG_MISC_COMPAT); + + //read the hash in the hash-file + size_t loaded_hash = 0; + try{std::ifstream(hash_file_path) >> loaded_hash;}catch(...){} + + //if not loaded: load the fpga image and write the hash-file + if (fpga_compat_num != USRP_E_COMPAT_NUM or loaded_hash != fpga_hash){ + iface.reset(); + usrp_e100_load_fpga(usrp_e100_fpga_image); + sleep(1); ///\todo do this better one day. + std::cout << boost::format("re-Opening USRP-E on %s") % node << std::endl; + iface = usrp_e100_iface::make(node); + try{std::ofstream(hash_file_path) << fpga_hash;}catch(...){} + } + + //check that the compatibility is correct + fpga_compat_num = iface->peek16(UE_REG_MISC_COMPAT); + if (fpga_compat_num != USRP_E_COMPAT_NUM){ + throw std::runtime_error(str(boost::format( + "Expected fpga compatibility number 0x%x, but got 0x%x:\n" + "The fpga build is not compatible with the host code build." + ) % USRP_E_COMPAT_NUM % fpga_compat_num)); + } + + return device::sptr(new usrp_e100_impl(iface)); +} + +UHD_STATIC_BLOCK(register_usrp_e100_device){ + device::register_device(&usrp_e100_find, &usrp_e100_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp_e100_impl::usrp_e100_impl(usrp_e100_iface::sptr iface): _iface(iface){ + + //setup interfaces into hardware + _clock_ctrl = usrp_e100_clock_ctrl::make(_iface); + _codec_ctrl = usrp_e100_codec_ctrl::make(_iface); + + //initialize the mboard + mboard_init(); + + //initialize the dboards + dboard_init(); + + //initialize the dsps + rx_ddc_init(); + tx_duc_init(); + + //init the codec properties + codec_init(); + + //init the io send/recv + io_init(); + + //set default subdev specs + this->mboard_set(MBOARD_PROP_RX_SUBDEV_SPEC, subdev_spec_t()); + this->mboard_set(MBOARD_PROP_TX_SUBDEV_SPEC, subdev_spec_t()); +} + +usrp_e100_impl::~usrp_e100_impl(void){ + /* NOP */ +} + +/*********************************************************************** + * Device Get + **********************************************************************/ +void usrp_e100_impl::get(const wax::obj &key_, wax::obj &val){ + named_prop_t key = named_prop_t::extract(key_); + + //handle the get request conditioned on the key + switch(key.as<device_prop_t>()){ + case DEVICE_PROP_NAME: + val = std::string("usrp-e device"); + return; + + case DEVICE_PROP_MBOARD: + UHD_ASSERT_THROW(key.name == ""); + val = _mboard_proxy->get_link(); + return; + + case DEVICE_PROP_MBOARD_NAMES: + val = prop_names_t(1, ""); //vector of size 1 with empty string + return; + + default: UHD_THROW_PROP_GET_ERROR(); + } +} + +/*********************************************************************** + * Device Set + **********************************************************************/ +void usrp_e100_impl::set(const wax::obj &, const wax::obj &){ + UHD_THROW_PROP_SET_ERROR(); +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_impl.hpp b/host/lib/usrp/usrp_e100/usrp_e100_impl.hpp new file mode 100644 index 000000000..de158ea5e --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_impl.hpp @@ -0,0 +1,167 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_iface.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/device.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_manager.hpp> + +#ifndef INCLUDED_USRP_E100_IMPL_HPP +#define INCLUDED_USRP_E100_IMPL_HPP + +static const boost::uint16_t USRP_E_COMPAT_NUM = 0x02; + +//! load an fpga image from a bin file into the usrp-e fpga +extern void usrp_e100_load_fpga(const std::string &bin_file); + +/*! + * Make a usrp-e dboard interface. + * \param iface the usrp-e interface object + * \param clock the clock control interface + * \param codec the codec control interface + * \return a sptr to a new dboard interface + */ +uhd::usrp::dboard_iface::sptr make_usrp_e100_dboard_iface( + usrp_e100_iface::sptr iface, + usrp_e100_clock_ctrl::sptr clock, + usrp_e100_codec_ctrl::sptr codec +); + +/*! + * Simple wax obj proxy class: + * Provides a wax obj interface for a set and a get function. + * This allows us to create nested properties structures + * while maintaining flattened code within the implementation. + */ +class wax_obj_proxy : public wax::obj{ +public: + typedef boost::function<void(const wax::obj &, wax::obj &)> get_t; + typedef boost::function<void(const wax::obj &, const wax::obj &)> set_t; + typedef boost::shared_ptr<wax_obj_proxy> sptr; + + static sptr make(const get_t &get, const set_t &set){ + return sptr(new wax_obj_proxy(get, set)); + } + +private: + get_t _get; set_t _set; + wax_obj_proxy(const get_t &get, const set_t &set): _get(get), _set(set){}; + void get(const wax::obj &key, wax::obj &val){return _get(key, val);} + void set(const wax::obj &key, const wax::obj &val){return _set(key, val);} +}; + +/*! + * USRP-E100 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class usrp_e100_impl : public uhd::device{ +public: + //structors + usrp_e100_impl(usrp_e100_iface::sptr); + ~usrp_e100_impl(void); + + //the io interface + size_t send(const std::vector<const void *> &, size_t, const uhd::tx_metadata_t &, const uhd::io_type_t &, send_mode_t, double); + size_t recv(const std::vector<void *> &, size_t, uhd::rx_metadata_t &, const uhd::io_type_t &, recv_mode_t, double); + bool recv_async_msg(uhd::async_metadata_t &, double); + size_t get_max_send_samps_per_packet(void) const; + size_t get_max_recv_samps_per_packet(void) const; + +private: + //interface to ioctls and file descriptor + usrp_e100_iface::sptr _iface; + + //handle io stuff + UHD_PIMPL_DECL(io_impl) _io_impl; + uhd::otw_type_t _send_otw_type, _recv_otw_type; + void io_init(void); + void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd); + void handle_overrun(size_t); + + //configuration shadows + uhd::clock_config_t _clock_config; + + //ad9522 clock control + usrp_e100_clock_ctrl::sptr _clock_ctrl; + + //ad9862 codec control + usrp_e100_codec_ctrl::sptr _codec_ctrl; + + //device functions and settings + void get(const wax::obj &, wax::obj &); + void set(const wax::obj &, const wax::obj &); + + //mboard functions and settings + void mboard_init(void); + void mboard_get(const wax::obj &, wax::obj &); + void mboard_set(const wax::obj &, const wax::obj &); + wax_obj_proxy::sptr _mboard_proxy; + uhd::usrp::subdev_spec_t _rx_subdev_spec, _tx_subdev_spec; + + //xx dboard functions and settings + void dboard_init(void); + uhd::usrp::dboard_manager::sptr _dboard_manager; + uhd::usrp::dboard_iface::sptr _dboard_iface; + + //rx dboard functions and settings + uhd::usrp::dboard_eeprom_t _rx_db_eeprom; + void rx_dboard_get(const wax::obj &, wax::obj &); + void rx_dboard_set(const wax::obj &, const wax::obj &); + wax_obj_proxy::sptr _rx_dboard_proxy; + + //tx dboard functions and settings + uhd::usrp::dboard_eeprom_t _tx_db_eeprom; + void tx_dboard_get(const wax::obj &, wax::obj &); + void tx_dboard_set(const wax::obj &, const wax::obj &); + wax_obj_proxy::sptr _tx_dboard_proxy; + + //rx ddc functions and settings + void rx_ddc_init(void); + void rx_ddc_get(const wax::obj &, wax::obj &); + void rx_ddc_set(const wax::obj &, const wax::obj &); + double _ddc_freq; size_t _ddc_decim; + wax_obj_proxy::sptr _rx_ddc_proxy; + + //tx duc functions and settings + void tx_duc_init(void); + void tx_duc_get(const wax::obj &, wax::obj &); + void tx_duc_set(const wax::obj &, const wax::obj &); + double _duc_freq; size_t _duc_interp; + wax_obj_proxy::sptr _tx_duc_proxy; + + //codec functions and settings + void codec_init(void); + void rx_codec_get(const wax::obj &, wax::obj &); + void rx_codec_set(const wax::obj &, const wax::obj &); + void tx_codec_get(const wax::obj &, wax::obj &); + void tx_codec_set(const wax::obj &, const wax::obj &); + wax_obj_proxy::sptr _rx_codec_proxy, _tx_codec_proxy; + + //clock control functions and settings + void init_clock_config(void); + void update_clock_config(void); +}; + +#endif /* INCLUDED_USRP_E100_IMPL_HPP */ diff --git a/host/lib/usrp/usrp_e100/usrp_e100_mmap_zero_copy.cpp b/host/lib/usrp/usrp_e100/usrp_e100_mmap_zero_copy.cpp new file mode 100644 index 000000000..bf378a9b1 --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_mmap_zero_copy.cpp @@ -0,0 +1,215 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_e100_iface.hpp" +#include <uhd/transport/zero_copy.hpp> +#include <uhd/utils/assert.hpp> +#include <linux/usrp_e.h> +#include <sys/mman.h> //mmap +#include <unistd.h> //getpagesize +#include <poll.h> //poll +#include <boost/bind.hpp> +#include <boost/enable_shared_from_this.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::transport; + +static const bool fp_verbose = false; //fast-path verbose +static const bool sp_verbose = false; //slow-path verbose +static const size_t poll_breakout = 10; //how many poll timeouts constitute a full timeout + +/*********************************************************************** + * The zero copy interface implementation + **********************************************************************/ +class usrp_e100_mmap_zero_copy_impl : public zero_copy_if, public boost::enable_shared_from_this<usrp_e100_mmap_zero_copy_impl> { +public: + usrp_e100_mmap_zero_copy_impl(usrp_e100_iface::sptr iface): + _fd(iface->get_file_descriptor()), _recv_index(0), _send_index(0) + { + //get system sizes + iface->ioctl(USRP_E_GET_RB_INFO, &_rb_size); + size_t page_size = getpagesize(); + _frame_size = page_size/2; + + //calculate the memory size + _map_size = + (_rb_size.num_pages_rx_flags + _rb_size.num_pages_tx_flags) * page_size + + (_rb_size.num_rx_frames + _rb_size.num_tx_frames) * _frame_size; + + //print sizes summary + if (sp_verbose){ + std::cout << "page_size: " << page_size << std::endl; + std::cout << "frame_size: " << _frame_size << std::endl; + std::cout << "num_pages_rx_flags: " << _rb_size.num_pages_rx_flags << std::endl; + std::cout << "num_rx_frames: " << _rb_size.num_rx_frames << std::endl; + std::cout << "num_pages_tx_flags: " << _rb_size.num_pages_tx_flags << std::endl; + std::cout << "num_tx_frames: " << _rb_size.num_tx_frames << std::endl; + std::cout << "map_size: " << _map_size << std::endl; + } + + //call mmap to get the memory + _mapped_mem = ::mmap( + NULL, _map_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0 + ); + UHD_ASSERT_THROW(_mapped_mem != MAP_FAILED); + + //calculate the memory offsets for info and buffers + size_t recv_info_off = 0; + size_t recv_buff_off = recv_info_off + (_rb_size.num_pages_rx_flags * page_size); + size_t send_info_off = recv_buff_off + (_rb_size.num_rx_frames * _frame_size); + size_t send_buff_off = send_info_off + (_rb_size.num_pages_tx_flags * page_size); + + //print offset summary + if (sp_verbose){ + std::cout << "recv_info_off: " << recv_info_off << std::endl; + std::cout << "recv_buff_off: " << recv_buff_off << std::endl; + std::cout << "send_info_off: " << send_info_off << std::endl; + std::cout << "send_buff_off: " << send_buff_off << std::endl; + } + + //set the internal pointers for info and buffers + typedef ring_buffer_info (*rbi_pta)[]; + char *rb_ptr = reinterpret_cast<char *>(_mapped_mem); + _recv_info = reinterpret_cast<rbi_pta>(rb_ptr + recv_info_off); + _recv_buff = rb_ptr + recv_buff_off; + _send_info = reinterpret_cast<rbi_pta>(rb_ptr + send_info_off); + _send_buff = rb_ptr + send_buff_off; + } + + ~usrp_e100_mmap_zero_copy_impl(void){ + if (sp_verbose) std::cout << "cleanup: munmap" << std::endl; + ::munmap(_mapped_mem, _map_size); + } + + managed_recv_buffer::sptr get_recv_buff(double timeout){ + if (fp_verbose) std::cout << "get_recv_buff: " << _recv_index << std::endl; + + //grab pointers to the info and buffer + ring_buffer_info *info = (*_recv_info) + _recv_index; + void *mem = _recv_buff + _frame_size*_recv_index; + + //poll/wait for a ready frame + if (not (info->flags & RB_USER)){ + for (size_t i = 0; i < poll_breakout; i++){ + pollfd pfd; + pfd.fd = _fd; + pfd.events = POLLIN; + ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3/poll_breakout)); + if (fp_verbose) std::cout << " POLLIN: " << poll_ret << std::endl; + if (poll_ret > 0) goto found_user_frame; //good poll, continue on + } + return managed_recv_buffer::sptr(); //timed-out for real + } found_user_frame: + + //the process has claimed the frame + info->flags = RB_USER_PROCESS; + + //increment the index for the next call + if (++_recv_index == size_t(_rb_size.num_rx_frames)) _recv_index = 0; + + //return the managed buffer for this frame + if (fp_verbose) std::cout << " make_recv_buff: " << info->len << std::endl; + return managed_recv_buffer::make_safe( + boost::asio::const_buffer(mem, info->len), + boost::bind(&usrp_e100_mmap_zero_copy_impl::release, shared_from_this(), info) + ); + } + + size_t get_num_recv_frames(void) const{ + return _rb_size.num_rx_frames; + } + + size_t get_recv_frame_size(void) const{ + return _frame_size; + } + + managed_send_buffer::sptr get_send_buff(double timeout){ + if (fp_verbose) std::cout << "get_send_buff: " << _send_index << std::endl; + + //grab pointers to the info and buffer + ring_buffer_info *info = (*_send_info) + _send_index; + void *mem = _send_buff + _frame_size*_send_index; + + //poll/wait for a ready frame + if (not (info->flags & RB_KERNEL)){ + pollfd pfd; + pfd.fd = _fd; + pfd.events = POLLOUT; + ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3)); + if (fp_verbose) std::cout << " POLLOUT: " << poll_ret << std::endl; + if (poll_ret <= 0) return managed_send_buffer::sptr(); + } + + //increment the index for the next call + if (++_send_index == size_t(_rb_size.num_tx_frames)) _send_index = 0; + + //return the managed buffer for this frame + if (fp_verbose) std::cout << " make_send_buff: " << _frame_size << std::endl; + return managed_send_buffer::make_safe( + boost::asio::mutable_buffer(mem, _frame_size), + boost::bind(&usrp_e100_mmap_zero_copy_impl::commit, shared_from_this(), info, _1) + ); + } + + size_t get_num_send_frames(void) const{ + return _rb_size.num_tx_frames; + } + + size_t get_send_frame_size(void) const{ + return _frame_size; + } + +private: + + void release(ring_buffer_info *info){ + if (fp_verbose) std::cout << "recv buff: release" << std::endl; + info->flags = RB_KERNEL; + } + + void commit(ring_buffer_info *info, size_t len){ + if (fp_verbose) std::cout << "send buff: commit " << len << std::endl; + info->len = len; + info->flags = RB_USER; + if (::write(_fd, NULL, 0) < 0){ + std::cerr << UHD_THROW_SITE_INFO("write error") << std::endl; + } + } + + int _fd; + + //the mapped memory itself + void *_mapped_mem; + + //mapped memory sizes + usrp_e_ring_buffer_size_t _rb_size; + size_t _frame_size, _map_size; + + //pointers to sections in the mapped memory + ring_buffer_info (*_recv_info)[], (*_send_info)[]; + char *_recv_buff, *_send_buff; + + //indexes into sub-sections of mapped memory + size_t _recv_index, _send_index; +}; + +/*********************************************************************** + * The zero copy interface make function + **********************************************************************/ +zero_copy_if::sptr usrp_e100_make_mmap_zero_copy(usrp_e100_iface::sptr iface){ + return zero_copy_if::sptr(new usrp_e100_mmap_zero_copy_impl(iface)); +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_regs.hpp b/host/lib/usrp/usrp_e100/usrp_e100_regs.hpp new file mode 100644 index 000000000..625fb2c35 --- /dev/null +++ b/host/lib/usrp/usrp_e100/usrp_e100_regs.hpp @@ -0,0 +1,198 @@ + + +//////////////////////////////////////////////////////////////// +// +// Memory map for embedded wishbone bus +// +//////////////////////////////////////////////////////////////// + +// All addresses are byte addresses. All accesses are word (16-bit) accesses. +// This means that address bit 0 is usually 0. +// There are 11 bits of address for the control. + +#ifndef INCLUDED_USRP_E100_REGS_HPP +#define INCLUDED_USRP_E100_REGS_HPP + +///////////////////////////////////////////////////// +// Slave pointers + +#define UE_REG_SLAVE(n) ((n)<<7) +#define UE_REG_SR_ADDR(n) ((UE_REG_SLAVE(5)) + (4*(n))) + +///////////////////////////////////////////////////// +// Slave 0 -- Misc Regs + +#define UE_REG_MISC_BASE UE_REG_SLAVE(0) + +#define UE_REG_MISC_LED UE_REG_MISC_BASE + 0 +#define UE_REG_MISC_SW UE_REG_MISC_BASE + 2 +#define UE_REG_MISC_CGEN_CTRL UE_REG_MISC_BASE + 4 +#define UE_REG_MISC_CGEN_ST UE_REG_MISC_BASE + 6 +#define UE_REG_MISC_TEST UE_REG_MISC_BASE + 8 +#define UE_REG_MISC_RX_LEN UE_REG_MISC_BASE + 10 +#define UE_REG_MISC_TX_LEN UE_REG_MISC_BASE + 12 +#define UE_REG_MISC_XFER_RATE UE_REG_MISC_BASE + 14 +#define UE_REG_MISC_COMPAT UE_REG_MISC_BASE + 16 + +///////////////////////////////////////////////////// +// Slave 1 -- UART +// CLKDIV is 16 bits, others are only 8 + +#define UE_REG_UART_BASE UE_REG_SLAVE(1) + +#define UE_REG_UART_CLKDIV UE_REG_UART_BASE + 0 +#define UE_REG_UART_TXLEVEL UE_REG_UART_BASE + 2 +#define UE_REG_UART_RXLEVEL UE_REG_UART_BASE + 4 +#define UE_REG_UART_TXCHAR UE_REG_UART_BASE + 6 +#define UE_REG_UART_RXCHAR UE_REG_UART_BASE + 8 + +///////////////////////////////////////////////////// +// Slave 2 -- SPI Core +// This should be accessed through the IOCTL +// Users should not touch directly + +#define UE_REG_SPI_BASE UE_REG_SLAVE(2) + +//spi slave constants +#define UE_SPI_SS_AD9522 (1 << 3) +#define UE_SPI_SS_AD9862 (1 << 2) +#define UE_SPI_SS_TX_DB (1 << 1) +#define UE_SPI_SS_RX_DB (1 << 0) + +//////////////////////////////////////////////// +// Slave 3 -- I2C Core +// This should be accessed through the IOCTL +// Users should not touch directly + +#define UE_REG_I2C_BASE UE_REG_SLAVE(3) + + +//////////////////////////////////////////////// +// Slave 4 -- GPIO + +#define UE_REG_GPIO_BASE UE_REG_SLAVE(4) + +#define UE_REG_GPIO_RX_IO UE_REG_GPIO_BASE + 0 +#define UE_REG_GPIO_TX_IO UE_REG_GPIO_BASE + 2 +#define UE_REG_GPIO_RX_DDR UE_REG_GPIO_BASE + 4 +#define UE_REG_GPIO_TX_DDR UE_REG_GPIO_BASE + 6 +#define UE_REG_GPIO_RX_SEL UE_REG_GPIO_BASE + 8 +#define UE_REG_GPIO_TX_SEL UE_REG_GPIO_BASE + 10 +#define UE_REG_GPIO_RX_DBG UE_REG_GPIO_BASE + 12 +#define UE_REG_GPIO_TX_DBG UE_REG_GPIO_BASE + 14 + +//possible bit values for sel when dbg is 0: +#define GPIO_SEL_SW 0 // if pin is an output, set by software in the io reg +#define GPIO_SEL_ATR 1 // if pin is an output, set by ATR logic + +//possible bit values for sel when dbg is 1: +#define GPIO_SEL_DEBUG_0 0 // if pin is an output, debug lines from FPGA fabric +#define GPIO_SEL_DEBUG_1 1 // if pin is an output, debug lines from FPGA fabric + + +//////////////////////////////////////////////////// +// Slave 5 -- Settings Bus +// +// Output-only, no readback, 32 registers total +// Each register must be written 32 bits at a time +// First the address xxx_xx00 and then xxx_xx10 + +#define UE_REG_SETTINGS_BASE UE_REG_SLAVE(5) + +/////////////////////////////////////////////////// +// Slave 6 -- ATR Controller +// 16 regs + +#define UE_REG_ATR_BASE UE_REG_SLAVE(6) + +#define UE_REG_ATR_IDLE_RXSIDE UE_REG_ATR_BASE + 0 +#define UE_REG_ATR_IDLE_TXSIDE UE_REG_ATR_BASE + 2 +#define UE_REG_ATR_INTX_RXSIDE UE_REG_ATR_BASE + 4 +#define UE_REG_ATR_INTX_TXSIDE UE_REG_ATR_BASE + 6 +#define UE_REG_ATR_INRX_RXSIDE UE_REG_ATR_BASE + 8 +#define UE_REG_ATR_INRX_TXSIDE UE_REG_ATR_BASE + 10 +#define UE_REG_ATR_FULL_RXSIDE UE_REG_ATR_BASE + 12 +#define UE_REG_ATR_FULL_TXSIDE UE_REG_ATR_BASE + 14 + +///////////////////////////////////////////////// +// DSP RX Regs +//////////////////////////////////////////////// +#define UE_REG_DSP_RX_FREQ UE_REG_SR_ADDR(0) +#define UE_REG_DSP_RX_SCALE_IQ UE_REG_SR_ADDR(1) // {scale_i,scale_q} +#define UE_REG_DSP_RX_DECIM_RATE UE_REG_SR_ADDR(2) // hb and decim rate +#define UE_REG_DSP_RX_DCOFFSET_I UE_REG_SR_ADDR(3) // Bit 31 high sets fixed offset mode, using lower 14 bits, // otherwise it is automatic +#define UE_REG_DSP_RX_DCOFFSET_Q UE_REG_SR_ADDR(4) // Bit 31 high sets fixed offset mode, using lower 14 bits +#define UE_REG_DSP_RX_MUX UE_REG_SR_ADDR(5) + +/////////////////////////////////////////////////// +// VITA RX CTRL regs +/////////////////////////////////////////////////// +// The following 3 are logically a single command register. +// They are clocked into the underlying fifo when time_ticks is written. +#define UE_REG_CTRL_RX_STREAM_CMD UE_REG_SR_ADDR(8) // {now, chain, num_samples(30) +#define UE_REG_CTRL_RX_TIME_SECS UE_REG_SR_ADDR(9) +#define UE_REG_CTRL_RX_TIME_TICKS UE_REG_SR_ADDR(10) +#define UE_REG_CTRL_RX_CLEAR_OVERRUN UE_REG_SR_ADDR(11) // write anything to clear overrun +#define UE_REG_CTRL_RX_VRT_HEADER UE_REG_SR_ADDR(12) // word 0 of packet. FPGA fills in packet counter +#define UE_REG_CTRL_RX_VRT_STREAM_ID UE_REG_SR_ADDR(13) // word 1 of packet. +#define UE_REG_CTRL_RX_VRT_TRAILER UE_REG_SR_ADDR(14) +#define UE_REG_CTRL_RX_NSAMPS_PER_PKT UE_REG_SR_ADDR(15) +#define UE_REG_CTRL_RX_NCHANNELS UE_REG_SR_ADDR(16) // 1 in basic case, up to 4 for vector sources + +///////////////////////////////////////////////// +// DSP TX Regs +//////////////////////////////////////////////// +#define UE_REG_DSP_TX_FREQ UE_REG_SR_ADDR(17) +#define UE_REG_DSP_TX_SCALE_IQ UE_REG_SR_ADDR(18) // {scale_i,scale_q} +#define UE_REG_DSP_TX_INTERP_RATE UE_REG_SR_ADDR(19) +#define UE_REG_DSP_TX_UNUSED UE_REG_SR_ADDR(20) +#define UE_REG_DSP_TX_MUX UE_REG_SR_ADDR(21) + +///////////////////////////////////////////////// +// VITA TX CTRL regs +//////////////////////////////////////////////// +#define UE_REG_CTRL_TX_NCHANNELS UE_REG_SR_ADDR(24) +#define UE_REG_CTRL_TX_CLEAR_UNDERRUN UE_REG_SR_ADDR(25) +#define UE_REG_CTRL_TX_REPORT_SID UE_REG_SR_ADDR(26) +#define UE_REG_CTRL_TX_POLICY UE_REG_SR_ADDR(27) + +#define UE_FLAG_CTRL_TX_POLICY_WAIT (0x1 << 0) +#define UE_FLAG_CTRL_TX_POLICY_NEXT_PACKET (0x1 << 1) +#define UE_FLAG_CTRL_TX_POLICY_NEXT_BURST (0x1 << 2) + +///////////////////////////////////////////////// +// VITA49 64 bit time (write only) +//////////////////////////////////////////////// + /*! + * \brief Time 64 flags + * + * <pre> + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-----------------------------------------------------------+-+-+ + * | |S|P| + * +-----------------------------------------------------------+-+-+ + * + * P - PPS edge selection (0=negedge, 1=posedge, default=0) + * S - Source (0=sma, 1=mimo, 0=default) + * + * </pre> + */ +#define UE_REG_TIME64_SECS UE_REG_SR_ADDR(28) // value to set absolute secs to on next PPS +#define UE_REG_TIME64_TICKS UE_REG_SR_ADDR(29) // value to set absolute ticks to on next PPS +#define UE_REG_TIME64_FLAGS UE_REG_SR_ADDR(30) // flags - see chart above +#define UE_REG_TIME64_IMM UE_REG_SR_ADDR(31) // set immediate (0=latch on next pps, 1=latch immediate, default=0) +#define UE_REG_TIME64_TPS UE_REG_SR_ADDR(31) // clock ticks per second (counter rollover) + +//pps flags (see above) +#define UE_FLAG_TIME64_PPS_NEGEDGE (0 << 0) +#define UE_FLAG_TIME64_PPS_POSEDGE (1 << 0) +#define UE_FLAG_TIME64_PPS_SMA (0 << 1) +#define UE_FLAG_TIME64_PPS_MIMO (1 << 1) + +#define UE_FLAG_TIME64_LATCH_NOW 1 +#define UE_FLAG_TIME64_LATCH_NEXT_PPS 0 + +#endif + |