diff options
| author | Josh Blum <josh@joshknows.com> | 2011-06-14 17:25:22 -0700 | 
|---|---|---|
| committer | Josh Blum <josh@joshknows.com> | 2011-06-14 17:56:54 -0700 | 
| commit | 5dfc458188538a32f95c05f860587d2c542e14c1 (patch) | |
| tree | 6f581982cba117af423b830a2199f300fe846d0d | |
| parent | deae10bc1c85726ed7c206abca37a869c6f77cea (diff) | |
| download | uhd-5dfc458188538a32f95c05f860587d2c542e14c1.tar.gz uhd-5dfc458188538a32f95c05f860587d2c542e14c1.tar.bz2 uhd-5dfc458188538a32f95c05f860587d2c542e14c1.zip | |
b100: added b100 host (squashed)
32 files changed, 4456 insertions, 14 deletions
| diff --git a/host/docs/CMakeLists.txt b/host/docs/CMakeLists.txt index 5926e062e..88afb9c98 100644 --- a/host/docs/CMakeLists.txt +++ b/host/docs/CMakeLists.txt @@ -30,6 +30,7 @@ SET(manual_sources      transport.rst      usrp1.rst      usrp2.rst +    usrp_b1xx.rst      usrp_e1xx.rst  ) diff --git a/host/docs/index.rst b/host/docs/index.rst index 40fe64599..b4ebdad6c 100644 --- a/host/docs/index.rst +++ b/host/docs/index.rst @@ -24,6 +24,7 @@ Application Notes  * `USRP1 Application Notes <./usrp1.html>`_  * `USRP2 Application Notes <./usrp2.html>`_  * `USRP-N2XX Series Application Notes <./usrp2.html>`_ +* `USRP-B1XX Series Application Notes <./usrp_b1xx.html>`_  * `USRP-E1XX Series Application Notes <./usrp_e1xx.html>`_  * `Daughterboard Application Notes <./dboards.html>`_  * `Transport Application Notes <./transport.html>`_ diff --git a/host/docs/usrp_b1xx.rst b/host/docs/usrp_b1xx.rst new file mode 100644 index 000000000..2f7385070 --- /dev/null +++ b/host/docs/usrp_b1xx.rst @@ -0,0 +1,76 @@ +======================================================================== +UHD - USRP-B1XX Series Application Notes +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Specify a non-standard image +------------------------------------------------------------------------ +The UHD will automatically select the USRP B-Series images from the installed images package. +The image selection can be overridden with the "fpga" and "fw" device address parameters. + +Example device address string representations to specify non-standard images: + +:: + +    fpga=usrp_b100_fpga_firmware.bin + +    -- OR -- + +    fw=usrp_b100_fw_firmware.ihx + +------------------------------------------------------------------------ +Changing the master clock rate +------------------------------------------------------------------------ +The master clock rate of the USRP embedded feeds both the FPGA DSP and the codec chip. +Hundreds of rates between 32MHz and 64MHz are available. +A few notable rates are: + +* 64MHz - maximum rate of the codec chip +* 61.44MHz - good for UMTS/WCDMA applications +* 52Mhz - good for GSM applications + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Set 61.44MHz - uses external VCXO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To use the 61.44MHz clock rate, the USRP embedded will require two jumpers to be moved. + +* J16 is a two pin header, remove the jumper (or leave it on pin1 only) +* J15 is a three pin header, move the jumper to (pin1, pin2) + +**Note:** See instructions below to communicate the desired clock rate into the UHD. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Set other rates - uses internal VCO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To use other clock rates, the jumpers will need to be in the default position. + +* J16 is a two pin header, move the jumper to (pin1, pin2) +* J15 is a three pin header, move the jumper to (pin2, pin3) + +To communicate the desired clock rate into the UHD, +specify the a special device address argument, +where the key is "master_clock_rate" and the value is a rate in Hz. +Example: +:: + +    uhd_usrp_probe --args="master_clock_rate=52e6" + +------------------------------------------------------------------------ +OS specific notes +------------------------------------------------------------------------ + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Linux - setup udev +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +On Linux, udev handles USB plug and unplug events. +The following commands create a udev rule for the B100 +so that non-root users may access the device: + +:: + +    echo 'ACTION=="add", BUS=="usb", SYSFS{idVendor}=="2500", SYSFS{idProduct}=="0001", MODE:="0666"' > tmpfile +    sudo chown root.root tmpfile +    sudo mv tmpfile /etc/udev/rules.d/10-usrp_b100.rules +    sudo udevadm control --reload-rules diff --git a/host/include/uhd/transport/usb_zero_copy.hpp b/host/include/uhd/transport/usb_zero_copy.hpp index b39171fba..dc344ad8b 100644 --- a/host/include/uhd/transport/usb_zero_copy.hpp +++ b/host/include/uhd/transport/usb_zero_copy.hpp @@ -1,5 +1,5 @@  // -// Copyright 2010 Ettus Research LLC +// Copyright 2010-2011 Ettus Research LLC  //  // This program is free software: you can redistribute it and/or modify  // it under the terms of the GNU General Public License as published by @@ -49,6 +49,7 @@ public:       * \param recv_endpoint an integer specifiying an IN endpoint number       * \param send_endpoint an integer specifiying an OUT endpoint number       * \param hints optional parameters to pass to the underlying transport +     * \return a new zero copy usb object       */      static sptr make(          usb_device_handle::sptr handle, @@ -56,6 +57,24 @@ public:          size_t send_endpoint,          const device_addr_t &hints = device_addr_t()      ); + +    /*! +     * Make a wrapper around a zero copy implementation. +     * The wrapper performs the following functions: +     * - Pad commits to the frame boundary +     * - Extract multiple packets on recv +     * +     * When enable multiple receive packets is set to true, +     * the implementation inspects the vita length on transfers, +     * and may split a single transfer into multiple managed buffers. +     * +     * \param usb_zc a usb zero copy interface object +     * \param usb_frame_boundary bytes per frame +     * \return a new zero copy wrapper object +     */ +    static sptr make_wrapper( +        sptr usb_zc, size_t usb_frame_boundary = 512 +    );  };  }} //namespace diff --git a/host/lib/transport/CMakeLists.txt b/host/lib/transport/CMakeLists.txt index 439c3d230..b1821956c 100644 --- a/host/lib/transport/CMakeLists.txt +++ b/host/lib/transport/CMakeLists.txt @@ -98,4 +98,5 @@ LIBUHD_APPEND_SOURCES(      ${CMAKE_CURRENT_SOURCE_DIR}/if_addrs.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/udp_simple.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/udp_zero_copy.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/usb_zero_copy_wrapper.cpp  ) diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp index 28bea978b..19a7a3742 100644 --- a/host/lib/transport/libusb1_zero_copy.cpp +++ b/host/lib/transport/libusb1_zero_copy.cpp @@ -20,7 +20,7 @@  #include <uhd/transport/bounded_buffer.hpp>  #include <uhd/transport/buffer_pool.hpp>  #include <uhd/utils/thread_priority.hpp> -#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp>  #include <uhd/exception.hpp>  #include <boost/function.hpp>  #include <boost/foreach.hpp> @@ -44,15 +44,24 @@ static const size_t DEFAULT_XFER_SIZE = 32*512; //bytes   * to ensure that they are compiled with the same calling convention as libusb.   */ -//! helper function: handles all async callbacks -static void LIBUSB_CALL libusb_async_cb(libusb_transfer *lut){ +//! helper function: handles all rx async callbacks +static void LIBUSB_CALL libusb_async_rx_cb(libusb_transfer *lut){ +    if(lut->actual_length == 0) { +        UHD_ASSERT_THROW(libusb_submit_transfer(lut) == 0); //get out until you find some real data +        return; +    } +    (*static_cast<boost::function<void()> *>(lut->user_data))(); +} + +//! helper function: handles all tx async callbacks +static void LIBUSB_CALL libusb_async_tx_cb(libusb_transfer *lut) {      (*static_cast<boost::function<void()> *>(lut->user_data))();  }  //! callback to free transfer upon cancellation  static void LIBUSB_CALL cancel_transfer_cb(libusb_transfer *lut){ -    if (lut->status == LIBUSB_TRANSFER_CANCELLED) libusb_free_transfer(lut); -    else UHD_LOGV(rarely) << "libusb cancel_transfer unexpected status " << lut->status << std::endl; +    if (lut->status == LIBUSB_TRANSFER_CANCELLED || lut->status == LIBUSB_TRANSFER_TIMED_OUT) libusb_free_transfer(lut); +    else UHD_MSG(error) << "libusb cancel_transfer unexpected status " << lut->status << std::endl;  }  /*********************************************************************** @@ -97,7 +106,7 @@ public:      void commit(size_t len){          if (_expired) return;          _lut->length = len; -        if(len == 0) libusb_async_cb(_lut); +        if(len == 0) libusb_async_tx_cb(_lut);          else UHD_ASSERT_THROW(libusb_submit_transfer(_lut) == 0);          _expired = true;      } @@ -157,9 +166,9 @@ public:                  (recv_endpoint & 0x7f) | 0x80,                          // endpoint                  static_cast<unsigned char *>(_recv_buffer_pool->at(i)), // buffer                  this->get_recv_frame_size(),                            // length -                libusb_transfer_cb_fn(&libusb_async_cb),                // callback +                libusb_transfer_cb_fn(&libusb_async_rx_cb),             // callback                  static_cast<void *>(&_callbacks.back()),                // user_data -                0                                                       // timeout +                0                                                       // timeout (ms)              );              _all_luts.push_back(lut); @@ -183,13 +192,13 @@ public:                  (send_endpoint & 0x7f) | 0x00,                          // endpoint                  static_cast<unsigned char *>(_send_buffer_pool->at(i)), // buffer                  this->get_send_frame_size(),                            // length -                libusb_transfer_cb_fn(&libusb_async_cb),                // callback +                libusb_transfer_cb_fn(&libusb_async_tx_cb),             // callback                  static_cast<void *>(&_callbacks.back()),                // user_data                  0                                                       // timeout              );              _all_luts.push_back(lut); -            libusb_async_cb(lut); +            libusb_async_tx_cb(lut);          }          //spawn the event handler threads @@ -206,7 +215,9 @@ public:          BOOST_FOREACH(libusb_transfer *lut, _all_luts){              lut->callback = libusb_transfer_cb_fn(&cancel_transfer_cb);              libusb_cancel_transfer(lut); -            while(lut->status != LIBUSB_TRANSFER_CANCELLED && lut->status != LIBUSB_TRANSFER_COMPLETED) { +            while(lut->status != LIBUSB_TRANSFER_CANCELLED +               && lut->status != LIBUSB_TRANSFER_COMPLETED +               && lut->status != LIBUSB_TRANSFER_TIMED_OUT) {                  boost::this_thread::sleep(boost::posix_time::milliseconds(10));              }          } diff --git a/host/lib/transport/usb_zero_copy_wrapper.cpp b/host/lib/transport/usb_zero_copy_wrapper.cpp new file mode 100644 index 000000000..227c4b392 --- /dev/null +++ b/host/lib/transport/usb_zero_copy_wrapper.cpp @@ -0,0 +1,202 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <boost/foreach.hpp> +#include <vector> +#include <iostream> + +using namespace uhd::transport; +bool debug = true; + +static inline size_t next_boundary(size_t length, size_t boundary){ +    //pad to the boundary, assumes boundary is a power of 2 +    return (length + (boundary-1)) & ~(boundary-1); +} + +/*********************************************************************** + * USB zero copy wrapper - managed receive buffer + **********************************************************************/ +class usb_zero_copy_wrapper_mrb : public managed_recv_buffer{ +public: +    usb_zero_copy_wrapper_mrb(bounded_buffer<usb_zero_copy_wrapper_mrb *> &queue): +        _queue(queue){/*NOP*/} + +    void release(void){ +        if (_mrb.get() == NULL) return; +        _mrb->release(); +        _queue.push_with_haste(this); +        _mrb.reset(); +    } + +    sptr get_new(managed_recv_buffer::sptr mrb, const void *mem, size_t len){ +        _mrb = mrb; +        _mem = mem; +        _len = len; +        return make_managed_buffer(this); +    } + +private: +    const void *get_buff(void) const{return _mem;} +    size_t get_size(void) const{return _len;} + +    bounded_buffer<usb_zero_copy_wrapper_mrb *> &_queue; +    const void *_mem; +    size_t _len; +    managed_recv_buffer::sptr _mrb; +}; + +/*********************************************************************** + * USB zero copy wrapper - managed send buffer + **********************************************************************/ +class usb_zero_copy_wrapper_msb : public managed_send_buffer{ +public: +    usb_zero_copy_wrapper_msb(bounded_buffer<usb_zero_copy_wrapper_msb *> &queue, size_t boundary): +        _queue(queue), _boundary(boundary){/*NOP*/} + +    void commit(size_t len){ +        if (_msb.get() == NULL) return; +        _msb->commit(next_boundary(len, _boundary)); +        _queue.push_with_haste(this); +        _msb.reset(); +    } + +    sptr get_new(managed_send_buffer::sptr msb){ +        _msb = msb; +        return make_managed_buffer(this); +    } + +private: +    void *get_buff(void) const{return _msb->cast<void *>();} +    size_t get_size(void) const{return _msb->size();} + +    bounded_buffer<usb_zero_copy_wrapper_msb *> &_queue; +    size_t _boundary; +    managed_send_buffer::sptr _msb; +}; + +/*********************************************************************** + * USB zero copy wrapper implementation + **********************************************************************/ +class usb_zero_copy_wrapper : public usb_zero_copy{ +public: +    usb_zero_copy_wrapper( +        sptr usb_zc, size_t usb_frame_boundary +    ): +        _internal_zc(usb_zc), +        _usb_frame_boundary(usb_frame_boundary), +        _available_recv_buffs(this->get_num_recv_frames()), +        _available_send_buffs(this->get_num_send_frames()), +        _mrb_pool(this->get_num_recv_frames(), usb_zero_copy_wrapper_mrb(_available_recv_buffs)), +        _msb_pool(this->get_num_send_frames(), usb_zero_copy_wrapper_msb(_available_send_buffs, usb_frame_boundary)) +    { +        BOOST_FOREACH(usb_zero_copy_wrapper_mrb &mrb, _mrb_pool){ +            _available_recv_buffs.push_with_haste(&mrb); +        } + +        BOOST_FOREACH(usb_zero_copy_wrapper_msb &msb, _msb_pool){ +            _available_send_buffs.push_with_haste(&msb); +        } +    } + +    managed_recv_buffer::sptr get_recv_buff(double timeout){ +        //attempt to get a managed recv buffer +        if (not _last_recv_buff.get()){ +            _last_recv_buff = _internal_zc->get_recv_buff(timeout); +            _last_recv_offset = 0; +        } + +        //attempt to get a wrapper for a managed recv buffer +        usb_zero_copy_wrapper_mrb *wmrb = NULL; +        if (_last_recv_buff.get() and _available_recv_buffs.pop_with_timed_wait(wmrb, timeout)){ +            //extract this packet's memory address and length in bytes +            const char *mem = _last_recv_buff->cast<const char *>() + _last_recv_offset; +            const boost::uint32_t *mem32 = reinterpret_cast<const boost::uint32_t *>(mem); +            size_t len = (mem32[0] & 0xffff)*sizeof(boost::uint32_t); //length in bytes (from VRT header) +             +            managed_recv_buffer::sptr recv_buff; //the buffer to be returned to the user +             +            recv_buff = wmrb->get_new(_last_recv_buff, mem, len); +            _last_recv_offset = next_boundary(_last_recv_offset + len, _usb_frame_boundary); +             +            //check if this receive buffer has been exhausted +            if (_last_recv_offset >= _last_recv_buff->size()) { +                _last_recv_buff.reset(); +            } +             +            return recv_buff; +        } + +        //otherwise return a null sptr for failure +        return managed_recv_buffer::sptr(); +    } + +    size_t get_num_recv_frames(void) const{ +        return _internal_zc->get_num_recv_frames(); +    } + +    size_t get_recv_frame_size(void) const{ +        return _internal_zc->get_recv_frame_size(); +    } + +    managed_send_buffer::sptr get_send_buff(double timeout){ +        managed_send_buffer::sptr send_buff = _internal_zc->get_send_buff(timeout); +         +        //attempt to get a wrapper for a managed send buffer +        usb_zero_copy_wrapper_msb *wmsb = NULL; +        if (send_buff.get() and _available_send_buffs.pop_with_haste(wmsb)){ +            return wmsb->get_new(send_buff); +        } + +        //otherwise return a null sptr for failure +        return managed_send_buffer::sptr(); +    } + +    size_t get_num_send_frames(void) const{ +        return _internal_zc->get_num_send_frames(); +    } + +    size_t get_send_frame_size(void) const{ +        return _internal_zc->get_send_frame_size(); +    } + +private: +    sptr _internal_zc; +    size_t _usb_frame_boundary; +    bounded_buffer<usb_zero_copy_wrapper_mrb *> _available_recv_buffs; +    bounded_buffer<usb_zero_copy_wrapper_msb *> _available_send_buffs; +    std::vector<usb_zero_copy_wrapper_mrb> _mrb_pool; +    std::vector<usb_zero_copy_wrapper_msb> _msb_pool; +     +    //buffer to store partially-received VRT packets in +    buffer_pool::sptr _fragment_mem; +     +    //state for last recv buffer to create multiple managed buffers +    managed_recv_buffer::sptr _last_recv_buff; +    size_t _last_recv_offset; +}; + +/*********************************************************************** + * USB zero copy wrapper factory function + **********************************************************************/ +usb_zero_copy::sptr usb_zero_copy::make_wrapper( +    sptr usb_zc, size_t usb_frame_boundary +){ +    return sptr(new usb_zero_copy_wrapper(usb_zc, usb_frame_boundary)); +} diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index 281910735..a30a45977 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -37,4 +37,5 @@ INCLUDE_SUBDIRECTORY(dboard)  INCLUDE_SUBDIRECTORY(fx2)  INCLUDE_SUBDIRECTORY(usrp1)  INCLUDE_SUBDIRECTORY(usrp2) +INCLUDE_SUBDIRECTORY(usrp_b100)  INCLUDE_SUBDIRECTORY(usrp_e100) diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp index 6f8de9a7b..566e24d97 100644 --- a/host/lib/usrp/dboard/db_basic_and_lf.cpp +++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp @@ -111,6 +111,7 @@ basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){      this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0000);      this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0xFFFF);      this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0x0000); +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true);  }  basic_rx::~basic_rx(void){ @@ -216,6 +217,7 @@ void basic_rx::rx_set(const wax::obj &key_, const wax::obj &val){   **********************************************************************/  basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){      _max_freq = max_freq; +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true);  }  basic_tx::~basic_tx(void){ diff --git a/host/lib/usrp/fx2/fx2_ctrl.hpp b/host/lib/usrp/fx2/fx2_ctrl.hpp index 98c98bb8c..37fa09605 100644 --- a/host/lib/usrp/fx2/fx2_ctrl.hpp +++ b/host/lib/usrp/fx2/fx2_ctrl.hpp @@ -57,7 +57,7 @@ public:       * \param filename name of EEPROM image       */      virtual void usrp_load_eeprom(std::string filestring) = 0; - +          /*!       * Submit an IN transfer        * \param request device specific request  diff --git a/host/lib/usrp/usrp_b100/CMakeLists.txt b/host/lib/usrp/usrp_b100/CMakeLists.txt new file mode 100644 index 000000000..f83a10935 --- /dev/null +++ b/host/lib/usrp/usrp_b100/CMakeLists.txt @@ -0,0 +1,47 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the B100 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF) + +IF(ENABLE_B100) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/clock_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/clock_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/codec_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/codec_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/codec_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/ctrl_packet.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/dboard_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/dboard_iface.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/dsp_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/io_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/mboard_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/b100_ctrl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/b100_ctrl.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/b100_iface.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/b100_iface.hpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/b100_impl.cpp +        ${CMAKE_SOURCE_DIR}/lib/usrp/usrp_b100/b100_impl.hpp +    ) +ENDIF(ENABLE_B100) diff --git a/host/lib/usrp/usrp_b100/b100_ctrl.cpp b/host/lib/usrp/usrp_b100/b100_ctrl.cpp new file mode 100644 index 000000000..4d4520e1e --- /dev/null +++ b/host/lib/usrp/usrp_b100/b100_ctrl.cpp @@ -0,0 +1,245 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "../../transport/super_recv_packet_handler.hpp" +#include "b100_ctrl.hpp" +#include "b100_impl.hpp" +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/serial.hpp> +#include "ctrl_packet.hpp" +#include <boost/thread.hpp> +#include <uhd/exception.hpp> + +using namespace uhd::transport; +using namespace uhd; + +bool b100_ctrl_debug = false; + +class b100_ctrl_impl : public b100_ctrl { +public: +    b100_ctrl_impl(uhd::transport::usb_zero_copy::sptr ctrl_transport) :  +        sync_ctrl_fifo(2), +        async_msg_fifo(100), +        _ctrl_transport(ctrl_transport), +        _seq(0) +    { +        boost::barrier spawn_barrier(2); +        viking_marauders.create_thread(boost::bind(&b100_ctrl_impl::viking_marauder_loop, this, boost::ref(spawn_barrier))); +        spawn_barrier.wait(); +    } +     +    int write(boost::uint32_t addr, const ctrl_data_t &data); +    ctrl_data_t read(boost::uint32_t addr, size_t len); +     +    ~b100_ctrl_impl(void) { +        bbl_out_marauding = false; +        viking_marauders.interrupt_all(); +        viking_marauders.join_all(); +    } +     +    bool get_ctrl_data(ctrl_data_t &pkt_data, double timeout); +    bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout); +     +private: +    int send_pkt(boost::uint16_t *cmd); +     +    //änd hërë wë gö ä-Vïkïng för äsynchronous control packets +    void viking_marauder_loop(boost::barrier &); +    bounded_buffer<ctrl_data_t> sync_ctrl_fifo; +    bounded_buffer<async_metadata_t> async_msg_fifo; +    boost::thread_group viking_marauders; +    bool bbl_out_marauding; +     +    uhd::transport::usb_zero_copy::sptr _ctrl_transport; +    boost::uint8_t _seq; +}; + +/*********************************************************************** + * helper functions for packing/unpacking control packets + **********************************************************************/ +void pack_ctrl_pkt(boost::uint16_t *pkt_buff, +                          const ctrl_pkt_t &pkt){ +    //first two bits are OP +    //next six bits are CALLBACKS +    //next 8 bits are SEQUENCE +    //next 16 bits are LENGTH (16-bit word) +    //next 32 bits are ADDRESS (16-bit word LSW) +    //then DATA (28 16-bit words) +    pkt_buff[0] = (boost::uint16_t(pkt.pkt_meta.op) << 14) | (boost::uint16_t(pkt.pkt_meta.callbacks) << 8) | pkt.pkt_meta.seq; +    pkt_buff[1] = pkt.pkt_meta.len; +    pkt_buff[2] = (pkt.pkt_meta.addr & 0x00000FFF); +    pkt_buff[3] = 0x0000; //address high bits always 0 on this device +     +    for(size_t i = 0; i < pkt.data.size(); i++) { +        pkt_buff[4+i] = pkt.data[i]; +    } +} + +void unpack_ctrl_pkt(const boost::uint16_t *pkt_buff, +                            ctrl_pkt_t &pkt){ +    pkt.pkt_meta.seq = pkt_buff[0] & 0xFF; +    pkt.pkt_meta.op = CTRL_PKT_OP_READ; //really this is useless +    pkt.pkt_meta.len = pkt_buff[1]; +    pkt.pkt_meta.callbacks = 0; //callbacks aren't implemented yet +    pkt.pkt_meta.addr = pkt_buff[2] | boost::uint32_t(pkt_buff[3] << 16); +     +    //let's check this so we don't go pushing 64K of crap onto the pkt +    if(pkt.pkt_meta.len > CTRL_PACKET_DATA_LENGTH) { +        throw uhd::runtime_error("Received control packet too long"); +    } + +    for(int i = 4; i < 4+pkt.pkt_meta.len; i++) pkt.data.push_back(pkt_buff[i]); +} + +int b100_ctrl_impl::send_pkt(boost::uint16_t *cmd) { +    managed_send_buffer::sptr sbuf = _ctrl_transport->get_send_buff(); +    if(!sbuf.get()) { +        throw uhd::runtime_error("Control channel send error"); +    } +         +    //FIXME there's a better way to do this +    for(size_t i = 0; i < (CTRL_PACKET_LENGTH / sizeof(boost::uint16_t)); i++) { +        sbuf->cast<boost::uint16_t *>()[i] = cmd[i]; +    } +    sbuf->commit(CTRL_PACKET_LENGTH); //fixed size transaction +    return 0; +} + +int b100_ctrl_impl::write(boost::uint32_t addr, const ctrl_data_t &data) { +    UHD_ASSERT_THROW(data.size() <= (CTRL_PACKET_DATA_LENGTH / sizeof(boost::uint16_t))); +    ctrl_pkt_t pkt; +    pkt.data = data; +    pkt.pkt_meta.op = CTRL_PKT_OP_WRITE; +    pkt.pkt_meta.callbacks = 0; +    pkt.pkt_meta.seq = _seq++; +    pkt.pkt_meta.len = pkt.data.size(); +    pkt.pkt_meta.addr = addr; +    boost::uint16_t pkt_buff[CTRL_PACKET_LENGTH / sizeof(boost::uint16_t)]; +         +    pack_ctrl_pkt(pkt_buff, pkt); +    size_t result = send_pkt(pkt_buff); +    return result; +} + +ctrl_data_t b100_ctrl_impl::read(boost::uint32_t addr, size_t len) { +    UHD_ASSERT_THROW(len <= (CTRL_PACKET_DATA_LENGTH / sizeof(boost::uint16_t))); + +    ctrl_pkt_t pkt; +    pkt.pkt_meta.op = CTRL_PKT_OP_READ; +    pkt.pkt_meta.callbacks = 0; +    pkt.pkt_meta.seq = _seq++; +    pkt.pkt_meta.len = len; +    pkt.pkt_meta.addr = addr; +    boost::uint16_t pkt_buff[CTRL_PACKET_LENGTH / sizeof(boost::uint16_t)]; + +    pack_ctrl_pkt(pkt_buff, pkt); +    send_pkt(pkt_buff); +     +    //loop around waiting for the response to appear +    while(!get_ctrl_data(pkt.data, 0.05)); + +    return pkt.data; +} + +/*********************************************************************** + * Viking marauders go pillaging for asynchronous control packets in the + * control response endpoint. Sync packets go in sync_ctrl_fifo, + * async TX error messages go in async_msg_fifo. sync_ctrl_fifo should + * never have more than 1 message in it, since it's expected that we'll + * wait for a control operation to finish before starting another one. + **********************************************************************/ +void b100_ctrl_impl::viking_marauder_loop(boost::barrier &spawn_barrier) { +    bbl_out_marauding = true; +    spawn_barrier.wait(); +    set_thread_priority_safe(); +     +    while(bbl_out_marauding){ +        managed_recv_buffer::sptr rbuf = _ctrl_transport->get_recv_buff(); +        if(!rbuf.get()) continue; //that's ok, there are plenty of villages to pillage! +        const boost::uint16_t *pkt_buf = rbuf->cast<const boost::uint16_t *>(); +         +        if(pkt_buf[0] >> 8 == CTRL_PACKET_HEADER_MAGIC) { +            //so it's got a control packet header, let's parse it. +            ctrl_pkt_t pkt; +            unpack_ctrl_pkt(pkt_buf, pkt); +         +            if(pkt.pkt_meta.seq != boost::uint8_t(_seq - 1)) { +                throw uhd::runtime_error("Sequence error on control channel"); +            } +            if(pkt.pkt_meta.len > (CTRL_PACKET_LENGTH - CTRL_PACKET_HEADER_LENGTH)) { +                throw uhd::runtime_error("Control channel packet length too long"); +            } +         +            //push it onto the queue +            sync_ctrl_fifo.push_with_wait(pkt.data); +        } else { //it's an async status pkt +            //extract the vrt header packet info +            vrt::if_packet_info_t if_packet_info; +            if_packet_info.num_packet_words32 = rbuf->size()/sizeof(boost::uint32_t); +            const boost::uint32_t *vrt_hdr = rbuf->cast<const boost::uint32_t *>(); +            vrt::if_hdr_unpack_le(vrt_hdr, if_packet_info); +             +            if(    if_packet_info.sid == 0  +               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), 64e6 //FIXME get from clock_ctrl +                ); +                metadata.event_code = async_metadata_t::event_code_t(sph::get_context_code(vrt_hdr, if_packet_info)); +                //print the famous U, and push the metadata into the message queue +                if (metadata.event_code &  +                    ( async_metadata_t::EVENT_CODE_UNDERFLOW  +                    | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET) ) +                    UHD_MSG(fastpath) << "U"; +                     +                if (metadata.event_code &  +                    ( async_metadata_t::EVENT_CODE_SEQ_ERROR +                    | async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST) ) +                    UHD_MSG(fastpath) << "S"; +                     +                async_msg_fifo.push_with_pop_on_full(metadata); +                continue; +            } +            throw uhd::runtime_error("Control: unknown async response"); +        } +    } +} + +bool b100_ctrl_impl::get_ctrl_data(ctrl_data_t &pkt_data, double timeout){ +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return sync_ctrl_fifo.pop_with_timed_wait(pkt_data, timeout); +} + +bool b100_ctrl_impl::recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout) { +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return async_msg_fifo.pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Public make function for b100_ctrl interface + **********************************************************************/ +b100_ctrl::sptr b100_ctrl::make(uhd::transport::usb_zero_copy::sptr ctrl_transport){ +    return sptr(new b100_ctrl_impl(ctrl_transport)); +} diff --git a/host/lib/usrp/usrp_b100/b100_ctrl.hpp b/host/lib/usrp/usrp_b100/b100_ctrl.hpp new file mode 100644 index 000000000..ae706dbb4 --- /dev/null +++ b/host/lib/usrp/usrp_b100/b100_ctrl.hpp @@ -0,0 +1,69 @@ +// +// 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_B100_CTRL_HPP +#define INCLUDED_B100_CTRL_HPP + +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include "ctrl_packet.hpp" +#include <boost/thread.hpp> + +class b100_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<b100_ctrl> sptr; + +    /*! +     * Make a USRP control object from a data transport +     * \param ctrl_transport a USB data transport +     * \return a new b100 control object +     */ +    static sptr make(uhd::transport::usb_zero_copy::sptr ctrl_transport); + +    /*! +     * Write a byte vector to an FPGA register +     * \param addr the FPGA register address +     * \param bytes the data to write +     * \return 0 on success, error code on failure +     */ +    virtual int write(boost::uint32_t addr, const ctrl_data_t &data) = 0; + +    /*! +     * Read a byte vector from an FPGA register (blocking read) +     * \param addr the FPGA register address +     * \param len the length of the read +     * \return a vector of bytes from the register(s) in question +     */ +    virtual ctrl_data_t read(boost::uint32_t addr, size_t len) = 0; + +    /*! +     * Get a sync ctrl packet (blocking) +     * \param the packet data buffer +     * \param the timeout value +     * \return true if it got something +     */ +    virtual bool get_ctrl_data(ctrl_data_t &pkt_data, double timeout) = 0; +     +    virtual bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout) = 0; +     +}; + +#endif /* INCLUDED_B100_CTRL_HPP */ diff --git a/host/lib/usrp/usrp_b100/b100_iface.cpp b/host/lib/usrp/usrp_b100/b100_iface.cpp new file mode 100644 index 000000000..17ea2e6ad --- /dev/null +++ b/host/lib/usrp/usrp_b100/b100_iface.cpp @@ -0,0 +1,336 @@ +// +// 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 "b100_iface.hpp" +#include "usrp_commands.h" +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/format.hpp> +#include <iomanip> +#include <iostream> + +//FOR TESTING ONLY +#include "b100_regs.hpp" +#include <boost/thread/thread.hpp> +#include "usrp_i2c_addr.h" + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const bool iface_debug = true; +static const boost::uint16_t USRP_B_FW_COMPAT_NUM = 0x02; +static const boost::uint16_t USRP_B_FPGA_COMPAT_NUM = 0x03; + +/*********************************************************************** + * I2C + FX2 implementation wrapper + **********************************************************************/ +class b100_i2c_fx2_iface : public i2c_iface{ +public: +    b100_i2c_fx2_iface(uhd::usrp::fx2_ctrl::sptr fx2_ctrl){ +        _fx2_ctrl = fx2_ctrl; +    } + +    void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes) +    { +        UHD_ASSERT_THROW(bytes.size() < max_i2c_data_bytes); + +        unsigned char buff[max_i2c_data_bytes]; +        std::copy(bytes.begin(), bytes.end(), buff); + +        int ret = _fx2_ctrl->usrp_i2c_write(addr & 0xff, +                                             buff, +                                             bytes.size()); + +        if (iface_debug && (ret < 0)) +            uhd::runtime_error("USRP: failed i2c write"); +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes) +    { +      UHD_ASSERT_THROW(num_bytes < max_i2c_data_bytes); + +      unsigned char buff[max_i2c_data_bytes]; +      int ret = _fx2_ctrl->usrp_i2c_read(addr & 0xff, +                                            buff, +                                            num_bytes); + +      if (iface_debug && ((ret < 0) || (unsigned)ret < (num_bytes))) +          uhd::runtime_error("USRP: failed i2c read"); + +      byte_vector_t out_bytes; +      for (size_t i = 0; i < num_bytes; i++) +          out_bytes.push_back(buff[i]); + +      return out_bytes; +    } + +private: +    static const size_t max_i2c_data_bytes = 64; +    uhd::usrp::fx2_ctrl::sptr _fx2_ctrl; +}; + +/*********************************************************************** + * USRP-E100 interface implementation + **********************************************************************/ +class b100_iface_impl : public b100_iface{ +public: +    /******************************************************************* +     * Structors +     ******************************************************************/ +    b100_iface_impl(uhd::usrp::fx2_ctrl::sptr fx2_ctrl, +                      b100_ctrl::sptr fpga_ctrl) : +                      _fx2_i2c_iface(fx2_ctrl), +                      _fx2_ctrl(fx2_ctrl), +                      _fpga_ctrl(fpga_ctrl) +    { +        this->check_fw_compat(); +        if (fpga_ctrl.get() != NULL){ +            enable_gpif(1); +            i2c_init(); +            this->check_fpga_compat(); +        } +        mb_eeprom = mboard_eeprom_t(get_fx2_i2c_iface(), mboard_eeprom_t::MAP_B000); +    } + +    void check_fw_compat(void){ +        unsigned char data[4]; //useless data buffer +        const boost::uint16_t fw_compat_num = _fx2_ctrl->usrp_control_read( +            VRQ_FW_COMPAT, 0, 0, data, sizeof(data) +        ); +        if (fw_compat_num != USRP_B_FW_COMPAT_NUM){ +            throw uhd::runtime_error(str(boost::format( +                "Expected firmware compatibility number 0x%x, but got 0x%x:\n" +                "The firmware build is not compatible with the host code build." +            ) % USRP_B_FW_COMPAT_NUM % fw_compat_num)); +        } +    } + +    void check_fpga_compat(void){ +        const boost::uint16_t fpga_compat_num = this->peek16(B100_REG_MISC_COMPAT); +        if (fpga_compat_num != USRP_B_FPGA_COMPAT_NUM){ +            throw uhd::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_B_FPGA_COMPAT_NUM % fpga_compat_num)); +        } +    } + +    ~b100_iface_impl(void) +    { +        /* NOP */ +    } + +    /******************************************************************* +     * Peek and Poke +     ******************************************************************/ + +    void poke(boost::uint32_t addr, const ctrl_data_t &data) { +        boost::mutex::scoped_lock lock(_ctrl_mutex); +        _fpga_ctrl->write(addr, data); +    } + +    ctrl_data_t peek(boost::uint32_t addr, size_t len) { +        boost::mutex::scoped_lock lock(_ctrl_mutex); +        return _fpga_ctrl->read(addr, len); +    } + +    void poke16(boost::uint32_t addr, boost::uint16_t value) +    { +        ctrl_data_t words(1); +        words[0] = value; +        poke(addr, words); +    } + +    void poke32(boost::uint32_t addr, boost::uint32_t value) +    { +        //just a subset of poke() to maintain compatibility +        ctrl_data_t words(2); +        words[0] = value & 0x0000FFFF; +        words[1] = value >> 16; +        poke(addr, words); +    } + +    boost::uint32_t peek32(boost::uint32_t addr) +    { +        ctrl_data_t words = peek(addr, 2); +        return boost::uint32_t((boost::uint32_t(words[1]) << 16) | words[0]); +    } + +    boost::uint16_t peek16(boost::uint32_t addr) +    { +        ctrl_data_t words = peek(addr, 1); +        return boost::uint16_t(words[0]); +    } + +    /******************************************************************* +     * I2C +     ******************************************************************/ +    static const boost::uint32_t i2c_datarate = 400000; +    static const boost::uint32_t wishbone_clk = 64000000; //FIXME should go somewhere else + +    void i2c_init(void) { +        //init I2C FPGA interface. +        poke16(B100_REG_I2C_CTRL, 0x0000); +        //set prescalers to operate at 400kHz: WB_CLK is 64MHz... +        boost::uint16_t prescaler = wishbone_clk / (i2c_datarate*5) - 1; +        poke16(B100_REG_I2C_PRESCALER_LO, prescaler & 0xFF); +        poke16(B100_REG_I2C_PRESCALER_HI, (prescaler >> 8) & 0xFF); +        poke16(B100_REG_I2C_CTRL, I2C_CTRL_EN); //enable I2C core +    } + +    static const size_t max_i2c_data_bytes = 64; + +    void i2c_wait_for_xfer(void) +    { +        while(this->peek16(B100_REG_I2C_CMD_STATUS) & I2C_ST_TIP) +            boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +    } + +    bool wait_chk_ack(void) { +        i2c_wait_for_xfer(); +        return (this->peek16(B100_REG_I2C_CMD_STATUS) & I2C_ST_RXACK) == 0; +    } + +    void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes) +    { +        poke16(B100_REG_I2C_DATA, (addr << 1) | 0); //addr and read bit (0) +        poke16(B100_REG_I2C_CMD_STATUS, I2C_CMD_WR | I2C_CMD_START | (bytes.size() == 0 ? I2C_CMD_STOP : 0)); + +        //wait for previous transfer to complete +        if(!wait_chk_ack()) { +            poke16(B100_REG_I2C_CMD_STATUS, I2C_CMD_STOP); +            return; +        } + +        for(size_t i = 0; i < bytes.size(); i++) { +            poke16(B100_REG_I2C_DATA, bytes[i]); +            poke16(B100_REG_I2C_CMD_STATUS, I2C_CMD_WR | ((i == (bytes.size() - 1)) ? I2C_CMD_STOP : 0)); +            if(!wait_chk_ack()) { +                poke16(B100_REG_I2C_CMD_STATUS, I2C_CMD_STOP); +                return; +            } +        } +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes) +    { +        byte_vector_t bytes; +        if(num_bytes == 0) return bytes; + +        while (peek16(B100_REG_I2C_CMD_STATUS) & I2C_ST_BUSY); + +        poke16(B100_REG_I2C_DATA, (addr << 1) | 1); //addr and read bit (1) +        poke16(B100_REG_I2C_CMD_STATUS, I2C_CMD_WR | I2C_CMD_START); +        //wait for previous transfer to complete +        if(!wait_chk_ack()) { +            poke16(B100_REG_I2C_CMD_STATUS, I2C_CMD_STOP); +        } + +        for(; num_bytes > 0; num_bytes--) { +            poke16(B100_REG_I2C_CMD_STATUS, I2C_CMD_RD | ((num_bytes == 1) ? (I2C_CMD_STOP | I2C_CMD_NACK) : 0)); +            i2c_wait_for_xfer(); +            boost::uint8_t readback = peek16(B100_REG_I2C_DATA) & 0xFF; +            bytes.push_back(readback); +        } +        return bytes; +    } + +    i2c_iface &get_fx2_i2c_iface(void){ +        return _fx2_i2c_iface; +    } + +    /******************************************************************* +     * SPI interface +     * Eventually this will be replaced with a control-channel system +     * to let the firmware do the actual write/readback cycles. +     * This keeps the bandwidth on the control channel down. +     ******************************************************************/ + +    void spi_wait(void) { +        while(peek32(B100_REG_SPI_CTRL) & SPI_CTRL_GO_BSY); +    } + +    boost::uint32_t transact_spi(int which_slave, +                                 const spi_config_t &config, +                                 boost::uint32_t bits, +                                 size_t num_bits, +                                 bool readback) +    { +        UHD_ASSERT_THROW((num_bits <= 32) && !(num_bits % 8)); + +        int edge_flags = ((config.miso_edge==spi_config_t::EDGE_FALL) ? SPI_CTRL_RXNEG : 0) | +                         ((config.mosi_edge==spi_config_t::EDGE_FALL) ? 0 : SPI_CTRL_TXNEG) +                         ; + +        boost::uint16_t ctrl = SPI_CTRL_ASS | (SPI_CTRL_CHAR_LEN_MASK & num_bits) | edge_flags; + +        poke16(B100_REG_SPI_DIV, 0x0001); // = fpga_clk / 4 +        poke32(B100_REG_SPI_SS, which_slave & 0xFFFF); +        poke32(B100_REG_SPI_TXRX0, bits); +        poke16(B100_REG_SPI_CTRL, ctrl); + +        poke16(B100_REG_SPI_CTRL, ctrl | SPI_CTRL_GO_BSY); +        if(readback) { +            spi_wait(); +            return peek32(B100_REG_SPI_TXRX0); +        } +        else { +            return 0; +        } +    } + +    void reset_gpif(boost::uint16_t ep) { +        _fx2_ctrl->usrp_control_write(VRQ_RESET_GPIF, ep, ep, 0, 0); +    } + +    void enable_gpif(bool en) { +        _fx2_ctrl->usrp_control_write(VRQ_ENABLE_GPIF, en ? 1 : 0, 0, 0, 0); +    } + +    void clear_fpga_fifo(void) { +        _fx2_ctrl->usrp_control_write(VRQ_CLEAR_FPGA_FIFO, 0, 0, 0, 0); +    } + +    void write_uart(boost::uint8_t, const std::string &) { +        throw uhd::not_implemented_error("Unhandled command write_uart()"); +    } +     +    std::string read_uart(boost::uint8_t) { +        throw uhd::not_implemented_error("Unhandled command read_uart()"); +    } + +private: +    b100_i2c_fx2_iface _fx2_i2c_iface; +    uhd::usrp::fx2_ctrl::sptr _fx2_ctrl; +    b100_ctrl::sptr _fpga_ctrl; +    boost::mutex _ctrl_mutex; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +b100_iface::sptr b100_iface::make(uhd::usrp::fx2_ctrl::sptr fx2_ctrl, +                                      b100_ctrl::sptr fpga_ctrl) +{ +    return b100_iface::sptr(new b100_iface_impl(fx2_ctrl, fpga_ctrl)); +} diff --git a/host/lib/usrp/usrp_b100/b100_iface.hpp b/host/lib/usrp/usrp_b100/b100_iface.hpp new file mode 100644 index 000000000..4da43681d --- /dev/null +++ b/host/lib/usrp/usrp_b100/b100_iface.hpp @@ -0,0 +1,73 @@ +// +// 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_B100_IFACE_HPP +#define INCLUDED_B100_IFACE_HPP + +#include <uhd/usrp/mboard_iface.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include "fx2_ctrl.hpp" +#include "b100_ctrl.hpp" + +/*! + * The usrp1 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class b100_iface : boost::noncopyable, public uhd::usrp::mboard_iface{ +public: +    typedef boost::shared_ptr<b100_iface> sptr; + +    /*! +     * Make a new b100 interface with the control transport. +     * \param fx2_ctrl the usrp control object +     * \param fpga_ctrl the FPGA interface control object +     * \return a new usrp1 interface object +     */ +    static sptr make(uhd::usrp::fx2_ctrl::sptr fx2_ctrl, +                     b100_ctrl::sptr fpga_ctrl = b100_ctrl::sptr() +    ); + +    /*! +     * Reset the GPIF interface on the FX2 +     * \param which endpoint to reset +     * \return +     */ +    virtual void reset_gpif(boost::uint16_t ep) = 0; + +    /*! +     * Clear the GPIF FIFOs on the FPGA +     * \return +     */ +    virtual void clear_fpga_fifo(void) = 0; + +    /*! +     * Enable/disable the GPIF interfaces on the FX2 +     * \return +     */ +    virtual void enable_gpif(bool en) = 0; + +    //! Get access to the FX2 I2C interface +    virtual uhd::i2c_iface &get_fx2_i2c_iface(void) = 0; + +    uhd::usrp::mboard_eeprom_t mb_eeprom; +}; + +#endif /* INCLUDED_USRP1_IFACE_HPP */ diff --git a/host/lib/usrp/usrp_b100/b100_impl.cpp b/host/lib/usrp/usrp_b100/b100_impl.cpp new file mode 100644 index 000000000..066831a92 --- /dev/null +++ b/host/lib/usrp/usrp_b100/b100_impl.cpp @@ -0,0 +1,277 @@ +// +// 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 "b100_impl.hpp" +#include "b100_ctrl.hpp" +#include "fpga_regs_standard.h" +#include "usrp_spi_defs.h" +#include <uhd/transport/usb_control.hpp> +#include "ctrl_packet.hpp" +#include <uhd/usrp/device_props.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/format.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread/thread.hpp> +#include <boost/lexical_cast.hpp> +#include "b100_regs.hpp" + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +const boost::uint16_t B100_VENDOR_ID  = 0x2500; +const boost::uint16_t B100_PRODUCT_ID = 0x0001; +const boost::uint16_t FX2_VENDOR_ID    = 0x04b4; +const boost::uint16_t FX2_PRODUCT_ID   = 0x8613; + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t b100_find(const device_addr_t &hint) +{ +    device_addrs_t b100_addrs; + +    //return an empty list of addresses when type is set to non-b100 +    if (hint.has_key("type") and hint["type"] != "b100") return b100_addrs; + +    //extract the firmware path for the b100 +    std::string b100_fw_image; +    try{ +        b100_fw_image = find_image_path( +            hint.has_key("fw")? hint["fw"] : "usrp_b100_fw_c2.ihx" +        ); +    } +    catch(...){ +        UHD_MSG(warning) << boost::format( +            "Could not locate B100 firmware.\n" +            "Please install the images package.\n" +        ); +        return b100_addrs; +    } + +    boost::uint16_t vid = hint.has_key("uninit") ? FX2_VENDOR_ID : B100_VENDOR_ID; +    boost::uint16_t pid = hint.has_key("uninit") ? FX2_PRODUCT_ID : B100_PRODUCT_ID; + +    // Important note: +    // The get device list calls are nested inside the for loop. +    // This allows the usb guts to decontruct when not in use, +    // so that re-enumeration after fw load can occur successfully. +    // This requirement is a courtesy of libusb1.0 on windows. + +    //find the usrps and load firmware +    BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { +        try { +            fx2_ctrl::make(usb_control::make(handle))->usrp_load_firmware(b100_fw_image); +        } catch (...) { +            UHD_MSG(status) << "Interface claimed, ignoring device" << std::endl; +        } +    } + +    //get descriptors again with serial number, but using the initialized VID/PID now since we have firmware +    vid = B100_VENDOR_ID; +    pid = B100_PRODUCT_ID; + +    BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { +        device_addr_t new_addr; +        new_addr["type"] = "b100"; + +        //Attempt to read the name from the EEPROM and perform filtering. +        //This operation can throw due to compatibility mismatch. +        try{ +            usb_control::sptr control = usb_control::make(handle); +            b100_iface::sptr iface = b100_iface::make(fx2_ctrl::make(control)); +            new_addr["name"] = iface->mb_eeprom["name"]; +            new_addr["serial"] = handle->get_serial(); +        } +        catch(const uhd::exception &){ +            //set these values as empty string so the device may still be found +            //and the filter's below can still operate on the discovered device +            new_addr["name"] = ""; +            new_addr["serial"] = ""; +        } + +        //this is a found b100 when the hint serial and name match or blank +        if ( +            (not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +            (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) +        ){ +            b100_addrs.push_back(new_addr); +        } +    } + +    return b100_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr b100_make(const device_addr_t &device_addr){ + +    //extract the FPGA path for the B100 +    std::string b100_fpga_image = find_image_path( +        device_addr.has_key("fpga")? device_addr["fpga"] : "usrp_b100_fpga_c3.bin" +    ); + +    //try to match the given device address with something on the USB bus +    std::vector<usb_device_handle::sptr> device_list = +        usb_device_handle::get_device_list(B100_VENDOR_ID, B100_PRODUCT_ID); + +    //locate the matching handle in the device list +    usb_device_handle::sptr handle; +    BOOST_FOREACH(usb_device_handle::sptr dev_handle, device_list) { +        if (dev_handle->get_serial() == device_addr["serial"]){ +            handle = dev_handle; +            break; +        } +    } +    UHD_ASSERT_THROW(handle.get() != NULL); //better be found + +    //create control objects and a data transport +    usb_control::sptr fx2_transport = usb_control::make(handle); +    fx2_ctrl::sptr fx2_ctrl = fx2_ctrl::make(fx2_transport); +    fx2_ctrl->usrp_load_fpga(b100_fpga_image); + +    device_addr_t data_xport_args; +    data_xport_args["recv_frame_size"] = device_addr.get("recv_frame_size", "16384"); +    data_xport_args["num_recv_frames"] = device_addr.get("num_recv_frames", "16"); +    data_xport_args["send_frame_size"] = device_addr.get("send_frame_size", "16384"); +    data_xport_args["num_send_frames"] = device_addr.get("num_send_frames", "16"); + +    usb_zero_copy::sptr data_transport = usb_zero_copy::make_wrapper( +        usb_zero_copy::make( +            handle,        // identifier +            6,             // IN endpoint +            2,             // OUT endpoint +            data_xport_args    // param hints +        ) +    ); + +    //create the control transport +    device_addr_t ctrl_xport_args; +    ctrl_xport_args["recv_frame_size"] = boost::lexical_cast<std::string>(CTRL_PACKET_LENGTH); +    ctrl_xport_args["num_recv_frames"] = "16"; +    ctrl_xport_args["send_frame_size"] = boost::lexical_cast<std::string>(CTRL_PACKET_LENGTH); +    ctrl_xport_args["num_send_frames"] = "4"; + +    usb_zero_copy::sptr ctrl_transport = usb_zero_copy::make( +        handle, +        8, +        4, +        ctrl_xport_args +    ); + +    const double master_clock_rate = device_addr.cast<double>("master_clock_rate", 64e6); + + +    //create the b100 implementation guts +    return device::sptr(new b100_impl(data_transport, ctrl_transport, fx2_ctrl, master_clock_rate)); +} + +UHD_STATIC_BLOCK(register_b100_device){ +    device::register_device(&b100_find, &b100_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +b100_impl::b100_impl(uhd::transport::usb_zero_copy::sptr data_transport, +                         uhd::transport::usb_zero_copy::sptr ctrl_transport, +                         uhd::usrp::fx2_ctrl::sptr fx2_ctrl, +                         const double master_clock_rate) + : _data_transport(data_transport), _fx2_ctrl(fx2_ctrl) +{ +    //this is the handler object for FPGA control packets +    _fpga_ctrl = b100_ctrl::make(ctrl_transport); + +    _iface = b100_iface::make(_fx2_ctrl, _fpga_ctrl); + +    //create clock interface +    _clock_ctrl = b100_clock_ctrl::make(_iface, master_clock_rate); + +    //create codec interface +    _codec_ctrl = b100_codec_ctrl::make(_iface); + +    //initialize the codecs +    codec_init(); + +    //initialize the mboard +    mboard_init(); + +    //initialize the dboards +    dboard_init(); + +    //initialize the dsps +    rx_ddc_init(); + +    //initialize the dsps +    tx_duc_init(); + +    //init the subdev specs +    this->mboard_set(MBOARD_PROP_RX_SUBDEV_SPEC, subdev_spec_t()); +    this->mboard_set(MBOARD_PROP_TX_SUBDEV_SPEC, subdev_spec_t()); + +    //initialize the send/recv buffs +    io_init(); +} + +b100_impl::~b100_impl(void){ +    /* NOP */ +} + +bool b100_impl::recv_async_msg(uhd::async_metadata_t &md, double timeout){ +    return _fpga_ctrl->recv_async_msg(md, timeout); +} + +/*********************************************************************** + * Device Get + **********************************************************************/ +void b100_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-B100 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 b100_impl::set(const wax::obj &, const wax::obj &) +{ +    UHD_THROW_PROP_SET_ERROR(); +} diff --git a/host/lib/usrp/usrp_b100/b100_impl.hpp b/host/lib/usrp/usrp_b100/b100_impl.hpp new file mode 100644 index 000000000..daec70bca --- /dev/null +++ b/host/lib/usrp/usrp_b100/b100_impl.hpp @@ -0,0 +1,204 @@ +// +// 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 "b100_iface.hpp" +#include "b100_ctrl.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/device.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/transport/usb_zero_copy.hpp> + +#ifndef INCLUDED_B100_IMPL_HPP +#define INCLUDED_B100_IMPL_HPP + +/*! + * Make a b100 dboard interface. + * \param iface the b100 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_b100_dboard_iface( +    b100_iface::sptr iface, +    b100_clock_ctrl::sptr clock, +    b100_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);} +}; + +/*! + * USRP1 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class b100_impl : public uhd::device { +public: +    //structors +    b100_impl(uhd::transport::usb_zero_copy::sptr data_transport, +                uhd::transport::usb_zero_copy::sptr ctrl_transport, +                uhd::usrp::fx2_ctrl::sptr fx2_ctrl, +                double master_clock_rate); + +    ~b100_impl(void); + +    //the io interface +    size_t send(const send_buffs_type &, +                size_t, +                const uhd::tx_metadata_t &, +                const uhd::io_type_t &, +                send_mode_t, double); + +    size_t recv(const recv_buffs_type &, +                size_t, uhd::rx_metadata_t &, +                const uhd::io_type_t &, +                recv_mode_t, double); + +    size_t get_max_send_samps_per_packet(void) const; + +    size_t get_max_recv_samps_per_packet(void) const; + +    bool recv_async_msg(uhd::async_metadata_t &, double); + +private: +    //clock control +    b100_clock_ctrl::sptr _clock_ctrl; + +    //interface to ioctls and file descriptor +    b100_iface::sptr _iface; + +    //handle io stuff +    uhd::transport::zero_copy_if::sptr _data_transport; +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void update_transport_channel_mapping(void); +    void io_init(void); +    void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd); +    void handle_overrun(size_t); + +    //otw types +    uhd::otw_type_t _recv_otw_type; +    uhd::otw_type_t _send_otw_type; + +    //configuration shadows +    uhd::clock_config_t _clock_config; +    uhd::usrp::subdev_spec_t _rx_subdev_spec, _tx_subdev_spec; + +    //ad9862 codec control interface +    b100_codec_ctrl::sptr _codec_ctrl; + +    //codec properties interfaces +    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; + +    //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 &); +    void update_clock_config(void); +    wax_obj_proxy::sptr _mboard_proxy; +     +    /*! +     * Make a usrp1 dboard interface. +     * \param iface the usrp1 interface object +     * \param clock the clock control interface +     * \param codec the codec control interface +     * \param dboard_slot the slot identifier +     * \param rx_dboard_id the db id for the rx board (used for evil dbsrx purposes) +     * \return a sptr to a new dboard interface +     */ +    static uhd::usrp::dboard_iface::sptr make_dboard_iface( +        b100_iface::sptr iface, +        b100_clock_ctrl::sptr clock, +        b100_codec_ctrl::sptr codec, +        const uhd::usrp::dboard_id_t &rx_dboard_id +    ); + +    //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, _gdb_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; + +    //transports +    b100_ctrl::sptr _fpga_ctrl; +    uhd::usrp::fx2_ctrl::sptr _fx2_ctrl; + +}; + +#endif /* INCLUDED_b100_IMPL_HPP */ diff --git a/host/lib/usrp/usrp_b100/b100_regs.hpp b/host/lib/usrp/usrp_b100/b100_regs.hpp new file mode 100644 index 000000000..010df283e --- /dev/null +++ b/host/lib/usrp/usrp_b100/b100_regs.hpp @@ -0,0 +1,264 @@ + + +//////////////////////////////////////////////////////////////// +// +//         Memory map for 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 __B100_REGS_H +#define __B100_REGS_H + +///////////////////////////////////////////////////// +// Slave pointers + +#define B100_REG_SLAVE(n) ((n)<<7) +#define B100_REG_SR_ADDR(n) ((B100_REG_SETTINGS_BASE) + (4*(n))) + +///////////////////////////////////////////////////// +// Slave 0 -- Misc Regs + +#define B100_REG_MISC_BASE B100_REG_SLAVE(0) + +#define B100_REG_MISC_LED        B100_REG_MISC_BASE + 0 +#define B100_REG_MISC_SW         B100_REG_MISC_BASE + 2 +#define B100_REG_MISC_CGEN_CTRL  B100_REG_MISC_BASE + 4 +#define B100_REG_MISC_CGEN_ST    B100_REG_MISC_BASE + 6 +#define B100_REG_MISC_TEST       B100_REG_MISC_BASE + 8 +#define B100_REG_MISC_RX_LEN     B100_REG_MISC_BASE + 10 +#define B100_REG_MISC_TX_LEN     B100_REG_MISC_BASE + 12 +#define B100_REG_MISC_XFER_RATE  B100_REG_MISC_BASE + 14 +#define B100_REG_MISC_COMPAT     B100_REG_MISC_BASE + 16 + +///////////////////////////////////////////////////// +// Slave 1 -- UART +//   CLKDIV is 16 bits, others are only 8 + +#define B100_REG_UART_BASE B100_REG_SLAVE(1) + +#define B100_REG_UART_CLKDIV  B100_REG_UART_BASE + 0 +#define B100_REG_UART_TXLEVEL B100_REG_UART_BASE + 2 +#define B100_REG_UART_RXLEVEL B100_REG_UART_BASE + 4 +#define B100_REG_UART_TXCHAR  B100_REG_UART_BASE + 6 +#define B100_REG_UART_RXCHAR  B100_REG_UART_BASE + 8 + +///////////////////////////////////////////////////// +// Slave 2 -- SPI Core +//these are 32-bit registers mapped onto the 16-bit Wishbone bus.  +//Using peek32/poke32 should allow transparent use of these registers. +#define B100_REG_SPI_BASE B100_REG_SLAVE(2) +#define B100_REG_SPI_TXRX0 B100_REG_SPI_BASE + 0 +#define B100_REG_SPI_TXRX1 B100_REG_SPI_BASE + 4 +#define B100_REG_SPI_TXRX2 B100_REG_SPI_BASE + 8 +#define B100_REG_SPI_TXRX3 B100_REG_SPI_BASE + 12 +#define B100_REG_SPI_CTRL  B100_REG_SPI_BASE + 16 +#define B100_REG_SPI_DIV   B100_REG_SPI_BASE + 20 +#define B100_REG_SPI_SS    B100_REG_SPI_BASE + 24 + +//spi slave constants +#define B100_SPI_SS_AD9862    (1 << 2) +#define B100_SPI_SS_TX_DB     (1 << 1) +#define B100_SPI_SS_RX_DB     (1 << 0) + +//spi ctrl register bit definitions +#define SPI_CTRL_ASS      (1<<13) +#define SPI_CTRL_IE       (1<<12) +#define SPI_CTRL_LSB      (1<<11) +#define SPI_CTRL_TXNEG    (1<<10) //mosi edge, push on falling edge when 1 +#define SPI_CTRL_RXNEG    (1<< 9) //miso edge, latch on falling edge when 1 +#define SPI_CTRL_GO_BSY   (1<< 8) +#define SPI_CTRL_CHAR_LEN_MASK 0x7F + +//////////////////////////////////////////////// +// Slave 3 -- I2C Core + +#define B100_REG_I2C_BASE B100_REG_SLAVE(3) +#define B100_REG_I2C_PRESCALER_LO B100_REG_I2C_BASE + 0 +#define B100_REG_I2C_PRESCALER_HI B100_REG_I2C_BASE + 2 +#define B100_REG_I2C_CTRL         B100_REG_I2C_BASE + 4 +#define B100_REG_I2C_DATA         B100_REG_I2C_BASE + 6 +#define B100_REG_I2C_CMD_STATUS   B100_REG_I2C_BASE + 8 + +//and while we're here... + +// +// STA, STO, RD, WR, and IACK bits are cleared automatically +// + +#define	I2C_CTRL_EN	(1 << 7)	// core enable +#define	I2C_CTRL_IE	(1 << 6)	// interrupt enable + +#define	I2C_CMD_START	(1 << 7)	// generate (repeated) start condition +#define I2C_CMD_STOP	(1 << 6)	// generate stop condition +#define	I2C_CMD_RD	(1 << 5)	// read from slave +#define I2C_CMD_WR	(1 << 4)	// write to slave +#define	I2C_CMD_NACK	(1 << 3)	// when a rcvr, send ACK (ACK=0) or NACK (ACK=1) +#define I2C_CMD_RSVD_2	(1 << 2)	// reserved +#define	I2C_CMD_RSVD_1	(1 << 1)	// reserved +#define I2C_CMD_IACK	(1 << 0)	// set to clear pending interrupt + +#define I2C_ST_RXACK	(1 << 7)	// Received acknowledgement from slave (1 = NAK, 0 = ACK) +#define	I2C_ST_BUSY	(1 << 6)	// 1 after START signal detected; 0 after STOP signal detected +#define	I2C_ST_AL	(1 << 5)	// Arbitration lost.  1 when core lost arbitration +#define	I2C_ST_RSVD_4	(1 << 4)	// reserved +#define	I2C_ST_RSVD_3	(1 << 3)	// reserved +#define	I2C_ST_RSVD_2	(1 << 2)	// reserved +#define I2C_ST_TIP	(1 << 1)	// Transfer-in-progress +#define	I2C_ST_IP	(1 << 0)	// Interrupt pending + +//////////////////////////////////////////////// +// Slave 4 -- GPIO + +#define B100_REG_GPIO_BASE B100_REG_SLAVE(4) + +#define B100_REG_GPIO_RX_IO      B100_REG_GPIO_BASE + 0 +#define B100_REG_GPIO_TX_IO      B100_REG_GPIO_BASE + 2 +#define B100_REG_GPIO_RX_DDR     B100_REG_GPIO_BASE + 4 +#define B100_REG_GPIO_TX_DDR     B100_REG_GPIO_BASE + 6 +#define B100_REG_GPIO_RX_SEL     B100_REG_GPIO_BASE + 8 +#define B100_REG_GPIO_TX_SEL     B100_REG_GPIO_BASE + 10 +#define B100_REG_GPIO_RX_DBG     B100_REG_GPIO_BASE + 12 +#define B100_REG_GPIO_TX_DBG     B100_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 6 -- ATR Controller +//   16 regs + +#define B100_REG_ATR_BASE  B100_REG_SLAVE(6) + +#define	B100_REG_ATR_IDLE_RXSIDE  B100_REG_ATR_BASE + 0 +#define	B100_REG_ATR_IDLE_TXSIDE  B100_REG_ATR_BASE + 2 +#define B100_REG_ATR_INTX_RXSIDE  B100_REG_ATR_BASE + 4 +#define B100_REG_ATR_INTX_TXSIDE  B100_REG_ATR_BASE + 6 +#define	B100_REG_ATR_INRX_RXSIDE  B100_REG_ATR_BASE + 8 +#define	B100_REG_ATR_INRX_TXSIDE  B100_REG_ATR_BASE + 10 +#define	B100_REG_ATR_FULL_RXSIDE  B100_REG_ATR_BASE + 12 +#define	B100_REG_ATR_FULL_TXSIDE  B100_REG_ATR_BASE + 14 + +/////////////////////////////////////////////////// +// Slave 7 -- Readback Mux 32 + +#define B100_REG_RB_MUX_32_BASE  B100_REG_SLAVE(7) + +#define B100_REG_RB_TIME_NOW_SECS   B100_REG_RB_MUX_32_BASE + 0 +#define B100_REG_RB_TIME_NOW_TICKS  B100_REG_RB_MUX_32_BASE + 4 +#define B100_REG_RB_TIME_PPS_SECS   B100_REG_RB_MUX_32_BASE + 8 +#define B100_REG_RB_TIME_PPS_TICKS  B100_REG_RB_MUX_32_BASE + 12 +#define B100_REG_RB_MISC_TEST32     B100_REG_RB_MUX_32_BASE + 16 + +//////////////////////////////////////////////////// +// Slaves 8 & 9 -- Settings Bus +// +// Output-only, no readback, 64 registers total +//  Each register must be written 32 bits at a time +//  First the address xxx_xx00 and then xxx_xx10 + +#define B100_REG_SETTINGS_BASE_ADDR(n) (B100_REG_SLAVE(8) + (4*(n))) + +#define B100_REG_SR_MISC_TEST32        B100_REG_SETTINGS_BASE_ADDR(52) + +///////////////////////////////////////////////// +// DSP RX Regs +//////////////////////////////////////////////// +#define B100_REG_DSP_RX_ADDR(n)      (B100_REG_SETTINGS_BASE_ADDR(16) + (4*(n))) +#define B100_REG_DSP_RX_FREQ         B100_REG_DSP_RX_ADDR(0) +#define B100_REG_DSP_RX_SCALE_IQ     B100_REG_DSP_RX_ADDR(1)  // {scale_i,scale_q} +#define B100_REG_DSP_RX_DECIM_RATE   B100_REG_DSP_RX_ADDR(2)  // hb and decim rate +#define B100_REG_DSP_RX_DCOFFSET_I   B100_REG_DSP_RX_ADDR(3) // Bit 31 high sets fixed offset mode, using lower 14 bits, // otherwise it is automatic +#define B100_REG_DSP_RX_DCOFFSET_Q   B100_REG_DSP_RX_ADDR(4) // Bit 31 high sets fixed offset mode, using lower 14 bits +#define B100_REG_DSP_RX_MUX          B100_REG_DSP_RX_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 B100_REG_CTRL_RX_ADDR(n)           (B100_REG_SETTINGS_BASE_ADDR(0) + (4*(n))) +#define B100_REG_CTRL_RX_STREAM_CMD        B100_REG_CTRL_RX_ADDR(0) // {now, chain, num_samples(30) +#define B100_REG_CTRL_RX_TIME_SECS         B100_REG_CTRL_RX_ADDR(1) +#define B100_REG_CTRL_RX_TIME_TICKS        B100_REG_CTRL_RX_ADDR(2) +#define B100_REG_CTRL_RX_CLEAR_OVERRUN     B100_REG_CTRL_RX_ADDR(3) // write anything to clear overrun +#define B100_REG_CTRL_RX_VRT_HEADER        B100_REG_CTRL_RX_ADDR(4) // word 0 of packet.  FPGA fills in packet counter +#define B100_REG_CTRL_RX_VRT_STREAM_ID     B100_REG_CTRL_RX_ADDR(5) // word 1 of packet. +#define B100_REG_CTRL_RX_VRT_TRAILER       B100_REG_CTRL_RX_ADDR(6) +#define B100_REG_CTRL_RX_NSAMPS_PER_PKT    B100_REG_CTRL_RX_ADDR(7) +#define B100_REG_CTRL_RX_NCHANNELS         B100_REG_CTRL_RX_ADDR(8) // 1 in basic case, up to 4 for vector sources + +///////////////////////////////////////////////// +// DSP TX Regs +//////////////////////////////////////////////// +#define B100_REG_DSP_TX_ADDR(n)      (B100_REG_SETTINGS_BASE_ADDR(32) + (4*(n))) +#define B100_REG_DSP_TX_FREQ         B100_REG_DSP_TX_ADDR(0) +#define B100_REG_DSP_TX_SCALE_IQ     B100_REG_DSP_TX_ADDR(1) // {scale_i,scale_q} +#define B100_REG_DSP_TX_INTERP_RATE  B100_REG_DSP_TX_ADDR(2) +#define B100_REG_DSP_TX_UNUSED       B100_REG_DSP_TX_ADDR(3) +#define B100_REG_DSP_TX_MUX          B100_REG_DSP_TX_ADDR(4) + +///////////////////////////////////////////////// +// VITA TX CTRL regs +//////////////////////////////////////////////// +#define B100_REG_CTRL_TX_ADDR(n)           (B100_REG_SETTINGS_BASE_ADDR(24) + (4*(n))) +#define B100_REG_CTRL_TX_NCHANNELS         B100_REG_CTRL_TX_ADDR(0) +#define B100_REG_CTRL_TX_CLEAR_UNDERRUN    B100_REG_CTRL_TX_ADDR(1) +#define B100_REG_CTRL_TX_REPORT_SID        B100_REG_CTRL_TX_ADDR(2) +#define B100_REG_CTRL_TX_POLICY            B100_REG_CTRL_TX_ADDR(3) + +#define B100_FLAG_CTRL_TX_POLICY_WAIT          (0x1 << 0) +#define B100_FLAG_CTRL_TX_POLICY_NEXT_PACKET   (0x1 << 1) +#define B100_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 B100_REG_TIME64_ADDR(n)     (B100_REG_SETTINGS_BASE_ADDR(40) + (4*(n))) +#define B100_REG_TIME64_SECS  B100_REG_TIME64_ADDR(0)  // value to set absolute secs to on next PPS +#define B100_REG_TIME64_TICKS B100_REG_TIME64_ADDR(1)  // value to set absolute ticks to on next PPS +#define B100_REG_TIME64_FLAGS B100_REG_TIME64_ADDR(2)  // flags - see chart above +#define B100_REG_TIME64_IMM   B100_REG_TIME64_ADDR(3)  // set immediate (0=latch on next pps, 1=latch immediate, default=0) +#define B100_REG_TIME64_TPS   B100_REG_TIME64_ADDR(4)  // clock ticks per second (counter rollover) + +//pps flags (see above) +#define B100_FLAG_TIME64_PPS_NEGEDGE (0 << 0) +#define B100_FLAG_TIME64_PPS_POSEDGE (1 << 0) +#define B100_FLAG_TIME64_PPS_SMA     (0 << 1) +#define B100_FLAG_TIME64_PPS_MIMO    (1 << 1) + +#define B100_FLAG_TIME64_LATCH_NOW 1 +#define B100_FLAG_TIME64_LATCH_NEXT_PPS 0 + +#define B100_REG_CLEAR_RX_FIFO     B100_REG_SETTINGS_BASE_ADDR(48) +#define B100_REG_CLEAR_TX_FIFO     B100_REG_SETTINGS_BASE_ADDR(49) + +#define B100_REG_GLOBAL_RESET      B100_REG_SETTINGS_BASE_ADDR(50) +#define B100_REG_TEST32            B100_REG_SETTINGS_BASE_ADDR(52) + +#endif + diff --git a/host/lib/usrp/usrp_b100/clock_ctrl.cpp b/host/lib/usrp/usrp_b100/clock_ctrl.cpp new file mode 100644 index 000000000..e138242d1 --- /dev/null +++ b/host/lib/usrp/usrp_b100/clock_ctrl.cpp @@ -0,0 +1,525 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9522_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include "b100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <algorithm> +#include <utility> + +using namespace uhd; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const bool ENABLE_THE_TEST_OUT = true; +static const double REFERENCE_INPUT_RATE = 10e6; + +/*********************************************************************** + * Helpers + **********************************************************************/ +template <typename div_type, typename bypass_type> static void set_clock_divider( +    size_t divider, div_type &low, div_type &high, bypass_type &bypass +){ +    high = divider/2 - 1; +    low = divider - high - 2; +    bypass = (divider == 1)? 1 : 0; +} + +/*********************************************************************** + * Clock rate calculation stuff: + *   Using the internal VCO between 1400 and 1800 MHz + **********************************************************************/ +struct clock_settings_type{ +    size_t ref_clock_doubler, r_counter, a_counter, b_counter, prescaler, vco_divider, chan_divider; +    size_t get_n_counter(void) const{return prescaler * b_counter + a_counter;} +    double get_ref_rate(void) const{return REFERENCE_INPUT_RATE * ref_clock_doubler;} +    double get_vco_rate(void) const{return get_ref_rate()/r_counter * get_n_counter();} +    double get_chan_rate(void) const{return get_vco_rate()/vco_divider;} +    double get_out_rate(void) const{return get_chan_rate()/chan_divider;} +    std::string to_pp_string(void) const{ +        return str(boost::format( +            "  r_counter: %d\n" +            "  a_counter: %d\n" +            "  b_counter: %d\n" +            "  prescaler: %d\n" +            "  vco_divider: %d\n" +            "  chan_divider: %d\n" +            "  vco_rate: %fMHz\n" +            "  chan_rate: %fMHz\n" +            "  out_rate: %fMHz\n" +            ) +            % r_counter +            % a_counter +            % b_counter +            % prescaler +            % vco_divider +            % chan_divider +            % (get_vco_rate()/1e6) +            % (get_chan_rate()/1e6) +            % (get_out_rate()/1e6) +        ); +    } +}; + +//! gives the greatest divisor of num between 1 and max inclusive +template<typename T> static inline T greatest_divisor(T num, T max){ +    for (T i = max; i > 1; i--) if (num%i == 0) return i; return 1; +} + +//! gives the least divisor of num between min and num exclusive +template<typename T> static inline T least_divisor(T num, T min){ +    for (T i = min; i < num; i++) if (num%i == 0) return i; return 1; +} + +static clock_settings_type get_clock_settings(double rate){ +    clock_settings_type cs; +    cs.ref_clock_doubler = 2; //always doubling +    cs.prescaler = 8; //set to 8 when input is under 2400 MHz + +    //basic formulas used below: +    //out_rate*X = ref_rate*Y +    //X = i*ref_rate/gcd +    //Y = i*out_rate/gcd +    //X = chan_div * vco_div * R +    //Y = P*B + A + +    const boost::uint64_t out_rate = boost::uint64_t(rate); +    const boost::uint64_t ref_rate = boost::uint64_t(cs.get_ref_rate()); +    const size_t gcd = size_t(boost::math::gcd(ref_rate, out_rate)); + +    for (size_t i = 1; i <= 100; i++){ +        const size_t X = i*ref_rate/gcd; +        const size_t Y = i*out_rate/gcd; + +        //determine A and B (P is fixed) +        cs.b_counter = Y/cs.prescaler; +        cs.a_counter = Y - cs.b_counter*cs.prescaler; + +        static const double vco_bound_pad = 100e6; +        for ( //calculate an r divider that fits into the bounds of the vco +            cs.r_counter  = size_t(cs.get_n_counter()*cs.get_ref_rate()/(1800e6 - vco_bound_pad)); +            cs.r_counter <= size_t(cs.get_n_counter()*cs.get_ref_rate()/(1400e6 + vco_bound_pad)) +            and cs.r_counter > 0; cs.r_counter++ +        ){ + +            //determine chan_div and vco_div +            //and fill in that order of preference +            cs.chan_divider = greatest_divisor<size_t>(X/cs.r_counter, 32); +            cs.vco_divider = greatest_divisor<size_t>(X/cs.chan_divider/cs.r_counter, 6); + +            //avoid a vco divider of 1 (if possible) +            if (cs.vco_divider == 1){ +                cs.vco_divider = least_divisor<size_t>(cs.chan_divider, 2); +                cs.chan_divider /= cs.vco_divider; +            } + +            UHD_LOGV(always) +                << "gcd " << gcd << std::endl +                << "X " << X << std::endl +                << "Y " << Y << std::endl +                << cs.to_pp_string() << std::endl +            ; + +            //filter limits on the counters +            if (cs.vco_divider == 1) continue; +            if (cs.r_counter >= (1<<14)) continue; +            if (cs.b_counter == 2) continue; +            if (cs.b_counter == 1 and cs.a_counter != 0) continue; +            if (cs.b_counter >= (1<<13)) continue; +            if (cs.a_counter >= (1<<6)) continue; + +            UHD_MSG(status) << "USRP-B100 clock control: " << i << std::endl << cs.to_pp_string() << std::endl; +            return cs; +        } +    } + +    throw uhd::runtime_error(str(boost::format( +        "USRP-B100 clock control: could not calculate settings for clock rate %fMHz" +    ) % (rate/1e6))); +} + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class b100_clock_ctrl_impl : public b100_clock_ctrl{ +public: +    b100_clock_ctrl_impl(b100_iface::sptr iface, double master_clock_rate){ +        _iface = iface; +        _chan_rate = 0.0; +        _out_rate = 0.0; + +        //init the clock gen registers +        _ad9522_regs.sdo_active = ad9522_regs_t::SDO_ACTIVE_SDO_SDIO; +        _ad9522_regs.enb_stat_eeprom_at_stat_pin = 0; //use status pin +        _ad9522_regs.status_pin_control = 0x1; //n divider +        _ad9522_regs.ld_pin_control = 0x00; //dld +        _ad9522_regs.refmon_pin_control = 0x12; //show ref2 +        _ad9522_regs.lock_detect_counter = ad9522_regs_t::LOCK_DETECT_COUNTER_16CYC; + +        this->use_internal_ref(); + +        this->set_fpga_clock_rate(master_clock_rate); //initialize to something + +        this->enable_fpga_clock(true); +        this->enable_test_clock(ENABLE_THE_TEST_OUT); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +    } + +    ~b100_clock_ctrl_impl(void){ +        UHD_SAFE_CALL( +            this->enable_test_clock(ENABLE_THE_TEST_OUT); +            this->enable_rx_dboard_clock(false); +            this->enable_tx_dboard_clock(false); +            //this->enable_fpga_clock(false); //FIXME +        ) +    } + +    /*********************************************************************** +     * Clock rate control: +     *  - set clock rate w/ internal VCO +     *  - set clock rate w/ external VCXO +     **********************************************************************/ +    void set_clock_settings_with_internal_vco(double rate){ +        const clock_settings_type cs = get_clock_settings(rate); + +        //set the rates to private variables so the implementation knows! +        _chan_rate = cs.get_chan_rate(); +        _out_rate = cs.get_out_rate(); + +        _ad9522_regs.enable_clock_doubler = (cs.ref_clock_doubler == 2)? 1 : 0; + +        _ad9522_regs.set_r_counter(cs.r_counter); +        _ad9522_regs.a_counter = cs.a_counter; +        _ad9522_regs.set_b_counter(cs.b_counter); +        UHD_ASSERT_THROW(cs.prescaler == 8); //assumes this below: +        _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV8_9; + +        _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + +        _ad9522_regs.bypass_vco_divider = 0; +        switch(cs.vco_divider){ +        case 1: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV1; break; +        case 2: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV2; break; +        case 3: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV3; break; +        case 4: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV4; break; +        case 5: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV5; break; +        case 6: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV6; break; +        } +        _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_VCO; + +        //setup fpga master clock +        _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; +        set_clock_divider(cs.chan_divider, +            _ad9522_regs.divider0_low_cycles, +            _ad9522_regs.divider0_high_cycles, +            _ad9522_regs.divider0_bypass +        ); + +        //setup codec clock +        _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; +        set_clock_divider(cs.chan_divider, +            _ad9522_regs.divider1_low_cycles, +            _ad9522_regs.divider1_high_cycles, +            _ad9522_regs.divider1_bypass +        ); + +        this->send_all_regs(); +        calibrate_now(); +    } + +    void set_clock_settings_with_external_vcxo(double rate){ +        //set the rates to private variables so the implementation knows! +        _chan_rate = rate; +        _out_rate = rate; + +        _ad9522_regs.enable_clock_doubler = 1; //doubler always on +        const double ref_rate = REFERENCE_INPUT_RATE*2; + +        //bypass prescaler such that N = B +        long gcd = boost::math::gcd(long(ref_rate), long(rate)); +        _ad9522_regs.set_r_counter(int(ref_rate/gcd)); +        _ad9522_regs.a_counter = 0; +        _ad9522_regs.set_b_counter(int(rate/gcd)); +        _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV1; + +        //setup external vcxo +        _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; +        _ad9522_regs.bypass_vco_divider = 1; +        _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_EXTERNAL; + +        //setup fpga master clock +        _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; +        _ad9522_regs.divider0_bypass = 1; + +        //setup codec clock +        _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; +        _ad9522_regs.divider1_bypass = 1; + +        this->send_all_regs(); +    } + +    void set_fpga_clock_rate(double rate){ +        if (_out_rate == rate) return; +        if (rate == 61.44e6) set_clock_settings_with_external_vcxo(rate); +        else                 set_clock_settings_with_internal_vco(rate); +        //clock rate changed! update dboard clocks and FPGA ticks per second +        set_rx_dboard_clock_rate(rate); +        set_tx_dboard_clock_rate(rate); +        _iface->poke32(B100_REG_TIME64_TPS, boost::uint32_t(get_fpga_clock_rate())); +    } + +    double get_fpga_clock_rate(void){ +        return this->_out_rate; +    } + +    /*********************************************************************** +     * FPGA clock enable +     **********************************************************************/ +    void enable_fpga_clock(bool enb){ +        _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; +        _ad9522_regs.out0_lvds_power_down = !enb; +        this->send_reg(0x0F0); +        this->latch_regs(); +    } + +    /*********************************************************************** +     * Special test clock output +     **********************************************************************/ +    void enable_test_clock(bool enb){ +        //setup test clock (same divider as codec clock) +        _ad9522_regs.out4_format = ad9522_regs_t::OUT4_FORMAT_CMOS; +        _ad9522_regs.out4_cmos_configuration = (enb)? +            ad9522_regs_t::OUT4_CMOS_CONFIGURATION_A_ON : +            ad9522_regs_t::OUT4_CMOS_CONFIGURATION_OFF; +        this->send_reg(0x0F4); +        this->latch_regs(); +    } + +    /*********************************************************************** +     * RX Dboard Clock Control (output 9, divider 3) +     **********************************************************************/ +    void enable_rx_dboard_clock(bool enb){ +        _ad9522_regs.out9_format = ad9522_regs_t::OUT9_FORMAT_CMOS; +        _ad9522_regs.out9_cmos_configuration = (enb)? +            ad9522_regs_t::OUT9_CMOS_CONFIGURATION_B_ON : +            ad9522_regs_t::OUT9_CMOS_CONFIGURATION_OFF; +        this->send_reg(0x0F9); +        this->latch_regs(); +    } + +    std::vector<double> get_rx_dboard_clock_rates(void){ +        std::vector<double> rates; +        for(size_t div = 1; div <= 16+16; div++) +            rates.push_back(this->_chan_rate/div); +        return rates; +    } + +    void set_rx_dboard_clock_rate(double rate){ +        assert_has(get_rx_dboard_clock_rates(), rate, "rx dboard clock rate"); +        _rx_clock_rate = rate; +        size_t divider = size_t(this->_chan_rate/rate); +        //set the divider registers +        set_clock_divider(divider, +            _ad9522_regs.divider3_low_cycles, +            _ad9522_regs.divider3_high_cycles, +            _ad9522_regs.divider3_bypass +        ); +        this->send_reg(0x199); +        this->send_reg(0x19a); +        this->soft_sync(); +    } + +    double get_rx_clock_rate(void){ +        return _rx_clock_rate; +    } + +    /*********************************************************************** +     * TX Dboard Clock Control (output 6, divider 2) +     **********************************************************************/ +    void enable_tx_dboard_clock(bool enb){ +        _ad9522_regs.out6_format = ad9522_regs_t::OUT6_FORMAT_CMOS; +        _ad9522_regs.out6_cmos_configuration = (enb)? +            ad9522_regs_t::OUT6_CMOS_CONFIGURATION_B_ON : +            ad9522_regs_t::OUT6_CMOS_CONFIGURATION_OFF; +        this->send_reg(0x0F6); +        this->latch_regs(); +    } + +    std::vector<double> get_tx_dboard_clock_rates(void){ +        return get_rx_dboard_clock_rates(); //same master clock, same dividers... +    } + +    void set_tx_dboard_clock_rate(double rate){ +        assert_has(get_tx_dboard_clock_rates(), rate, "tx dboard clock rate"); +        _tx_clock_rate = rate; +        size_t divider = size_t(this->_chan_rate/rate); +        //set the divider registers +        set_clock_divider(divider, +            _ad9522_regs.divider2_low_cycles, +            _ad9522_regs.divider2_high_cycles, +            _ad9522_regs.divider2_bypass +        ); +        this->send_reg(0x196); +        this->send_reg(0x197); +        this->soft_sync(); +    } + +    double get_tx_clock_rate(void){ +        return _tx_clock_rate; +    } + +    /*********************************************************************** +     * Clock reference control +     **********************************************************************/ +    void use_internal_ref(void) { +        _ad9522_regs.enable_ref2 = 1; +        _ad9522_regs.enable_ref1 = 0; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF2; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; +        this->send_reg(0x01C); +        this->latch_regs(); +    } + +    void use_external_ref(void) { +        _ad9522_regs.enable_ref2 = 0; +        _ad9522_regs.enable_ref1 = 1; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_MANUAL; +        this->send_reg(0x01C); +        this->latch_regs(); +    } + +    void use_auto_ref(void) { +        _ad9522_regs.enable_ref2 = 1; +        _ad9522_regs.enable_ref1 = 1; +        _ad9522_regs.select_ref = ad9522_regs_t::SELECT_REF_REF1; +        _ad9522_regs.enb_auto_ref_switchover = ad9522_regs_t::ENB_AUTO_REF_SWITCHOVER_AUTO; +        this->send_reg(0x01C); +        this->latch_regs(); +    } + +private: +    b100_iface::sptr _iface; +    ad9522_regs_t _ad9522_regs; +    double _out_rate; //rate at the fpga and codec +    double _chan_rate; //rate before final dividers +    double _rx_clock_rate, _tx_clock_rate; + +    void latch_regs(void){ +        _ad9522_regs.io_update = 1; +        this->send_reg(0x232); +    } + +    void send_reg(boost::uint16_t addr){ +        boost::uint32_t reg = _ad9522_regs.get_write_reg(addr); +        UHD_LOGV(often) << "clock control write reg: " << std::hex << reg << std::endl; +        byte_vector_t buf; +        buf.push_back(boost::uint8_t(reg >> 16)); +        buf.push_back(boost::uint8_t(reg >> 8)); +        buf.push_back(boost::uint8_t(reg & 0xff)); + +        _iface->get_fx2_i2c_iface().write_i2c(0x5C, buf); +    } + +    boost::uint8_t read_reg(boost::uint16_t addr){ +        byte_vector_t buf; +        buf.push_back(boost::uint8_t(addr >> 8)); +        buf.push_back(boost::uint8_t(addr & 0xff)); +        _iface->get_fx2_i2c_iface().write_i2c(0x5C, buf); + +        buf = _iface->get_fx2_i2c_iface().read_i2c(0x5C, 1); + +        return boost::uint32_t(buf[0] & 0xFF); +    } + +    void calibrate_now(void){ +        //vco calibration routine: +        _ad9522_regs.vco_calibration_now = 0; +        this->send_reg(0x18); +        this->latch_regs(); +        _ad9522_regs.vco_calibration_now = 1; +        this->send_reg(0x18); +        this->latch_regs(); +        //wait for calibration done: +        static const boost::uint8_t addr = 0x01F; +        for (size_t ms10 = 0; ms10 < 100; ms10++){ +            boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +            boost::uint32_t reg = read_reg(addr); +            _ad9522_regs.set_reg(addr, reg); +            if (_ad9522_regs.vco_calibration_finished) goto wait_for_ld; +        } +        UHD_MSG(error) << "USRP-B100 clock control: VCO calibration timeout" << std::endl; +        wait_for_ld: +        //wait for digital lock detect: +        for (size_t ms10 = 0; ms10 < 100; ms10++){ +            boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +            boost::uint32_t reg = read_reg(addr); +            _ad9522_regs.set_reg(addr, reg); +            if (_ad9522_regs.digital_lock_detect) return; +        } +        UHD_MSG(error) << "USRP-B100 clock control: lock detection timeout" << std::endl; +    } + +    void soft_sync(void){ +        _ad9522_regs.soft_sync = 1; +        this->send_reg(0x230); +        this->latch_regs(); +        _ad9522_regs.soft_sync = 0; +        this->send_reg(0x230); +        this->latch_regs(); +    } + +    void send_all_regs(void){ +        //setup a list of register ranges to write +        typedef std::pair<boost::uint16_t, boost::uint16_t> range_t; +        static const std::vector<range_t> ranges = boost::assign::list_of +            (range_t(0x000, 0x000)) (range_t(0x010, 0x01F)) +            (range_t(0x0F0, 0x0FD)) (range_t(0x190, 0x19B)) +            (range_t(0x1E0, 0x1E1)) (range_t(0x230, 0x230)) +        ; + +        //write initial register values and latch/update +        BOOST_FOREACH(const range_t &range, ranges){ +            for(boost::uint16_t addr = range.first; addr <= range.second; addr++){ +                this->send_reg(addr); +            } +        } +        this->latch_regs(); +    } +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +b100_clock_ctrl::sptr b100_clock_ctrl::make(b100_iface::sptr iface, double master_clock_rate){ +    return sptr(new b100_clock_ctrl_impl(iface, master_clock_rate)); +} diff --git a/host/lib/usrp/usrp_b100/clock_ctrl.hpp b/host/lib/usrp/usrp_b100/clock_ctrl.hpp new file mode 100644 index 000000000..2a2e74024 --- /dev/null +++ b/host/lib/usrp/usrp_b100/clock_ctrl.hpp @@ -0,0 +1,118 @@ +// +// 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_B100_CLOCK_CTRL_HPP +#define INCLUDED_B100_CLOCK_CTRL_HPP + +#include "b100_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 b100_clock_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<b100_clock_ctrl> sptr; + +    /*! +     * Make a new clock control object. +     * \param iface the b100 iface object +     * \param master_clock_rate the master FPGA/sample clock rate +     * \return the clock control object +     */ +    static sptr make(b100_iface::sptr iface, double master_clock_rate); + +    /*! +     * Set the rate of the fpga clock line. +     * Throws if rate is not valid. +     * \param rate the new rate in Hz +     */ +    virtual void set_fpga_clock_rate(double rate) = 0; + +    /*! +     * Get the rate of the fpga clock line. +     * \return the fpga clock rate in Hz +     */ +    virtual double get_fpga_clock_rate(void) = 0; + +    /*! +     * Get the possible rates of the rx dboard clock. +     * \return a vector of clock rates in Hz +     */ +    virtual std::vector<double> get_rx_dboard_clock_rates(void) = 0; + +    /*! +     * Get the possible rates of the tx dboard clock. +     * \return a vector of clock rates in Hz +     */ +    virtual std::vector<double> get_tx_dboard_clock_rates(void) = 0; + +    /*! +     * Set the rx dboard clock rate to a possible rate. +     * \param rate the new clock rate in Hz +     * \throw exception when rate cannot be achieved +     */ +    virtual void set_rx_dboard_clock_rate(double rate) = 0; + +    /*! +     * Set the tx dboard clock rate to a possible rate. +     * \param rate the new clock rate in Hz +     * \throw exception when rate cannot be achieved +     */ +    virtual void set_tx_dboard_clock_rate(double rate) = 0; +     +    /*! +     * Enable/disable the FPGA clock. +     * \param enb true to enable +     */ +     +    virtual void enable_fpga_clock(bool enb) = 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_B100_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp_b100/codec_ctrl.cpp b/host/lib/usrp/usrp_b100/codec_ctrl.cpp new file mode 100644 index 000000000..4d118b68b --- /dev/null +++ b/host/lib/usrp/usrp_b100/codec_ctrl.cpp @@ -0,0 +1,283 @@ +// +// 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/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include "b100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> + +using namespace uhd; + +const gain_range_t b100_codec_ctrl::tx_pga_gain_range(-20, 0, double(0.1)); +const gain_range_t b100_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class b100_codec_ctrl_impl : public b100_codec_ctrl{ +public: +    //structors +    b100_codec_ctrl_impl(b100_iface::sptr iface); +    ~b100_codec_ctrl_impl(void); + +    //aux adc and dac control +    double read_aux_adc(aux_adc_t which); +    void write_aux_dac(aux_dac_t which, double volts); + +    //pga gain control +    void set_tx_pga_gain(double); +    double get_tx_pga_gain(void); +    void set_rx_pga_gain(double, char); +    double get_rx_pga_gain(char); + +private: +    b100_iface::sptr _iface; +    ad9862_regs_t _ad9862_regs; +    void send_reg(boost::uint8_t addr); +    void recv_reg(boost::uint8_t addr); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +b100_codec_ctrl_impl::b100_codec_ctrl_impl(b100_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.mux_out = ad9862_regs_t::MUX_OUT_RX_MUX_MODE; //B100 uses interleaved RX->FPGA +    _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); +    } + +    //always start conversions for aux ADC +    _ad9862_regs.start_a = 1; +    _ad9862_regs.start_b = 1; + +    //aux adc clock +    _ad9862_regs.clk_4 = ad9862_regs_t::CLK_4_1_4; +    this->send_reg(34); +} + +b100_codec_ctrl_impl::~b100_codec_ctrl_impl(void){ +    UHD_SAFE_CALL( +        //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 b100_codec_ctrl_impl::set_tx_pga_gain(double gain){ +    int gain_word = int(mtpgw*(gain - tx_pga_gain_range.start())/(tx_pga_gain_range.stop() - tx_pga_gain_range.start())); +    _ad9862_regs.tx_pga_gain = uhd::clip(gain_word, 0, mtpgw); +    this->send_reg(16); +} + +double b100_codec_ctrl_impl::get_tx_pga_gain(void){ +    return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.stop() - tx_pga_gain_range.start())/mtpgw) + tx_pga_gain_range.start(); +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void b100_codec_ctrl_impl::set_rx_pga_gain(double gain, char which){ +    int gain_word = int(mrpgw*(gain - rx_pga_gain_range.start())/(rx_pga_gain_range.stop() - rx_pga_gain_range.start())); +    gain_word = uhd::clip(gain_word, 0, mrpgw); +    switch(which){ +    case 'A': +        _ad9862_regs.rx_pga_a = gain_word; +        this->send_reg(2); +        return; +    case 'B': +        _ad9862_regs.rx_pga_b = gain_word; +        this->send_reg(3); +        return; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +double b100_codec_ctrl_impl::get_rx_pga_gain(char which){ +    int gain_word; +    switch(which){ +    case 'A': gain_word = _ad9862_regs.rx_pga_a; break; +    case 'B': gain_word = _ad9862_regs.rx_pga_b; break; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +    return (gain_word*(rx_pga_gain_range.stop() - rx_pga_gain_range.start())/mrpgw) + rx_pga_gain_range.start(); +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static double aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low){ +    return double((boost::uint16_t(high) << 2) | low)*3.3/0x3ff; +} + +double b100_codec_ctrl_impl::read_aux_adc(aux_adc_t which){ +    switch(which){ + +    case AUX_ADC_A1: +        _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC1; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(28); //read the value (2 bytes, 2 reads) +        this->recv_reg(29); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); +    case AUX_ADC_A2: +        _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC2; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(26); //read the value (2 bytes, 2 reads) +        this->recv_reg(27); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); + +    case AUX_ADC_B1: +        _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC1; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(32); //read the value (2 bytes, 2 reads) +        this->recv_reg(33); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); +    case AUX_ADC_B2: +        _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC2; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(30); //read the value (2 bytes, 2 reads) +        this->recv_reg(31); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void b100_codec_ctrl_impl::write_aux_dac(aux_dac_t which, double volts){ +    //special case for aux dac d (aka sigma delta word) +    if (which == AUX_DAC_D){ +        boost::uint16_t dac_word = uhd::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); +        _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); +        _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); +        this->send_reg(42); +        this->send_reg(43); +        return; +    } + +    //calculate the dac word for aux dac a, b, c +    boost::uint8_t dac_word = uhd::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + +    //setup a lookup table for the aux dac params (reg ref, reg addr) +    typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; +    uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of +        (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) +        (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) +        (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) +    ; + +    //set the aux dac register +    UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); +    boost::uint8_t *reg_ref, reg_addr; +    boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; +    *reg_ref = dac_word; +    this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void b100_codec_ctrl_impl::send_reg(boost::uint8_t addr){ +    boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); +    UHD_LOGV(rarely) << "codec control write reg: " << std::hex << reg << std::endl; +    _iface->transact_spi( +        B100_SPI_SS_AD9862, +        spi_config_t::EDGE_RISE, +        reg, 16, false /*no rb*/ +    ); +} + +void b100_codec_ctrl_impl::recv_reg(boost::uint8_t addr){ +    boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); +    UHD_LOGV(rarely) << "codec control read reg: " << std::hex << reg << std::endl; +    boost::uint32_t ret = _iface->transact_spi( +        B100_SPI_SS_AD9862, +        spi_config_t::EDGE_RISE, +        reg, 16, true /*rb*/ +    ); +    UHD_LOGV(rarely) << "codec control read ret: " << std::hex << boost::uint16_t(ret & 0xFF) << std::endl; +    _ad9862_regs.set_reg(addr, boost::uint8_t(ret&0xff)); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +b100_codec_ctrl::sptr b100_codec_ctrl::make(b100_iface::sptr iface){ +    return sptr(new b100_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp_b100/codec_ctrl.hpp b/host/lib/usrp/usrp_b100/codec_ctrl.hpp new file mode 100644 index 000000000..1bd579190 --- /dev/null +++ b/host/lib/usrp/usrp_b100/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_B100_CODEC_CTRL_HPP +#define INCLUDED_B100_CODEC_CTRL_HPP + +#include "b100_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 b100_codec_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<b100_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_e iface object +     * \return the codec control object +     */ +    static sptr make(b100_iface::sptr iface); + +    //! aux adc identifier constants +    enum aux_adc_t{ +        AUX_ADC_A2 = 0xA2, +        AUX_ADC_A1 = 0xA1, +        AUX_ADC_B2 = 0xB2, +        AUX_ADC_B1 = 0xB1 +    }; + +    /*! +     * Read an auxiliary adc: +     * The internals remember which aux adc was read last. +     * Therefore, the aux adc switch is only changed as needed. +     * \param which which of the 4 adcs +     * \return a value in volts +     */ +    virtual double read_aux_adc(aux_adc_t which) = 0; + +    //! aux dac identifier constants +    enum aux_dac_t{ +        AUX_DAC_A = 0xA, +        AUX_DAC_B = 0xB, +        AUX_DAC_C = 0xC, +        AUX_DAC_D = 0xD //really the sigma delta output +    }; + +    /*! +     * Write an auxiliary dac. +     * \param which which of the 4 dacs +     * \param volts the level in in volts +     */ +    virtual void write_aux_dac(aux_dac_t which, double volts) = 0; + +    //! Set the TX PGA gain +    virtual void set_tx_pga_gain(double gain) = 0; + +    //! Get the TX PGA gain +    virtual double get_tx_pga_gain(void) = 0; + +    //! Set the RX PGA gain ('A' or 'B') +    virtual void set_rx_pga_gain(double gain, char which) = 0; + +    //! Get the RX PGA gain ('A' or 'B') +    virtual double get_rx_pga_gain(char which) = 0; +}; + +#endif /* INCLUDED_B100_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp_b100/codec_impl.cpp b/host/lib/usrp/usrp_b100/codec_impl.cpp new file mode 100644 index 000000000..de3ca3a66 --- /dev/null +++ b/host/lib/usrp/usrp_b100/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 "b100_impl.hpp" +#include <uhd/exception.hpp> +#include <uhd/usrp/codec_props.hpp> +#include <boost/bind.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Helper Methods + **********************************************************************/ +void b100_impl::codec_init(void){ +    //make proxies +    _rx_codec_proxy = wax_obj_proxy::make( +        boost::bind(&b100_impl::rx_codec_get, this, _1, _2), +        boost::bind(&b100_impl::rx_codec_set, this, _1, _2) +    ); +    _tx_codec_proxy = wax_obj_proxy::make( +        boost::bind(&b100_impl::tx_codec_get, this, _1, _2), +        boost::bind(&b100_impl::tx_codec_set, this, _1, _2) +    ); +} + +/*********************************************************************** + * RX Codec Properties + **********************************************************************/ +static const std::string ad9862_pga_gain_name = "ad9862 pga"; + +void b100_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("b100 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 = b100_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 b100_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<double>(), 'A'); +        return; + +    case CODEC_PROP_GAIN_Q: +        UHD_ASSERT_THROW(key.name == ad9862_pga_gain_name); +        _codec_ctrl->set_rx_pga_gain(val.as<double>(), 'B'); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Codec Properties + **********************************************************************/ +void b100_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("b100 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 = b100_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 b100_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<double>()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp_b100/ctrl_packet.hpp b/host/lib/usrp/usrp_b100/ctrl_packet.hpp new file mode 100644 index 000000000..f504fc5aa --- /dev/null +++ b/host/lib/usrp/usrp_b100/ctrl_packet.hpp @@ -0,0 +1,75 @@ +// +// 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_CTRL_PACKET_HPP +#define INCLUDED_CTRL_PACKET_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <uhd/types/serial.hpp> + +typedef std::vector<boost::uint16_t> ctrl_data_t; + +/*! + * Control packet operation type + */ +enum ctrl_pkt_op_t {  +    CTRL_PKT_OP_WRITE = 1, +    CTRL_PKT_OP_READ = 2, +    CTRL_PKT_OP_READBACK = 3 +}; + +/*! + * Control packet transaction length + */ +const size_t CTRL_PACKET_LENGTH = 32; +const size_t CTRL_PACKET_HEADER_LENGTH = 8; +const size_t CTRL_PACKET_DATA_LENGTH = 24; //=length-header + +/*! + * Control packet header magic value + */ +const boost::uint8_t CTRL_PACKET_HEADER_MAGIC = 0xAA; + +/*!  + * Callback triggers for readback operation + */ +//FIXME: these are not real numbers, callbacks aren't implemented yet +const boost::uint16_t CTRL_PACKET_CALLBACK_SPI = 0x0001; +const boost::uint16_t CTRL_PACKET_CALLBACK_I2C = 0x0002; +//and so on + +/*! + * Metadata structure to describe a control packet + */ +struct UHD_API ctrl_pkt_meta_t { +    ctrl_pkt_op_t op; +    boost::uint8_t callbacks; +    boost::uint8_t seq; +    boost::uint16_t len; +    boost::uint32_t addr; +}; + +/*!  + * Full control packet structure + */ +struct UHD_API ctrl_pkt_t { +    ctrl_pkt_meta_t pkt_meta; +    ctrl_data_t data; +}; + +#endif /* INCLUDED_CTRL_PACKET_HPP */ diff --git a/host/lib/usrp/usrp_b100/dboard_iface.cpp b/host/lib/usrp/usrp_b100/dboard_iface.cpp new file mode 100644 index 000000000..ec3da6220 --- /dev/null +++ b/host/lib/usrp/usrp_b100/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 "b100_iface.hpp" +#include "b100_regs.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> + + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class b100_dboard_iface : public dboard_iface{ +public: + +    b100_dboard_iface( +        b100_iface::sptr iface, +        b100_clock_ctrl::sptr clock, +        b100_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(B100_REG_GPIO_RX_DBG, 0); +        _iface->poke16(B100_REG_GPIO_TX_DBG, 0); +    } + +    ~b100_dboard_iface(void){ +        /* NOP */ +    } + +    special_props_t get_special_props(void){ +        special_props_t props; +        props.soft_clock_divider = false; +        props.mangle_i2c_addrs = false; +        return props; +    } + +    void write_aux_dac(unit_t, aux_dac_t, double); +    double read_aux_adc(unit_t, aux_adc_t); + +    void _set_pin_ctrl(unit_t, boost::uint16_t); +    void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); +    void _set_gpio_ddr(unit_t, boost::uint16_t); +    void _set_gpio_out(unit_t, boost::uint16_t); +    void set_gpio_debug(unit_t, int); +    boost::uint16_t read_gpio(unit_t); + +    void write_i2c(boost::uint8_t, const byte_vector_t &); +    byte_vector_t read_i2c(boost::uint8_t, size_t); + +    void write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    boost::uint32_t read_write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    void set_clock_rate(unit_t, double); +    std::vector<double> get_clock_rates(unit_t); +    double get_clock_rate(unit_t); +    void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t); + +private: +    b100_iface::sptr _iface; +    b100_clock_ctrl::sptr _clock; +    b100_codec_ctrl::sptr _codec; +    uhd::dict<unit_t, double> _clock_rates; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_b100_dboard_iface( +    b100_iface::sptr iface, +    b100_clock_ctrl::sptr clock, +    b100_codec_ctrl::sptr codec +){ +    return dboard_iface::sptr(new b100_dboard_iface(iface, clock, codec)); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +void b100_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> b100_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 b100_dboard_iface::get_clock_rate(unit_t unit){ +    return _clock_rates[unit]; +} + +void b100_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 b100_dboard_iface::get_codec_rate(unit_t){ +    return _clock->get_fpga_clock_rate(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void b100_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(B100_REG_GPIO_RX_SEL, value); return; +    case UNIT_TX: _iface->poke16(B100_REG_GPIO_TX_SEL, value); return; +    } +} + +void b100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ +    switch(unit){ +    case UNIT_RX: _iface->poke16(B100_REG_GPIO_RX_DDR, value); return; +    case UNIT_TX: _iface->poke16(B100_REG_GPIO_TX_DDR, value); return; +    } +} + +void b100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ +    switch(unit){ +    case UNIT_RX: _iface->poke16(B100_REG_GPIO_RX_IO, value); return; +    case UNIT_TX: _iface->poke16(B100_REG_GPIO_TX_IO, value); return; +    } +} + +boost::uint16_t b100_dboard_iface::read_gpio(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _iface->peek16(B100_REG_GPIO_RX_IO); +    case UNIT_TX: return _iface->peek16(B100_REG_GPIO_TX_IO); +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +void b100_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,        B100_REG_ATR_IDLE_RXSIDE) +            (ATR_REG_TX_ONLY,     B100_REG_ATR_INTX_RXSIDE) +            (ATR_REG_RX_ONLY,     B100_REG_ATR_INRX_RXSIDE) +            (ATR_REG_FULL_DUPLEX, B100_REG_ATR_FULL_RXSIDE) +        ) +        (UNIT_TX, map_list_of +            (ATR_REG_IDLE,        B100_REG_ATR_IDLE_TXSIDE) +            (ATR_REG_TX_ONLY,     B100_REG_ATR_INTX_TXSIDE) +            (ATR_REG_RX_ONLY,     B100_REG_ATR_INRX_TXSIDE) +            (ATR_REG_FULL_DUPLEX, B100_REG_ATR_FULL_TXSIDE) +        ) +    ; +    _iface->poke16(unit_to_atr_to_addr[unit][atr], value); +} + +void b100_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(B100_REG_GPIO_RX_DBG, 0xffff); +        _iface->poke16(B100_REG_GPIO_RX_SEL, dbg_sels); +        return; + +    case UNIT_TX: +        _iface->poke16(B100_REG_GPIO_TX_DBG, 0xffff); +        _iface->poke16(B100_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 B100_SPI_SS_TX_DB; +    case dboard_iface::UNIT_RX: return B100_SPI_SS_RX_DB; +    } +    throw std::invalid_argument("unknown unit type"); +} + +void b100_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 b100_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 b100_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +    return _iface->write_i2c(addr, bytes); +} + +byte_vector_t b100_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ +    return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void b100_dboard_iface::write_aux_dac(dboard_iface::unit_t, aux_dac_t which, double value){ +    //same aux dacs for each unit +    static const uhd::dict<aux_dac_t, b100_codec_ctrl::aux_dac_t> which_to_aux_dac = map_list_of +        (AUX_DAC_A, b100_codec_ctrl::AUX_DAC_A) +        (AUX_DAC_B, b100_codec_ctrl::AUX_DAC_B) +        (AUX_DAC_C, b100_codec_ctrl::AUX_DAC_C) +        (AUX_DAC_D, b100_codec_ctrl::AUX_DAC_D) +    ; +    _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +double b100_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, b100_codec_ctrl::aux_adc_t> +    > unit_to_which_to_aux_adc = map_list_of +        (UNIT_RX, map_list_of +            (AUX_ADC_A, b100_codec_ctrl::AUX_ADC_A1) +            (AUX_ADC_B, b100_codec_ctrl::AUX_ADC_B1) +        ) +        (UNIT_TX, map_list_of +            (AUX_ADC_A, b100_codec_ctrl::AUX_ADC_A2) +            (AUX_ADC_B, b100_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_b100/dboard_impl.cpp b/host/lib/usrp/usrp_b100/dboard_impl.cpp new file mode 100644 index 000000000..ba3776728 --- /dev/null +++ b/host/lib/usrp/usrp_b100/dboard_impl.cpp @@ -0,0 +1,185 @@ +// +// 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 "b100_impl.hpp" +#include "b100_regs.hpp" +#include <uhd/exception.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/misc_utils.hpp> +#include <boost/bind.hpp> +#include "usrp_i2c_addr.h" + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Dboard Initialization + **********************************************************************/ +void b100_impl::dboard_init(void){ +    //read the tx and rx dboard eeproms +    _rx_db_eeprom.load(*_iface, I2C_ADDR_RX_A); +    _tx_db_eeprom.load(*_iface, I2C_ADDR_TX_A); +    _gdb_eeprom.load(*_iface, I2C_ADDR_TX_A ^ 5); + +    //create a new dboard interface and manager +    _dboard_iface = make_b100_dboard_iface( +        _iface, _clock_ctrl, _codec_ctrl +    ); +    _dboard_manager = dboard_manager::make( +        _rx_db_eeprom.id, +        ((_gdb_eeprom.id == dboard_id_t::none())? _tx_db_eeprom : _gdb_eeprom).id, +        _dboard_iface +    ); + +    //setup the dboard proxies +    _rx_dboard_proxy = wax_obj_proxy::make( +        boost::bind(&b100_impl::rx_dboard_get, this, _1, _2), +        boost::bind(&b100_impl::rx_dboard_set, this, _1, _2) +    ); +    _tx_dboard_proxy = wax_obj_proxy::make( +        boost::bind(&b100_impl::tx_dboard_get, this, _1, _2), +        boost::bind(&b100_impl::tx_dboard_set, this, _1, _2) +    ); +} + +/*********************************************************************** + * RX Dboard Get + **********************************************************************/ +void b100_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("b100 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_EEPROM: +        val = _rx_db_eeprom; +        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 b100_impl::rx_dboard_set(const wax::obj &key, const wax::obj &val){ +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_DBOARD_EEPROM: +        _rx_db_eeprom = val.as<dboard_eeprom_t>(); +        _rx_db_eeprom.store(*_iface, I2C_ADDR_RX_A); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Dboard Get + **********************************************************************/ +void b100_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("b100 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_EEPROM: +        val = _tx_db_eeprom; +        return; + +    case DBOARD_PROP_GBOARD_EEPROM: +        val = _gdb_eeprom; +        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 b100_impl::tx_dboard_set(const wax::obj &key, const wax::obj &val){ +    switch(key.as<dboard_prop_t>()){ +    case DBOARD_PROP_DBOARD_EEPROM: +        _tx_db_eeprom = val.as<dboard_eeprom_t>(); +        _tx_db_eeprom.store(*_iface, I2C_ADDR_TX_A); +        return; + +    case DBOARD_PROP_GBOARD_EEPROM: +        _gdb_eeprom = val.as<dboard_eeprom_t>(); +        _gdb_eeprom.store(*_iface, I2C_ADDR_TX_A ^ 5); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp_b100/dsp_impl.cpp b/host/lib/usrp/usrp_b100/dsp_impl.cpp new file mode 100644 index 000000000..c1bf6bedd --- /dev/null +++ b/host/lib/usrp/usrp_b100/dsp_impl.cpp @@ -0,0 +1,189 @@ +// +// 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 "b100_impl.hpp" +#include "b100_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; + +static const double MASTER_CLOCK_RATE = 64e6; //TODO get from clock control + +/*********************************************************************** + * RX DDC Initialization + **********************************************************************/ +void b100_impl::rx_ddc_init(void){ +    _rx_ddc_proxy = wax_obj_proxy::make( +        boost::bind(&b100_impl::rx_ddc_get, this, _1, _2), +        boost::bind(&b100_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(16e6)); +} + +/*********************************************************************** + * RX DDC Get + **********************************************************************/ +void b100_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-B100 RX DSP"); +        return; + +    case DSP_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case DSP_PROP_FREQ_SHIFT: +        val = _ddc_freq; +        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 b100_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_STREAM_CMD: +        issue_stream_cmd(val.as<stream_cmd_t>()); +        return; + +    case DSP_PROP_FREQ_SHIFT:{ +            double new_freq = val.as<double>(); +            _iface->poke32(B100_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(B100_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(B100_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 b100_impl::tx_duc_init(void){ +    _tx_duc_proxy = wax_obj_proxy::make( +        boost::bind(&b100_impl::tx_duc_get, this, _1, _2), +        boost::bind(&b100_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(16e6)); +} + +/*********************************************************************** + * TX DUC Get + **********************************************************************/ +void b100_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-B100 TX DSP"); +        return; + +    case DSP_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case DSP_PROP_FREQ_SHIFT: +        val = _duc_freq; +        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 b100_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(B100_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(B100_REG_DSP_TX_INTERP_RATE, dsp_type1::calc_cic_filter_word(_duc_interp)); + +            //set the scaling +            _iface->poke32(B100_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_b100/io_impl.cpp b/host/lib/usrp/usrp_b100/io_impl.cpp new file mode 100644 index 000000000..3978bea75 --- /dev/null +++ b/host/lib/usrp/usrp_b100/io_impl.cpp @@ -0,0 +1,210 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "usrp_commands.h" +#include "b100_impl.hpp" +#include "b100_regs.hpp" +#include <uhd/usrp/dsp_utils.hpp> +#include <uhd/usrp/dsp_props.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; + +/*********************************************************************** + * IO Implementation Details + **********************************************************************/ +struct b100_impl::io_impl{ +    io_impl(zero_copy_if::sptr data_transport): +        data_transport(data_transport) +    { +        /* NOP */ +    } + +    ~io_impl(void){ +        //drain the rx buffs +        //while(data_transport->get_recv_buff().get() != NULL){ +                /* NOP */ +        //} +    } + +    zero_copy_if::sptr &data_transport; + +    sph::recv_packet_handler recv_handler; +    sph::send_packet_handler send_handler; +    bool continuous_streaming; +}; + +/*********************************************************************** + * Initialize internals within this file + **********************************************************************/ +void b100_impl::io_init(void){ +    _recv_otw_type.width = 16; +    _recv_otw_type.shift = 0; +    _recv_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + +    _send_otw_type.width = 16; +    _send_otw_type.shift = 0; +    _send_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; +     +    _iface->reset_gpif(6); + +    //reset state machines +    _iface->poke32(B100_REG_CTRL_TX_CLEAR_UNDERRUN, 0); +    _iface->poke32(B100_REG_CTRL_RX_CLEAR_OVERRUN, 0); + +    _io_impl = UHD_PIMPL_MAKE(io_impl, (_data_transport)); +     +    //setup rx data path +    _iface->poke32(B100_REG_CTRL_RX_NSAMPS_PER_PKT, get_max_recv_samps_per_packet()); +    UHD_LOGV(always) << "IO: Using " << get_max_recv_samps_per_packet() << " samples per packet" << std::endl; +    _iface->poke32(B100_REG_CTRL_RX_NCHANNELS, 1); +    _iface->poke32(B100_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(B100_REG_CTRL_RX_VRT_TRAILER, 0); + +    //set the streamid to reset the seq num +    _iface->poke32(B100_REG_CTRL_TX_REPORT_SID, 0); +    //setup the tx policy +    _iface->poke32(B100_REG_CTRL_TX_POLICY, B100_FLAG_CTRL_TX_POLICY_NEXT_PACKET); +     +    //set the expected packet size in USB frames +    _iface->poke32(B100_REG_MISC_RX_LEN, 4); + +    update_transport_channel_mapping(); +} + +void b100_impl::update_transport_channel_mapping(void){ +    if (_io_impl.get() == NULL) return; //not inited yet + +    //set all of the relevant properties on the handler +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.resize(_rx_subdev_spec.size()); +    _io_impl->recv_handler.set_vrt_unpacker(&vrt::if_hdr_unpack_le); +    _io_impl->recv_handler.set_tick_rate(_clock_ctrl->get_fpga_clock_rate()); +    _io_impl->recv_handler.set_samp_rate(_rx_ddc_proxy->get_link()[DSP_PROP_HOST_RATE].as<double>()); +    for (size_t chan = 0; chan < _io_impl->recv_handler.size(); chan++){ +        _io_impl->recv_handler.set_xport_chan_get_buff(chan, boost::bind( +            &uhd::transport::zero_copy_if::get_recv_buff, _io_impl->data_transport, _1 +        )); +        _io_impl->recv_handler.set_overflow_handler(chan, boost::bind( +            &b100_impl::handle_overrun, this, chan +        )); +    } +    _io_impl->recv_handler.set_converter(_recv_otw_type); + +    //set all of the relevant properties on the handler +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.resize(_tx_subdev_spec.size()); +    _io_impl->send_handler.set_vrt_packer(&vrt::if_hdr_pack_le); +    _io_impl->send_handler.set_tick_rate(_clock_ctrl->get_fpga_clock_rate()); +    _io_impl->send_handler.set_samp_rate(_tx_duc_proxy->get_link()[DSP_PROP_HOST_RATE].as<double>()); +    for (size_t chan = 0; chan < _io_impl->send_handler.size(); chan++){ +        _io_impl->send_handler.set_xport_chan_get_buff(chan, boost::bind( +            &uhd::transport::zero_copy_if::get_send_buff, _io_impl->data_transport, _1 +        )); +    } +    _io_impl->send_handler.set_converter(_send_otw_type); +    _io_impl->send_handler.set_max_samples_per_packet(get_max_send_samps_per_packet()); +} + +/*********************************************************************** + * Data send + helper functions + **********************************************************************/ +size_t b100_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 +    ; +    static const size_t bpp = 2048 - hdr_size; +    return bpp / _send_otw_type.get_sample_size(); +} + +size_t b100_impl::send( +    const send_buffs_type &buffs, size_t nsamps_per_buff, +    const tx_metadata_t &metadata, const io_type_t &io_type, +    send_mode_t send_mode, double timeout +){     +    return _io_impl->send_handler.send( +        buffs, nsamps_per_buff, +        metadata, io_type, +        send_mode, timeout +    ); +} + +/*********************************************************************** + * Data recv + helper functions + **********************************************************************/ + +size_t b100_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 = 2048 - hdr_size; //limited by FPGA pkt buffer size +    return bpp/_recv_otw_type.get_sample_size(); +} + +size_t b100_impl::recv( +    const recv_buffs_type &buffs, size_t nsamps_per_buff, +    rx_metadata_t &metadata, const io_type_t &io_type, +    recv_mode_t recv_mode, double timeout +){ +    return _io_impl->recv_handler.recv( +        buffs, nsamps_per_buff, +        metadata, io_type, +        recv_mode, timeout +    ); +} + +void b100_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(B100_REG_CTRL_RX_STREAM_CMD, dsp_type1::calc_stream_cmd_word(stream_cmd)); +    _iface->poke32(B100_REG_CTRL_RX_TIME_SECS,  boost::uint32_t(stream_cmd.time_spec.get_full_secs())); +    _iface->poke32(B100_REG_CTRL_RX_TIME_TICKS, stream_cmd.time_spec.get_tick_count(_clock_ctrl->get_fpga_clock_rate())); +     +    if (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS) { +        while(_io_impl->data_transport->get_recv_buff().get() != NULL){ +            /* NOP */ +        } +    } +} + +void b100_impl::handle_overrun(size_t){ +    if (_io_impl->continuous_streaming){ +        this->issue_stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); +    } +} diff --git a/host/lib/usrp/usrp_b100/mboard_impl.cpp b/host/lib/usrp/usrp_b100/mboard_impl.cpp new file mode 100644 index 000000000..c651ff2a2 --- /dev/null +++ b/host/lib/usrp/usrp_b100/mboard_impl.cpp @@ -0,0 +1,246 @@ +// +// 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 "b100_impl.hpp" +#include "usrp_commands.h" +#include "fpga_regs_standard.h" +#include "fpga_regs_common.h" +#include "b100_regs.hpp" +#include "usrp_i2c_addr.h" +#include <uhd/usrp/misc_utils.hpp> +#include <uhd/usrp/mboard_props.hpp> +#include <uhd/usrp/dboard_props.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/images.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <boost/thread/thread.hpp> +#include <uhd/usrp/dsp_utils.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +static const bool b100_mboard_verbose = true; + +/*********************************************************************** + * Mboard Initialization + **********************************************************************/ +void b100_impl::mboard_init(void) +{ +    _mboard_proxy = wax_obj_proxy::make( +                     boost::bind(&b100_impl::mboard_get, this, _1, _2), +                     boost::bind(&b100_impl::mboard_set, this, _1, _2)); + +    //set the ticks per seconds into the vita time control +    _iface->poke32(B100_REG_TIME64_TPS, +        boost::uint32_t(_clock_ctrl->get_fpga_clock_rate()) +    ); +     +    //init the clock config +    _clock_config = clock_config_t::internal(); +    update_clock_config(); +} + +void b100_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 |= B100_FLAG_TIME64_PPS_POSEDGE; break; +    case clock_config_t::PPS_NEG: pps_flags |= B100_FLAG_TIME64_PPS_NEGEDGE; break; +    default: throw uhd::runtime_error("unhandled clock configuration pps polarity"); +    } + +    //set the pps flags +    _iface->poke32(B100_REG_TIME64_FLAGS, pps_flags); + +    //clock source ref 10mhz +    switch(_clock_config.ref_source){ +    case clock_config_t::REF_AUTO: _clock_ctrl->use_auto_ref(); break; +    case clock_config_t::REF_INT: _clock_ctrl->use_internal_ref(); break; +    case clock_config_t::REF_SMA: _clock_ctrl->use_auto_ref(); break; +    default: throw uhd::runtime_error("unhandled clock configuration ref source"); +    } +} + +/*********************************************************************** + * Mboard Get + **********************************************************************/ +void b100_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-B100 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; + +    case MBOARD_PROP_TIME_NOW:while(true){ +        uint32_t secs = _iface->peek32(B100_REG_RB_TIME_NOW_SECS); +        uint32_t ticks = _iface->peek32(B100_REG_RB_TIME_NOW_TICKS); +        if (secs != _iface->peek32(B100_REG_RB_TIME_NOW_SECS)) continue; +        val = time_spec_t(secs, ticks, _clock_ctrl->get_fpga_clock_rate()); +        return; +    } +     +    case MBOARD_PROP_TIME_PPS: while(true){ +        uint32_t secs = _iface->peek32(B100_REG_RB_TIME_PPS_SECS); +        uint32_t ticks = _iface->peek32(B100_REG_RB_TIME_PPS_TICKS); +        if (secs != _iface->peek32(B100_REG_RB_TIME_PPS_SECS)) continue; +        val = time_spec_t(secs, ticks, _clock_ctrl->get_fpga_clock_rate()); +        return; +    } + +    case MBOARD_PROP_CLOCK_RATE: +        val = _clock_ctrl->get_fpga_clock_rate(); +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +/*********************************************************************** + * Mboard Set + **********************************************************************/ +void b100_impl::mboard_set(const wax::obj &key, const wax::obj &val) +{ +    if(key.type() == typeid(std::string)) { +      if(key.as<std::string>() == "load_eeprom") { +        std::string b100_eeprom_image = val.as<std::string>(); +        UHD_MSG(status) << "B100 EEPROM image: " << b100_eeprom_image << std::endl; +        _fx2_ctrl->usrp_load_eeprom(val.as<std::string>()); +      } +      return; +   	} + +    //handle the get request conditioned on the key +    switch(key.as<mboard_prop_t>()){ +    case MBOARD_PROP_TIME_NOW: +    case MBOARD_PROP_TIME_PPS:{ +            time_spec_t time_spec = val.as<time_spec_t>(); +            _iface->poke32(B100_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(B100_REG_TIME64_IMM, imm_flags); +            _iface->poke32(B100_REG_TIME64_SECS, boost::uint32_t(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()); +        UHD_ASSERT_THROW(_rx_subdev_spec.size() == 1); +        //set the mux +        _iface->poke32(B100_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()); +        UHD_ASSERT_THROW(_tx_subdev_spec.size() == 1); +        //set the mux and set the number of tx channels +        _iface->poke32(B100_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, mboard_eeprom_t::MAP_B000); +        _iface->mb_eeprom = mboard_eeprom_t(*_iface, mboard_eeprom_t::MAP_B000); +        return; + +    case MBOARD_PROP_CLOCK_CONFIG: +        _clock_config = val.as<clock_config_t>(); +        update_clock_config(); +        return; + +    case MBOARD_PROP_CLOCK_RATE: +        UHD_MSG(warning) +            << "You are setting the master clock rate from the API.\n" +            << "You may want to pass this into the device address as master_clock_rate=<rate>.\n" +            << "This way, the clock rate is guaranteed to be initialized first.\n" +            << "See the application notes for USRP-B100 for further instructions.\n" +        ; +        _clock_ctrl->set_fpga_clock_rate(val.as<double>()); +        update_transport_channel_mapping(); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp b/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp index 01a49c129..5b039aafc 100644 --- a/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp +++ b/host/lib/usrp/usrp_e100/usrp_e100_impl.cpp @@ -39,7 +39,7 @@ 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; +    if (hint.has_key("type") and hint["type"] != "e100") return usrp_e100_addrs;      //device node not provided, assume its 0      if (not hint.has_key("node")){ diff --git a/host/utils/usrp1p_gpif_loopback.cpp b/host/utils/usrp1p_gpif_loopback.cpp new file mode 100644 index 000000000..3b9da4304 --- /dev/null +++ b/host/utils/usrp1p_gpif_loopback.cpp @@ -0,0 +1,111 @@ +//USB->GPIF->FPGA loopback test for USRP1P +//uses UHD libusb transport + +#include <uhd/device.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/usb_control.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/shared_array.hpp> +#include <boost/foreach.hpp> +#include <boost/thread.hpp> +#include <boost/format.hpp> +#include <vector> +#include <iostream> +#include <iomanip> + +//so the goal is to open a USB device to endpoints (2,6), submit a buffer, receive a reply, and compare them. +//use usb_zero_copy::make() to get a usb_zero_copy object and then start submitting. +//need to get a usb dev handle to pass to make +//use static std::vector<usb_device_handle::sptr> get_device_list(boost::uint16_t vid, boost::uint16_t pid) to get a device handle +//then get_send_buffer, send, etc. +using namespace uhd; +using namespace uhd::transport; + +const boost::uint16_t data_xfer_size = 32; +const boost::uint16_t ctrl_xfer_size = 32; + +int main(int argc, char *argv[]) { +    std::cout << "USRP1+ GPIF loopback test" << std::endl; +    //step 1: get a handle on it +    std::vector<usb_device_handle::sptr> handles = usb_device_handle::get_device_list(0xfffe, 0x0003); +    if(handles.size() == 0) { +        std::cout << "No USRP1+ found." << std::endl; +        return ~0; +    } +     +    bool verbose = false; +    if(argc > 1) if(std::string(argv[1]) == "-v") verbose = true; +     +    usb_device_handle::sptr handle = handles.front(); + +    usb_zero_copy::sptr data_transport; +    usb_control::sptr ctrl_transport = usb_control::make(handle); //just in case + +    data_transport = usb_zero_copy::make( +                handle,        // identifier +                8,             // IN endpoint +                4,             // OUT endpoint +                uhd::device_addr_t("recv_frame_size=32, num_recv_frames=1, send_frame_size=32, num_send_frames=1") //args +    ); +     +    if(verbose) std::cout << "Made." << std::endl; +     +    //ok now we're made. time to get a buffer and start sending data. +     +    boost::uint8_t localbuf[data_xfer_size]; + +    managed_send_buffer::sptr sbuf; +    managed_recv_buffer::sptr rbuf; +    size_t xfercount = 0; +     +    srand(time(0)); +    while(1) { + +        if(verbose) std::cout << "Getting send buffer." << std::endl; +        sbuf = data_transport->get_send_buff(); +        if(sbuf == 0) { +            std::cout << "Failed to get a send buffer." << std::endl; +            return ~0; +        } +        for(int i = 0; i < data_xfer_size; i++) { +            boost::uint8_t x = rand(); +            sbuf->cast<boost::uint8_t *>()[i] = x; +            localbuf[i] = x; +        } +         +        if(verbose) std::cout << "Buffer loaded" << std::endl; + +        sbuf->commit(data_xfer_size); +        if(verbose) std::cout << "Committed." << std::endl; + +        rbuf = data_transport->get_recv_buff(0.3); //timeout +         +        if(rbuf == 0) { +            std::cout << "Failed to get receive buffer (timeout?)" << std::endl; +            return ~0; +        } +         +        if(verbose) std::cout << "# " << xfercount << std::endl; +     +        if(!memcmp(rbuf->cast<const boost::uint8_t *>(), localbuf, data_xfer_size)) { +            std::cout << "."; +        } else { +            if(verbose) { +                int i = 0; +                for(int j = 0; j < 32; j++) { +                    std::cout << boost::format("%02X ") % int(rbuf->cast<const boost::uint8_t *>()[i*32+j]); +                } +                std::cout << std::endl; +            }     +            else std::cout << "x"; + +        } +        sbuf.reset(); +        rbuf.reset(); +        xfercount++; +        //if(verbose) std::cout << "sptrs reset" << std::endl; +    } +     +    return 0; +} diff --git a/host/utils/usrp1p_poketest.cpp b/host/utils/usrp1p_poketest.cpp new file mode 100644 index 000000000..ca7628e01 --- /dev/null +++ b/host/utils/usrp1p_poketest.cpp @@ -0,0 +1,134 @@ +//FPGA register poke test for USRP1P +//uses UHD libusb transport + +#include <uhd/device.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/usb_control.hpp> +#include <uhd/utils/assert.hpp> +#include <boost/shared_array.hpp> +#include <boost/foreach.hpp> +#include <boost/thread.hpp> +#include <boost/format.hpp> +#include <vector> +#include <iostream> +#include <iomanip> +#include "../lib/usrp/usrp1p/ctrl_packet.hpp" + +//so the goal is to open a USB device to endpoints (2,6), submit a buffer, receive a reply, and compare them. +//use usb_zero_copy::make() to get a usb_zero_copy object and then start submitting. +//need to get a usb dev handle to pass to make +//use static std::vector<usb_device_handle::sptr> get_device_list(boost::uint16_t vid, boost::uint16_t pid) to get a device handle +//then get_send_buffer, send, etc. +using namespace uhd; +using namespace uhd::transport; + +const boost::uint16_t ctrl_xfer_size = 32; + +int main(int argc, char *argv[]) { +    std::cout << "USRP1+ GPIF poke test" << std::endl; +    //step 1: get a handle on it +    std::vector<usb_device_handle::sptr> handles = usb_device_handle::get_device_list(0xfffe, 0x0003); +    if(handles.size() == 0) { +        std::cout << "No USRP1+ found." << std::endl; +        return ~0; +    } +     +    bool verbose = false; +    if(argc > 1) if(std::string(argv[1]) == "-v") verbose = true; +     +    usb_device_handle::sptr handle = handles.front(); + +    usb_zero_copy::sptr data_transport; +    usb_control::sptr ctrl_transport = usb_control::make(handle); //just in case + +    data_transport = usb_zero_copy::make( +                handle,        // identifier +                8,             // IN endpoint +                4,             // OUT endpoint +                uhd::device_addr_t("recv_frame_size=32, num_recv_frames=1, send_frame_size=32, num_send_frames=1") //args +    ); +     +    if(verbose) std::cout << "Made." << std::endl; +     +    //ok now we're made. time to get a buffer and start sending data. + +    managed_send_buffer::sptr sbuf; +    managed_recv_buffer::sptr rbuf; +    size_t xfercount = 0; +     +    static uint8_t sequence = 0; +    //uhd::usrp::ctrl_packet_out_t outpkt; +    //memset(outpkt.data, 0x00, sizeof(outpkt.data)); +//    outpkt.op = uhd::usrp::CTRL_PACKET_WRITE; +//    outpkt.callbacks = 0; +//    outpkt.seq = sequence++; +//    outpkt.len = 4; +//    outpkt.addr = 0x00000000; +//    outpkt.data[0] = 0xff; +//    outpkt.data[1] = 0xfe; +//    outpkt.data[2] = 0xfd; +//    outpkt.data[3] = 0xfc; + +    boost::uint16_t outpkt[16]; +    /* Packet format: +     * Command: 2 bits +     * Callbacks: 6 bits +     * Seq num: 8 bits +     * Length: 16 bits +     * Addr LSW: 16 bits +     * Addr MSW: 16 bits +     * Data: 24 bytes/12 words +     * Lengths are in lines +     *  +     * readback: +     * AA00 LEN(16) SEQ(16) ADDR(32) DATA(16bx12B) +     */ +    memset(outpkt, 0x00, sizeof(outpkt)); +    outpkt[0] = 0x8000; //read cmd + callbacks (0) + seq +    outpkt[1] = 0x0001; //len +    outpkt[2] = 0x0000; //addr LSW +    outpkt[3] = 0x0000; //addr MSW +    outpkt[4] = 0x0A0A; //data +    outpkt[5] = 0xFFFF; +     +     +    srand(time(0)); +//    while(1) { + +        if(verbose) std::cout << "Getting send buffer." << std::endl; +        sbuf = data_transport->get_send_buff(); +        if(sbuf == 0) { +            std::cout << "Failed to get a send buffer." << std::endl; +            return ~0; +        } +         +        for(int i = 0; i < ctrl_xfer_size; i++) { +            sbuf->cast<boost::uint8_t *>()[i] = ((boost::uint8_t *)&outpkt)[i]; +        } +         +        if(verbose) std::cout << "Buffer loaded" << std::endl; + +        sbuf->commit(ctrl_xfer_size); +        if(verbose) std::cout << "Committed." << std::endl; + +        rbuf = data_transport->get_recv_buff(0.3); //timeout +         +        if(rbuf == 0) { +            std::cout << "Failed to get receive buffer (timeout?)" << std::endl; +            return ~0; +        } +         +        for(int j = 0; j < 32; j++) { +            std::cout << boost::format("%02X ") % int(rbuf->cast<const boost::uint8_t *>()[j]); +        } +        std::cout << std::endl; + +        sbuf.reset(); +        rbuf.reset(); +        xfercount++; +        //if(verbose) std::cout << "sptrs reset" << std::endl; +//    } +     +    return 0; +} | 
