diff options
Diffstat (limited to 'host/lib')
173 files changed, 33724 insertions, 0 deletions
diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt new file mode 100644 index 000000000..60ddbce5b --- /dev/null +++ b/host/lib/CMakeLists.txt @@ -0,0 +1,113 @@ +# +# 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/>. +# + +######################################################################## +# Helpful Macros +######################################################################## +MACRO(LIBUHD_APPEND_SOURCES) +    LIST(APPEND libuhd_sources ${ARGV}) +ENDMACRO(LIBUHD_APPEND_SOURCES) + +MACRO(LIBUHD_APPEND_LIBS) +    LIST(APPEND libuhd_libs ${ARGV}) +ENDMACRO(LIBUHD_APPEND_LIBS) + +MACRO(LIBUHD_PYTHON_GEN_SOURCE pyfile outfile) +    #ensure that the directory exists for outfile +    GET_FILENAME_COMPONENT(outfile_dir ${outfile} PATH) +    FILE(MAKE_DIRECTORY ${outfile_dir}) + +    #make the outfile depend on the python script +    ADD_CUSTOM_COMMAND( +        OUTPUT ${outfile} DEPENDS ${pyfile} ${LIBUHD_PYTHON_GEN_SOURCE_DEPS} +        COMMAND ${PYTHON_EXECUTABLE} -B ${pyfile} ${outfile} +        COMMENT "Generating ${outfile}" +    ) + +    #make libuhd depend on the outfile +    LIBUHD_APPEND_SOURCES(${outfile}) +ENDMACRO(LIBUHD_PYTHON_GEN_SOURCE) + +MACRO(INCLUDE_SUBDIRECTORY subdir) +    #insert the current directories on the front of the list +    LIST(INSERT _cmake_source_dirs 0 ${CMAKE_CURRENT_SOURCE_DIR}) +    LIST(INSERT _cmake_binary_dirs 0 ${CMAKE_CURRENT_BINARY_DIR}) + +    #set the current directories to the names of the subdirs +    SET(CMAKE_CURRENT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${subdir}) +    SET(CMAKE_CURRENT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${subdir}) + +    #include the subdirectory CMakeLists to run it +    FILE(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +    INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt) + +    #reset the value of the current directories +    LIST(GET _cmake_source_dirs 0 CMAKE_CURRENT_SOURCE_DIR) +    LIST(GET _cmake_binary_dirs 0 CMAKE_CURRENT_BINARY_DIR) + +    #pop the subdir names of the front of the list +    LIST(REMOVE_AT _cmake_source_dirs 0) +    LIST(REMOVE_AT _cmake_binary_dirs 0) +ENDMACRO(INCLUDE_SUBDIRECTORY) + +######################################################################## +# Include subdirectories (different than add) +######################################################################## +INCLUDE_SUBDIRECTORY(ic_reg_maps) +INCLUDE_SUBDIRECTORY(types) +INCLUDE_SUBDIRECTORY(convert) +INCLUDE_SUBDIRECTORY(transport) +INCLUDE_SUBDIRECTORY(usrp) +INCLUDE_SUBDIRECTORY(utils) + +######################################################################## +# Setup UHD_VERSION_STRING for version.cpp +######################################################################## +SET_SOURCE_FILES_PROPERTIES( +    ${CMAKE_CURRENT_SOURCE_DIR}/version.cpp +    PROPERTIES COMPILE_DEFINITIONS +    "UHD_VERSION_STRING=\"${UHD_VERSION}-${UHD_BUILD_INFO}\"" +) + +######################################################################## +# Append to the list of sources for lib uhd +######################################################################## +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/device.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/exception.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/property_tree.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/version.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/wax.cpp +) + +######################################################################## +# Setup libuhd library +######################################################################## +ADD_LIBRARY(uhd SHARED ${libuhd_sources}) +TARGET_LINK_LIBRARIES(uhd ${Boost_LIBRARIES} ${libuhd_libs}) +SET_TARGET_PROPERTIES(uhd PROPERTIES DEFINE_SYMBOL "UHD_DLL_EXPORTS") +SET_TARGET_PROPERTIES(uhd PROPERTIES SOVERSION "${UHD_VERSION_MAJOR}") +SET_TARGET_PROPERTIES(uhd PROPERTIES VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_MINOR}") +IF(DEFINED LIBUHD_OUTPUT_NAME) +    SET_TARGET_PROPERTIES(uhd PROPERTIES OUTPUT_NAME ${LIBUHD_OUTPUT_NAME}) +ENDIF(DEFINED LIBUHD_OUTPUT_NAME) + +INSTALL(TARGETS uhd +    LIBRARY DESTINATION ${LIBRARY_DIR} COMPONENT libraries # .so file +    ARCHIVE DESTINATION ${LIBRARY_DIR} COMPONENT libraries # .lib file +    RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT libraries # .dll file +) diff --git a/host/lib/convert/CMakeLists.txt b/host/lib/convert/CMakeLists.txt new file mode 100644 index 000000000..3eea49eae --- /dev/null +++ b/host/lib/convert/CMakeLists.txt @@ -0,0 +1,132 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +INCLUDE(CheckIncludeFileCXX) +MESSAGE(STATUS "") + +######################################################################## +# Look for Orc support +######################################################################## +FIND_PACKAGE(PkgConfig) +IF(PKG_CONFIG_FOUND) +PKG_CHECK_MODULES(ORC "orc-0.4") +ENDIF(PKG_CONFIG_FOUND) + +FIND_PROGRAM(ORCC_EXECUTABLE orcc) + +IF(ORC_FOUND AND ORCC_EXECUTABLE) +    SET(ORC_SUPPORT_ENABLED TRUE) +    INCLUDE_DIRECTORIES(${ORC_INCLUDE_DIRS}) +    LINK_DIRECTORIES(${ORC_LIBRARY_DIRS}) +    ENABLE_LANGUAGE(C) + +    SET(orcc_src ${CMAKE_CURRENT_SOURCE_DIR}/convert_orc.orc) + +    GET_FILENAME_COMPONENT(orc_file_name_we ${orcc_src} NAME_WE) +    SET(orcc_gen ${CMAKE_CURRENT_BINARY_DIR}/${orc_file_name_we}.c) +    MESSAGE(STATUS "Orc found, enabling Orc support") +    ADD_CUSTOM_COMMAND( +        COMMAND ${ORCC_EXECUTABLE} --implementation -o ${orcc_gen} ${orcc_src} +        DEPENDS ${orcc_src} OUTPUT ${orcc_gen} +    ) +    LIBUHD_APPEND_SOURCES(${orcc_gen}) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_orc.cpp +    ) +    LIBUHD_APPEND_LIBS(${ORC_LIBRARIES}) +ELSE() +    SET(ORC_SUPPORT_ENABLED FALSE) +    MESSAGE(STATUS "Orc not found, disabling orc support...") +ENDIF(ORC_FOUND AND ORCC_EXECUTABLE) + +######################################################################## +# Check for SSE2 SIMD headers +######################################################################## +IF(CMAKE_COMPILER_IS_GNUCXX) +    SET(EMMINTRIN_FLAGS -msse2) +ELSEIF(MSVC) +    SET(EMMINTRIN_FLAGS /arch:SSE2) +ENDIF() + +SET(CMAKE_REQUIRED_FLAGS ${EMMINTRIN_FLAGS}) +CHECK_INCLUDE_FILE_CXX(emmintrin.h HAVE_EMMINTRIN_H) +UNSET(CMAKE_REQUIRED_FLAGS) + +IF(HAVE_EMMINTRIN_H) +    SET(convert_with_sse2_sources +        ${CMAKE_CURRENT_SOURCE_DIR}/convert_fc32_with_sse2.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/convert_fc64_with_sse2.cpp +    ) +    SET_SOURCE_FILES_PROPERTIES( +        ${convert_with_sse2_sources} +        PROPERTIES COMPILE_FLAGS "${EMMINTRIN_FLAGS}" +    ) +    LIBUHD_APPEND_SOURCES(${convert_with_sse2_sources}) +ENDIF(HAVE_EMMINTRIN_H) + +######################################################################## +# Check for NEON SIMD headers +######################################################################## +IF(CMAKE_COMPILER_IS_GNUCXX) +    SET(NEON_FLAGS "-mfloat-abi=softfp -mfpu=neon") +    SET(CMAKE_REQUIRED_FLAGS ${NEON_FLAGS}) +    CHECK_INCLUDE_FILE_CXX(arm_neon.h HAVE_ARM_NEON_H) +    UNSET(CMAKE_REQUIRED_FLAGS) +ENDIF(CMAKE_COMPILER_IS_GNUCXX) + +IF(HAVE_ARM_NEON_H AND ORC_SUPPORT_ENABLED) +    #prefer orc support, its faster than the current intrinsic implementations +    MESSAGE(STATUS "Enabled conversion support with ORC.") +ELSEIF(HAVE_ARM_NEON_H) +    MESSAGE(STATUS "Enabled conversion support with NEON intrinsics.") +    SET_SOURCE_FILES_PROPERTIES( +        ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_neon.cpp +        PROPERTIES COMPILE_FLAGS "${NEON_FLAGS}" +    ) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/convert_with_neon.cpp +    ) +ENDIF() + +######################################################################## +# Convert types generation +######################################################################## +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_convert_pred.py +    ${CMAKE_CURRENT_BINARY_DIR}/convert_pred.hpp +) + +INCLUDE(AddFileDependencies) +ADD_FILE_DEPENDENCIES( +    ${CMAKE_CURRENT_SOURCE_DIR}/convert_impl.cpp +    ${CMAKE_CURRENT_BINARY_DIR}/convert_pred.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_convert_general.py +    ${CMAKE_CURRENT_BINARY_DIR}/convert_general.cpp +) + +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/convert_impl.cpp +) diff --git a/host/lib/convert/convert_common.hpp b/host/lib/convert/convert_common.hpp new file mode 100644 index 000000000..7f513b124 --- /dev/null +++ b/host/lib/convert/convert_common.hpp @@ -0,0 +1,107 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_CONVERT_COMMON_HPP +#define INCLUDED_LIBUHD_CONVERT_COMMON_HPP + +#include <uhd/convert.hpp> +#include <uhd/utils/static.hpp> +#include <boost/cstdint.hpp> +#include <complex> + +#define DECLARE_CONVERTER(fcn, prio) \ +    static void fcn( \ +        const uhd::convert::input_type &inputs, \ +        const uhd::convert::output_type &outputs, \ +        size_t nsamps, double scale_factor \ +    ); \ +    UHD_STATIC_BLOCK(register_##fcn##_##prio){ \ +        uhd::convert::register_converter(#fcn, fcn, prio); \ +    } \ +    static void fcn( \ +        const uhd::convert::input_type &inputs, \ +        const uhd::convert::output_type &outputs, \ +        size_t nsamps, double scale_factor \ +    ) + +/*********************************************************************** + * Typedefs + **********************************************************************/ +typedef std::complex<double>         fc64_t; +typedef std::complex<float>          fc32_t; +typedef std::complex<boost::int16_t> sc16_t; +typedef std::complex<boost::int8_t>  sc8_t; +typedef boost::uint32_t              item32_t; + +/*********************************************************************** + * Convert complex short buffer to items32 + **********************************************************************/ +static UHD_INLINE item32_t sc16_to_item32(sc16_t num, double){ +    boost::uint16_t real = num.real(); +    boost::uint16_t imag = num.imag(); +    return (item32_t(real) << 16) | (item32_t(imag) << 0); +} + +/*********************************************************************** + * Convert items32 buffer to complex short + **********************************************************************/ +static UHD_INLINE sc16_t item32_to_sc16(item32_t item, double){ +    return sc16_t( +        boost::int16_t(item >> 16), +        boost::int16_t(item >> 0) +    ); +} + +/*********************************************************************** + * Convert complex float buffer to items32 (no swap) + **********************************************************************/ +static UHD_INLINE item32_t fc32_to_item32(fc32_t num, float scale_factor){ +    boost::uint16_t real = boost::int16_t(num.real()*scale_factor); +    boost::uint16_t imag = boost::int16_t(num.imag()*scale_factor); +    return (item32_t(real) << 16) | (item32_t(imag) << 0); +} + +/*********************************************************************** + * Convert items32 buffer to complex float + **********************************************************************/ +static UHD_INLINE fc32_t item32_to_fc32(item32_t item, float scale_factor){ +    return fc32_t( +        float(boost::int16_t(item >> 16)*scale_factor), +        float(boost::int16_t(item >> 0)*scale_factor) +    ); +} + +/*********************************************************************** + * Convert complex double buffer to items32 (no swap) + **********************************************************************/ +static UHD_INLINE item32_t fc64_to_item32(fc64_t num, double scale_factor){ +    boost::uint16_t real = boost::int16_t(num.real()*scale_factor); +    boost::uint16_t imag = boost::int16_t(num.imag()*scale_factor); +    return (item32_t(real) << 16) | (item32_t(imag) << 0); +} + +/*********************************************************************** + * Convert items32 buffer to complex double + **********************************************************************/ +static UHD_INLINE fc64_t item32_to_fc64(item32_t item, double scale_factor){ +    return fc64_t( +        float(boost::int16_t(item >> 16)*scale_factor), +        float(boost::int16_t(item >> 0)*scale_factor) +    ); +} + +#endif /* INCLUDED_LIBUHD_CONVERT_COMMON_HPP */ diff --git a/host/lib/convert/convert_fc32_with_sse2.cpp b/host/lib/convert/convert_fc32_with_sse2.cpp new file mode 100644 index 000000000..676e1561c --- /dev/null +++ b/host/lib/convert/convert_fc32_with_sse2.cpp @@ -0,0 +1,196 @@ +// +// 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 "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> +#include <emmintrin.h> + +using namespace uhd::convert; + +DECLARE_CONVERTER(convert_fc32_1_to_item32_1_nswap, PRIORITY_CUSTOM){ +    const fc32_t *input = reinterpret_cast<const fc32_t *>(inputs[0]); +    item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + +    const __m128 scalar = _mm_set_ps1(float(scale_factor)); + +    #define convert_fc32_1_to_item32_1_nswap_guts(_al_)                 \ +    for (; i+4 < nsamps; i+=4){                                         \ +        /* load from input */                                           \ +        __m128 tmplo = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+0)); \ +        __m128 tmphi = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+2)); \ +                                                                        \ +        /* convert and scale */                                         \ +        __m128i tmpilo = _mm_cvtps_epi32(_mm_mul_ps(tmplo, scalar));    \ +        __m128i tmpihi = _mm_cvtps_epi32(_mm_mul_ps(tmphi, scalar));    \ +                                                                        \ +        /* pack + swap 16-bit pairs */                                  \ +        __m128i tmpi = _mm_packs_epi32(tmpilo, tmpihi);                 \ +        tmpi = _mm_shufflelo_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1));      \ +        tmpi = _mm_shufflehi_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1));      \ +                                                                        \ +        /* store to output */                                           \ +        _mm_storeu_si128(reinterpret_cast<__m128i *>(output+i), tmpi);  \ +    }                                                                   \ + +    size_t i = 0; + +    //dispatch according to alignment +    switch (size_t(input) & 0xf){ +    case 0x8: +        output[i] = fc32_to_item32(input[i], float(scale_factor)); i++; +    case 0x0: +        convert_fc32_1_to_item32_1_nswap_guts(_) +        break; +    default: convert_fc32_1_to_item32_1_nswap_guts(u_) +    } + +    //convert remainder +    for (; i < nsamps; i++){ +        output[i] = fc32_to_item32(input[i], float(scale_factor)); +    } +} + +DECLARE_CONVERTER(convert_fc32_1_to_item32_1_bswap, PRIORITY_CUSTOM){ +    const fc32_t *input = reinterpret_cast<const fc32_t *>(inputs[0]); +    item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + +    const __m128 scalar = _mm_set_ps1(float(scale_factor)); + +    #define convert_fc32_1_to_item32_1_bswap_guts(_al_)                 \ +    for (; i+4 < nsamps; i+=4){                                         \ +        /* load from input */                                           \ +        __m128 tmplo = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+0)); \ +        __m128 tmphi = _mm_load ## _al_ ## ps(reinterpret_cast<const float *>(input+i+2)); \ +                                                                        \ +        /* convert and scale */                                         \ +        __m128i tmpilo = _mm_cvtps_epi32(_mm_mul_ps(tmplo, scalar));    \ +        __m128i tmpihi = _mm_cvtps_epi32(_mm_mul_ps(tmphi, scalar));    \ +                                                                        \ +        /* pack + byteswap -> byteswap 16 bit words */                  \ +        __m128i tmpi = _mm_packs_epi32(tmpilo, tmpihi);                 \ +        tmpi = _mm_or_si128(_mm_srli_epi16(tmpi, 8), _mm_slli_epi16(tmpi, 8)); \ +                                                                        \ +        /* store to output */                                           \ +        _mm_storeu_si128(reinterpret_cast<__m128i *>(output+i), tmpi);  \ +    }                                                                   \ + +    size_t i = 0; + +    //dispatch according to alignment +    switch (size_t(input) & 0xf){ +    case 0x8: +        output[i] = uhd::byteswap(fc32_to_item32(input[i], float(scale_factor))); i++; +    case 0x0: +        convert_fc32_1_to_item32_1_bswap_guts(_) +        break; +    default: convert_fc32_1_to_item32_1_bswap_guts(u_) +    } + +    //convert remainder +    for (; i < nsamps; i++){ +        output[i] = uhd::byteswap(fc32_to_item32(input[i], float(scale_factor))); +    } +} + +DECLARE_CONVERTER(convert_item32_1_to_fc32_1_nswap, PRIORITY_CUSTOM){ +    const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); +    fc32_t *output = reinterpret_cast<fc32_t *>(outputs[0]); + +    const __m128 scalar = _mm_set_ps1(float(scale_factor)/(1 << 16)); +    const __m128i zeroi = _mm_setzero_si128(); + +    #define convert_item32_1_to_fc32_1_nswap_guts(_al_)                 \ +    for (; i+4 < nsamps; i+=4){                                         \ +        /* load from input */                                           \ +        __m128i tmpi = _mm_loadu_si128(reinterpret_cast<const __m128i *>(input+i)); \ +                                                                        \ +        /* unpack + swap 16-bit pairs */                                \ +        tmpi = _mm_shufflelo_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1));      \ +        tmpi = _mm_shufflehi_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1));      \ +        __m128i tmpilo = _mm_unpacklo_epi16(zeroi, tmpi); /* value in upper 16 bits */ \ +        __m128i tmpihi = _mm_unpackhi_epi16(zeroi, tmpi);               \ +                                                                        \ +        /* convert and scale */                                         \ +        __m128 tmplo = _mm_mul_ps(_mm_cvtepi32_ps(tmpilo), scalar);     \ +        __m128 tmphi = _mm_mul_ps(_mm_cvtepi32_ps(tmpihi), scalar);     \ +                                                                        \ +        /* store to output */                                           \ +        _mm_store ## _al_ ## ps(reinterpret_cast<float *>(output+i+0), tmplo); \ +        _mm_store ## _al_ ## ps(reinterpret_cast<float *>(output+i+2), tmphi); \ +    }                                                                   \ + +    size_t i = 0; + +    //dispatch according to alignment +    switch (size_t(output) & 0xf){ +    case 0x8: +        output[i] = item32_to_fc32(input[i], float(scale_factor)); i++; +    case 0x0: +        convert_item32_1_to_fc32_1_nswap_guts(_) +        break; +    default: convert_item32_1_to_fc32_1_nswap_guts(u_) +    } + +    //convert remainder +    for (; i < nsamps; i++){ +        output[i] = item32_to_fc32(input[i], float(scale_factor)); +    } +} + +DECLARE_CONVERTER(convert_item32_1_to_fc32_1_bswap, PRIORITY_CUSTOM){ +    const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); +    fc32_t *output = reinterpret_cast<fc32_t *>(outputs[0]); + +    const __m128 scalar = _mm_set_ps1(float(scale_factor)/(1 << 16)); +    const __m128i zeroi = _mm_setzero_si128(); + +    #define convert_item32_1_to_fc32_1_bswap_guts(_al_)                 \ +    for (; i+4 < nsamps; i+=4){                                         \ +        /* load from input */                                           \ +        __m128i tmpi = _mm_loadu_si128(reinterpret_cast<const __m128i *>(input+i)); \ +                                                                        \ +        /* byteswap + unpack -> byteswap 16 bit words */                \ +        tmpi = _mm_or_si128(_mm_srli_epi16(tmpi, 8), _mm_slli_epi16(tmpi, 8)); \ +        __m128i tmpilo = _mm_unpacklo_epi16(zeroi, tmpi); /* value in upper 16 bits */ \ +        __m128i tmpihi = _mm_unpackhi_epi16(zeroi, tmpi);               \ +                                                                        \ +        /* convert and scale */                                         \ +        __m128 tmplo = _mm_mul_ps(_mm_cvtepi32_ps(tmpilo), scalar);     \ +        __m128 tmphi = _mm_mul_ps(_mm_cvtepi32_ps(tmpihi), scalar);     \ +                                                                        \ +        /* store to output */                                           \ +        _mm_store ## _al_ ## ps(reinterpret_cast<float *>(output+i+0), tmplo); \ +        _mm_store ## _al_ ## ps(reinterpret_cast<float *>(output+i+2), tmphi); \ +    }                                                                   \ + +    size_t i = 0; + +    //dispatch according to alignment +    switch (size_t(output) & 0xf){ +    case 0x8: +        output[i] = item32_to_fc32(uhd::byteswap(input[i]), float(scale_factor)); i++; +    case 0x0: +        convert_item32_1_to_fc32_1_bswap_guts(_) +        break; +    default: convert_item32_1_to_fc32_1_bswap_guts(u_) +    } + +    //convert remainder +    for (; i < nsamps; i++){ +        output[i] = item32_to_fc32(uhd::byteswap(input[i]), float(scale_factor)); +    } +} diff --git a/host/lib/convert/convert_fc64_with_sse2.cpp b/host/lib/convert/convert_fc64_with_sse2.cpp new file mode 100644 index 000000000..4d28396a4 --- /dev/null +++ b/host/lib/convert/convert_fc64_with_sse2.cpp @@ -0,0 +1,212 @@ +// +// 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 "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> +#include <emmintrin.h> + +using namespace uhd::convert; + +DECLARE_CONVERTER(convert_fc64_1_to_item32_1_nswap, PRIORITY_CUSTOM){ +    const fc64_t *input = reinterpret_cast<const fc64_t *>(inputs[0]); +    item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + +    const __m128d scalar = _mm_set1_pd(scale_factor); + +    #define convert_fc64_1_to_item32_1_nswap_guts(_al_)                 \ +    for (; i+4 < nsamps; i+=4){                                         \ +        /* load from input */                                           \ +        __m128d tmp0 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+0)); \ +        __m128d tmp1 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+1)); \ +        __m128d tmp2 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+2)); \ +        __m128d tmp3 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+3)); \ +                                                                        \ +        /* convert and scale */                                         \ +        __m128i tmpi0 = _mm_cvttpd_epi32(_mm_mul_pd(tmp0, scalar));     \ +        __m128i tmpi1 = _mm_cvttpd_epi32(_mm_mul_pd(tmp1, scalar));     \ +        __m128i tmpilo = _mm_unpacklo_epi64(tmpi0, tmpi1);              \ +        __m128i tmpi2 = _mm_cvttpd_epi32(_mm_mul_pd(tmp2, scalar));     \ +        __m128i tmpi3 = _mm_cvttpd_epi32(_mm_mul_pd(tmp3, scalar));     \ +        __m128i tmpihi = _mm_unpacklo_epi64(tmpi2, tmpi3);              \ +                                                                        \ +        /* pack + swap 16-bit pairs */                                  \ +        __m128i tmpi = _mm_packs_epi32(tmpilo, tmpihi);                 \ +        tmpi = _mm_shufflelo_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1));      \ +        tmpi = _mm_shufflehi_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1));      \ +                                                                        \ +        /* store to output */                                           \ +        _mm_storeu_si128(reinterpret_cast<__m128i *>(output+i), tmpi);  \ +    }                                                                   \ + +    size_t i = 0; + +    //dispatch according to alignment +    if ((size_t(input) & 0xf) == 0){ +        convert_fc64_1_to_item32_1_nswap_guts(_) +    } +    else{ +        convert_fc64_1_to_item32_1_nswap_guts(u_) +    } + +    //convert remainder +    for (; i < nsamps; i++){ +        output[i] = fc64_to_item32(input[i], scale_factor); +    } +} + +DECLARE_CONVERTER(convert_fc64_1_to_item32_1_bswap, PRIORITY_CUSTOM){ +    const fc64_t *input = reinterpret_cast<const fc64_t *>(inputs[0]); +    item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + +    const __m128d scalar = _mm_set1_pd(scale_factor); + +    #define convert_fc64_1_to_item32_1_bswap_guts(_al_)                 \ +    for (; i+4 < nsamps; i+=4){                                         \ +        /* load from input */                                           \ +        __m128d tmp0 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+0)); \ +        __m128d tmp1 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+1)); \ +        __m128d tmp2 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+2)); \ +        __m128d tmp3 = _mm_load ## _al_ ## pd(reinterpret_cast<const double *>(input+i+3)); \ +                                                                        \ +        /* convert and scale */                                         \ +        __m128i tmpi0 = _mm_cvttpd_epi32(_mm_mul_pd(tmp0, scalar));     \ +        __m128i tmpi1 = _mm_cvttpd_epi32(_mm_mul_pd(tmp1, scalar));     \ +        __m128i tmpilo = _mm_unpacklo_epi64(tmpi0, tmpi1);              \ +        __m128i tmpi2 = _mm_cvttpd_epi32(_mm_mul_pd(tmp2, scalar));     \ +        __m128i tmpi3 = _mm_cvttpd_epi32(_mm_mul_pd(tmp3, scalar));     \ +        __m128i tmpihi = _mm_unpacklo_epi64(tmpi2, tmpi3);              \ +                                                                        \ +        /* pack + byteswap -> byteswap 16 bit words */                  \ +        __m128i tmpi = _mm_packs_epi32(tmpilo, tmpihi);                 \ +        tmpi = _mm_or_si128(_mm_srli_epi16(tmpi, 8), _mm_slli_epi16(tmpi, 8)); \ +                                                                        \ +        /* store to output */                                           \ +        _mm_storeu_si128(reinterpret_cast<__m128i *>(output+i), tmpi);  \ +    }                                                                   \ + +    size_t i = 0; + +    //dispatch according to alignment +    if ((size_t(input) & 0xf) == 0){ +        convert_fc64_1_to_item32_1_bswap_guts(_) +    } +    else{ +        convert_fc64_1_to_item32_1_bswap_guts(u_) +    } + +    //convert remainder +    for (; i < nsamps; i++){ +        output[i] = uhd::byteswap(fc64_to_item32(input[i], scale_factor)); +    } +} + +DECLARE_CONVERTER(convert_item32_1_to_fc64_1_nswap, PRIORITY_CUSTOM){ +    const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); +    fc64_t *output = reinterpret_cast<fc64_t *>(outputs[0]); + +    const __m128d scalar = _mm_set1_pd(scale_factor/(1 << 16)); +    const __m128i zeroi = _mm_setzero_si128(); + +    #define convert_item32_1_to_fc64_1_nswap_guts(_al_)                 \ +    for (; i+4 < nsamps; i+=4){                                         \ +        /* load from input */                                           \ +        __m128i tmpi = _mm_loadu_si128(reinterpret_cast<const __m128i *>(input+i)); \ +                                                                        \ +        /* unpack + swap 16-bit pairs */                                \ +        tmpi = _mm_shufflelo_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1));      \ +        tmpi = _mm_shufflehi_epi16(tmpi, _MM_SHUFFLE(2, 3, 0, 1));      \ +        __m128i tmpilo = _mm_unpacklo_epi16(zeroi, tmpi); /* value in upper 16 bits */ \ +        __m128i tmpihi = _mm_unpackhi_epi16(zeroi, tmpi);               \ +                                                                        \ +        /* convert and scale */                                         \ +        __m128d tmp0 = _mm_mul_pd(_mm_cvtepi32_pd(tmpilo), scalar);     \ +        tmpilo = _mm_unpackhi_epi64(tmpilo, zeroi);                     \ +        __m128d tmp1 = _mm_mul_pd(_mm_cvtepi32_pd(tmpilo), scalar);     \ +        __m128d tmp2 = _mm_mul_pd(_mm_cvtepi32_pd(tmpihi), scalar);     \ +        tmpihi = _mm_unpackhi_epi64(tmpihi, zeroi);                     \ +        __m128d tmp3 = _mm_mul_pd(_mm_cvtepi32_pd(tmpihi), scalar);     \ +                                                                        \ +        /* store to output */                                           \ +        _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+0), tmp0); \ +        _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+1), tmp1); \ +        _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+2), tmp2); \ +        _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+3), tmp3); \ +    }                                                                   \ + +    size_t i = 0; + +    //dispatch according to alignment +    if ((size_t(output) & 0xf) == 0){ +        convert_item32_1_to_fc64_1_nswap_guts(_) +    } +    else{ +        convert_item32_1_to_fc64_1_nswap_guts(u_) +    } + +    //convert remainder +    for (; i < nsamps; i++){ +        output[i] = item32_to_fc64(input[i], scale_factor); +    } +} + +DECLARE_CONVERTER(convert_item32_1_to_fc64_1_bswap, PRIORITY_CUSTOM){ +    const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); +    fc64_t *output = reinterpret_cast<fc64_t *>(outputs[0]); + +    const __m128d scalar = _mm_set1_pd(scale_factor/(1 << 16)); +    const __m128i zeroi = _mm_setzero_si128(); + +    #define convert_item32_1_to_fc64_1_bswap_guts(_al_)                 \ +    for (; i+4 < nsamps; i+=4){                                         \ +        /* load from input */                                           \ +        __m128i tmpi = _mm_loadu_si128(reinterpret_cast<const __m128i *>(input+i)); \ +                                                                        \ +        /* byteswap + unpack -> byteswap 16 bit words */                \ +        tmpi = _mm_or_si128(_mm_srli_epi16(tmpi, 8), _mm_slli_epi16(tmpi, 8)); \ +        __m128i tmpilo = _mm_unpacklo_epi16(zeroi, tmpi); /* value in upper 16 bits */ \ +        __m128i tmpihi = _mm_unpackhi_epi16(zeroi, tmpi);               \ +                                                                        \ +        /* convert and scale */                                         \ +        __m128d tmp0 = _mm_mul_pd(_mm_cvtepi32_pd(tmpilo), scalar);     \ +        tmpilo = _mm_unpackhi_epi64(tmpilo, zeroi);                     \ +        __m128d tmp1 = _mm_mul_pd(_mm_cvtepi32_pd(tmpilo), scalar);     \ +        __m128d tmp2 = _mm_mul_pd(_mm_cvtepi32_pd(tmpihi), scalar);     \ +        tmpihi = _mm_unpackhi_epi64(tmpihi, zeroi);                     \ +        __m128d tmp3 = _mm_mul_pd(_mm_cvtepi32_pd(tmpihi), scalar);     \ +                                                                        \ +        /* store to output */                                           \ +        _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+0), tmp0); \ +        _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+1), tmp1); \ +        _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+2), tmp2); \ +        _mm_store ## _al_ ## pd(reinterpret_cast<double *>(output+i+3), tmp3); \ +    }                                                                   \ + +    size_t i = 0; + +    //dispatch according to alignment +    if ((size_t(output) & 0xf) == 0){ +        convert_item32_1_to_fc64_1_bswap_guts(_) +    } +    else{ +        convert_item32_1_to_fc64_1_bswap_guts(u_) +    } + +    //convert remainder +    for (; i < nsamps; i++){ +        output[i] = item32_to_fc64(uhd::byteswap(input[i]), scale_factor); +    } +} diff --git a/host/lib/convert/convert_impl.cpp b/host/lib/convert/convert_impl.cpp new file mode 100644 index 000000000..9b2cdcdc9 --- /dev/null +++ b/host/lib/convert/convert_impl.cpp @@ -0,0 +1,109 @@ +// +// 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/convert.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/exception.hpp> + +using namespace uhd; + +#include "convert_pred.hpp" + +/*********************************************************************** + * Define types for the function tables + **********************************************************************/ +struct fcn_table_entry_type{ +    convert::priority_type prio; +    convert::function_type fcn; +    fcn_table_entry_type(void) +    : prio(convert::PRIORITY_EMPTY), fcn(NULL){ +        /* NOP */ +    } +}; +typedef std::vector<fcn_table_entry_type> fcn_table_type; + +/*********************************************************************** + * Setup the table registry + **********************************************************************/ +UHD_SINGLETON_FCN(fcn_table_type, get_cpu_to_otw_table); +UHD_SINGLETON_FCN(fcn_table_type, get_otw_to_cpu_table); + +fcn_table_type &get_table(dir_type dir){ +    switch(dir){ +    case DIR_OTW_TO_CPU: return get_otw_to_cpu_table(); +    case DIR_CPU_TO_OTW: return get_cpu_to_otw_table(); +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * The registry functions + **********************************************************************/ +void uhd::convert::register_converter( +    const std::string &markup, +    function_type fcn, +    priority_type prio +){ +    //extract the predicate and direction from the markup +    dir_type dir; +    pred_type pred = make_pred(markup, dir); + +    //get a reference to the function table +    fcn_table_type &table = get_table(dir); + +    //resize the table so that its at least pred+1 +    if (table.size() <= pred) table.resize(pred+1); + +    //register the function if higher priority +    if (table[pred].prio < prio){ +        table[pred].fcn = fcn; +        table[pred].prio = prio; +    } + +    //----------------------------------------------------------------// +    UHD_LOGV(always) << "register_converter: " << markup << std::endl +        << "    prio: " << prio << std::endl +        << "    pred: " << pred << std::endl +        << "    dir: " << dir << std::endl +        << std::endl +    ; +    //----------------------------------------------------------------// +} + +/*********************************************************************** + * The converter functions + **********************************************************************/ +const convert::function_type &convert::get_converter_cpu_to_otw( +    const io_type_t &io_type, +    const otw_type_t &otw_type, +    size_t num_input_buffs, +    size_t num_output_buffs +){ +    pred_type pred = make_pred(io_type, otw_type, num_input_buffs, num_output_buffs); +    return get_cpu_to_otw_table().at(pred).fcn; +} + +const convert::function_type &convert::get_converter_otw_to_cpu( +    const io_type_t &io_type, +    const otw_type_t &otw_type, +    size_t num_input_buffs, +    size_t num_output_buffs +){ +    pred_type pred = make_pred(io_type, otw_type, num_input_buffs, num_output_buffs); +    return get_otw_to_cpu_table().at(pred).fcn; +} diff --git a/host/lib/convert/convert_orc.orc b/host/lib/convert/convert_orc.orc new file mode 100644 index 000000000..5450bf4db --- /dev/null +++ b/host/lib/convert/convert_orc.orc @@ -0,0 +1,63 @@ +.function _convert_fc32_1_to_item32_1_nswap_orc +.source 8 src +.dest 4 dst +.floatparam 4 scalar +.temp 8 scaled +.temp 8 converted +.temp 4 short +x2 mulf scaled, src, scalar +x2 convfl converted, scaled +x2 convlw short, converted +swapl short, short +x2 swapw dst, short + +.function _convert_fc32_1_to_item32_1_bswap_orc +.source 8 src +.dest 4 dst +.floatparam 4 scalar +.temp 8 scaled +.temp 8 converted +.temp 4 short +x2 mulf scaled, src, scalar +x2 convfl converted, scaled +x2 convlw short, converted +x2 swapw dst, short + +.function _convert_item32_1_to_fc32_1_nswap_orc +.source 4 src +.dest 8 dst +.floatparam 4 scalar +.temp 4 tmp1 +.temp 8 tmp2 +x2 swapw tmp1, src +swapl tmp1, tmp1 +x2 convswl tmp2, tmp1 +x2 convlf tmp2, tmp2 +x2 mulf dst, tmp2, scalar + +.function _convert_item32_1_to_fc32_1_bswap_orc +.source 4 src +.dest 8 dst +.floatparam 4 scalar +.temp 4 tmp1 +.temp 8 tmp2 +x2 swapw tmp1, src +x2 convswl tmp2, tmp1 +x2 convlf tmp2, tmp2 +x2 mulf dst, tmp2, scalar + +.function _convert_sc16_1_to_item32_1_nswap_orc +.source 4 src +.dest 4 dst +.temp 4 tmp +.floatparam 4 scalar +swapl tmp, src +x2 swapw dst, tmp + +.function _convert_item32_1_to_sc16_1_nswap_orc +.source 4 src +.dest 4 dst +.floatparam 4 scalar +.temp 4 tmp +x2 swapw tmp, src +swapl dst, tmp diff --git a/host/lib/convert/convert_with_neon.cpp b/host/lib/convert/convert_with_neon.cpp new file mode 100644 index 000000000..e5f08cad9 --- /dev/null +++ b/host/lib/convert/convert_with_neon.cpp @@ -0,0 +1,61 @@ +// +// Copyright 2011-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 "convert_common.hpp" +#include <arm_neon.h> + +using namespace uhd::convert; + +DECLARE_CONVERTER(convert_fc32_1_to_item32_1_nswap, PRIORITY_CUSTOM){ +    const fc32_t *input = reinterpret_cast<const fc32_t *>(inputs[0]); +    item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + +    size_t i; + +    float32x4_t Q0 = vdupq_n_f32(float(scale_factor)); +    for (i=0; i < (nsamps & ~0x03); i+=2) { +        float32x4_t Q1 = vld1q_f32(reinterpret_cast<const float *>(&input[i])); +        float32x4_t Q2 = vmulq_f32(Q1, Q0); +        int32x4_t Q3 = vcvtq_s32_f32(Q2); +        int16x4_t D8 = vmovn_s32(Q3); +        int16x4_t D9 = vrev32_s16(D8); +        vst1_s16((reinterpret_cast<int16_t *>(&output[i])), D9); +    } + +    for (; i < nsamps; i++) +        output[i] = fc32_to_item32(input[i], float(scale_factor)); +} + +DECLARE_CONVERTER(convert_item32_1_to_fc32_1_nswap, PRIORITY_CUSTOM){ +    const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); +    fc32_t *output = reinterpret_cast<fc32_t *>(outputs[0]); + +    size_t i; + +    float32x4_t Q1 = vdupq_n_f32(float(scale_factor)); +    for (i=0; i < (nsamps & ~0x03); i+=2) { +        int16x4_t D0 = vld1_s16(reinterpret_cast<const int16_t *>(&input[i])); +        int16x4_t D1 = vrev32_s16(D0); +        int32x4_t Q2 = vmovl_s16(D1); +        float32x4_t Q3 = vcvtq_f32_s32(Q2); +        float32x4_t Q4 = vmulq_f32(Q3, Q1); +        vst1q_f32((reinterpret_cast<float *>(&output[i])), Q4); +    } + +    for (; i < nsamps; i++) +        output[i] = item32_to_fc32(input[i], float(scale_factor)); +} diff --git a/host/lib/convert/convert_with_orc.cpp b/host/lib/convert/convert_with_orc.cpp new file mode 100644 index 000000000..844c2595c --- /dev/null +++ b/host/lib/convert/convert_with_orc.cpp @@ -0,0 +1,54 @@ +// +// 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 "convert_common.hpp" +#include <uhd/utils/byteswap.hpp> + +using namespace uhd::convert; + +extern "C" { +extern void _convert_fc32_1_to_item32_1_nswap_orc(void *, const void *, float, int); +extern void _convert_fc32_1_to_item32_1_bswap_orc(void *, const void *, float, int); +extern void _convert_item32_1_to_fc32_1_nswap_orc(void *, const void *, float, int); +extern void _convert_item32_1_to_fc32_1_bswap_orc(void *, const void *, float, int); +extern void _convert_sc16_1_to_item32_1_nswap_orc(void *, const void *, float, int); +extern void _convert_item32_1_to_sc16_1_nswap_orc(void *, const void *, float, int); +} + +DECLARE_CONVERTER(convert_fc32_1_to_item32_1_nswap, PRIORITY_LIBORC){ +    _convert_fc32_1_to_item32_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(convert_fc32_1_to_item32_1_bswap, PRIORITY_LIBORC){ +    _convert_fc32_1_to_item32_1_bswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(convert_item32_1_to_fc32_1_nswap, PRIORITY_LIBORC){ +    _convert_item32_1_to_fc32_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(convert_item32_1_to_fc32_1_bswap, PRIORITY_LIBORC){ +    _convert_item32_1_to_fc32_1_bswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(convert_sc16_1_to_item32_1_nswap, PRIORITY_LIBORC){ +    _convert_sc16_1_to_item32_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} + +DECLARE_CONVERTER(convert_item32_1_to_sc16_1_nswap, PRIORITY_LIBORC){ +    _convert_item32_1_to_sc16_1_nswap_orc(outputs[0], inputs[0], scale_factor, nsamps); +} diff --git a/host/lib/convert/gen_convert_general.py b/host/lib/convert/gen_convert_general.py new file mode 100644 index 000000000..8c3138bda --- /dev/null +++ b/host/lib/convert/gen_convert_general.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# +# 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/>. +# + +TMPL_HEADER = """ +#import time +/*********************************************************************** + * This file was generated by $file on $time.strftime("%c") + **********************************************************************/ + +\#include "convert_common.hpp" +\#include <uhd/utils/byteswap.hpp> + +using namespace uhd::convert; +""" + +TMPL_CONV_TO_FROM_ITEM32_1 = """ +DECLARE_CONVERTER(convert_$(cpu_type)_1_to_item32_1_$(swap), PRIORITY_GENERAL){ +    const $(cpu_type)_t *input = reinterpret_cast<const $(cpu_type)_t *>(inputs[0]); +    item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + +    for (size_t i = 0; i < nsamps; i++){ +        output[i] = $(swap_fcn)($(cpu_type)_to_item32(input[i], float(scale_factor))); +    } +} + +DECLARE_CONVERTER(convert_item32_1_to_$(cpu_type)_1_$(swap), PRIORITY_GENERAL){ +    const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); +    $(cpu_type)_t *output = reinterpret_cast<$(cpu_type)_t *>(outputs[0]); + +    for (size_t i = 0; i < nsamps; i++){ +        output[i] = item32_to_$(cpu_type)($(swap_fcn)(input[i]), float(scale_factor)); +    } +} +""" +TMPL_CONV_TO_FROM_ITEM32_X = """ +DECLARE_CONVERTER(convert_$(cpu_type)_$(width)_to_item32_1_$(swap), PRIORITY_GENERAL){ +    #for $w in range($width) +    const $(cpu_type)_t *input$(w) = reinterpret_cast<const $(cpu_type)_t *>(inputs[$(w)]); +    #end for +    item32_t *output = reinterpret_cast<item32_t *>(outputs[0]); + +    for (size_t i = 0, j = 0; i < nsamps; i++){ +        #for $w in range($width) +        output[j++] = $(swap_fcn)($(cpu_type)_to_item32(input$(w)[i], float(scale_factor))); +        #end for +    } +} + +DECLARE_CONVERTER(convert_item32_1_to_$(cpu_type)_$(width)_$(swap), PRIORITY_GENERAL){ +    const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]); +    #for $w in range($width) +    $(cpu_type)_t *output$(w) = reinterpret_cast<$(cpu_type)_t *>(outputs[$(w)]); +    #end for + +    for (size_t i = 0, j = 0; i < nsamps; i++){ +        #for $w in range($width) +        output$(w)[i] = item32_to_$(cpu_type)($(swap_fcn)(input[j++]), float(scale_factor)); +        #end for +    } +} +""" + +def parse_tmpl(_tmpl_text, **kwargs): +    from Cheetah.Template import Template +    return str(Template(_tmpl_text, kwargs)) + +if __name__ == '__main__': +    import sys, os +    file = os.path.basename(__file__) +    output = parse_tmpl(TMPL_HEADER, file=file) +    for width in 1, 2, 3, 4: +        for swap, swap_fcn in (('nswap', ''), ('bswap', 'uhd::byteswap')): +            for cpu_type in 'fc64', 'fc32', 'sc16': +                output += parse_tmpl( +                    TMPL_CONV_TO_FROM_ITEM32_1 if width == 1 else TMPL_CONV_TO_FROM_ITEM32_X, +                    width=width, swap=swap, swap_fcn=swap_fcn, cpu_type=cpu_type +                ) +    open(sys.argv[1], 'w').write(output) diff --git a/host/lib/convert/gen_convert_pred.py b/host/lib/convert/gen_convert_pred.py new file mode 100644 index 000000000..360fbcf44 --- /dev/null +++ b/host/lib/convert/gen_convert_pred.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# +# Copyright 2011-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/>. +# + +TMPL_TEXT = """ +#import time +/*********************************************************************** + * This file was generated by $file on $time.strftime("%c") + **********************************************************************/ +\#include <uhd/exception.hpp> +\#include <boost/tokenizer.hpp> +\#include <boost/lexical_cast.hpp> +\#include <boost/detail/endian.hpp> +\#include <boost/cstdint.hpp> +\#include <string> +\#include <vector> + +typedef size_t pred_type; +typedef std::vector<pred_type> pred_vector_type; + +enum dir_type{ +    DIR_OTW_TO_CPU = 0, +    DIR_CPU_TO_OTW = 1 +}; + +struct pred_error : uhd::value_error{ +    pred_error(const std::string &what): +        uhd::value_error("convert::make_pred: " + what) +    { +        /* NOP */ +    } +}; + +pred_type make_pred(const std::string &markup, dir_type &dir){ +    pred_type pred = 0; + +    try{ +        boost::tokenizer<boost::char_separator<char> > tokenizer(markup, boost::char_separator<char>("_")); +        std::vector<std::string> tokens(tokenizer.begin(), tokenizer.end()); +        //token 0 is <convert> +        std::string inp_type = tokens.at(1); +        std::string num_inps = tokens.at(2); +        //token 3 is <to> +        std::string out_type = tokens.at(4); +        std::string num_outs = tokens.at(5); +        std::string swap_type = tokens.at(6); + +        std::string cpu_type, otw_type; +        if (inp_type.find("item") == std::string::npos){ +            cpu_type = inp_type; +            otw_type = out_type; +            dir = DIR_CPU_TO_OTW; +        } +        else{ +            cpu_type = out_type; +            otw_type = inp_type; +            dir = DIR_OTW_TO_CPU; +        } + +        if      (cpu_type == "fc64") pred |= $ph.fc64_p; +        else if (cpu_type == "fc32") pred |= $ph.fc32_p; +        else if (cpu_type == "sc16") pred |= $ph.sc16_p; +        else if (cpu_type == "sc8")  pred |= $ph.sc8_p; +        else throw pred_error("unhandled io type " + cpu_type); + +        if (otw_type == "item32") pred |= $ph.item32_p; +        else throw pred_error("unhandled otw type " + otw_type); + +        int num_inputs = boost::lexical_cast<int>(num_inps); +        int num_outputs = boost::lexical_cast<int>(num_outs); + +        switch(num_inputs*num_outputs){ //FIXME treated as one value +        case 1: pred |= $ph.chan1_p; break; +        case 2: pred |= $ph.chan2_p; break; +        case 3: pred |= $ph.chan3_p; break; +        case 4: pred |= $ph.chan4_p; break; +        default: throw pred_error("unhandled number of channels"); +        } + +        if      (swap_type == "bswap") pred |= $ph.bswap_p; +        else if (swap_type == "nswap") pred |= $ph.nswap_p; +        else throw pred_error("unhandled swap type"); + +    } +    catch(...){ +        throw pred_error("could not parse markup: " + markup); +    } + +    return pred; +} + +#define pred_table_wildcard pred_type(~0) +#define pred_table_max_size size_t(128) +#define pred_table_index(e) (pred_type(e) & 0x7f) + +static pred_vector_type get_pred_byte_order_table(void){ +    pred_vector_type table(pred_table_max_size, pred_table_wildcard); +    \#ifdef BOOST_BIG_ENDIAN +    table[pred_table_index(otw_type_t::BO_BIG_ENDIAN)]    = $ph.nswap_p; +    table[pred_table_index(otw_type_t::BO_LITTLE_ENDIAN)] = $ph.bswap_p; +    \#else +    table[pred_table_index(otw_type_t::BO_BIG_ENDIAN)]    = $ph.bswap_p; +    table[pred_table_index(otw_type_t::BO_LITTLE_ENDIAN)] = $ph.nswap_p; +    \#endif +    table[pred_table_index(otw_type_t::BO_NATIVE)]        = $ph.nswap_p; +    return table; +} + +static pred_vector_type get_pred_io_type_table(void){ +    pred_vector_type table(pred_table_max_size, pred_table_wildcard); +    table[pred_table_index(io_type_t::COMPLEX_FLOAT64)]    = $ph.fc64_p; +    table[pred_table_index(io_type_t::COMPLEX_FLOAT32)]    = $ph.fc32_p; +    table[pred_table_index(io_type_t::COMPLEX_INT16)]      = $ph.sc16_p; +    return table; +} + +static pred_vector_type get_pred_num_io_table(void){ +    pred_vector_type table(pred_table_max_size, pred_table_wildcard); +    table[1] = $ph.chan1_p; +    table[2] = $ph.chan2_p; +    table[3] = $ph.chan3_p; +    table[4] = $ph.chan4_p; +    return table; +} + +UHD_INLINE pred_type make_pred( +    const io_type_t &io_type, +    const otw_type_t &otw_type, +    size_t num_inputs, +    size_t num_outputs +){ +    pred_type pred = $ph.item32_p; //only item32 supported as of now + +    static const pred_vector_type pred_byte_order_table(get_pred_byte_order_table()); +    pred |= pred_byte_order_table[pred_table_index(otw_type.byteorder)]; + +    static const pred_vector_type pred_io_type_table(get_pred_io_type_table()); +    pred |= pred_io_type_table[pred_table_index(io_type.tid)]; + +    static const pred_vector_type pred_num_io_table(get_pred_num_io_table()); +    pred |= pred_num_io_table[pred_table_index(num_inputs*num_outputs)]; + +    if (pred == pred_table_wildcard) throw pred_error( +        "unhanded input combination for make_pred()" +    ); + +    return pred; +} +""" + +def parse_tmpl(_tmpl_text, **kwargs): +    from Cheetah.Template import Template +    return str(Template(_tmpl_text, kwargs)) + +class ph: +    bswap_p  = 0b00001 +    nswap_p  = 0b00000 +    item32_p = 0b00000 +    sc8_p    = 0b00000 +    sc16_p   = 0b00010 +    fc32_p   = 0b00100 +    fc64_p   = 0b00110 +    chan1_p  = 0b00000 +    chan2_p  = 0b01000 +    chan3_p  = 0b10000 +    chan4_p  = 0b11000 + +if __name__ == '__main__': +    import sys, os +    file = os.path.basename(__file__) +    open(sys.argv[1], 'w').write(parse_tmpl(TMPL_TEXT, file=file, ph=ph)) diff --git a/host/lib/device.cpp b/host/lib/device.cpp new file mode 100644 index 000000000..c5bc047da --- /dev/null +++ b/host/lib/device.cpp @@ -0,0 +1,155 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/device.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/functional/hash.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/thread/mutex.hpp> + +using namespace uhd; + +static boost::mutex _device_mutex; + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +/*! + * Make a device hash that maps 1 to 1 with a device address. + * The hash will be used to identify created devices. + * \param dev_addr the device address + * \return the hash number + */ +static size_t hash_device_addr( +    const device_addr_t &dev_addr +){ +    //combine the hashes of sorted keys/value pairs +    size_t hash = 0; +    BOOST_FOREACH(const std::string &key, uhd::sorted(dev_addr.keys())){ +        boost::hash_combine(hash, key); +        boost::hash_combine(hash, dev_addr[key]); +    } +    return hash; +} + +/*********************************************************************** + * Registration + **********************************************************************/ +typedef boost::tuple<device::find_t, device::make_t> dev_fcn_reg_t; + +// instantiate the device function registry container +UHD_SINGLETON_FCN(std::vector<dev_fcn_reg_t>, get_dev_fcn_regs) + +void device::register_device( +    const find_t &find, +    const make_t &make +){ +    UHD_LOGV(always) << "registering device" << std::endl; +    get_dev_fcn_regs().push_back(dev_fcn_reg_t(find, make)); +} + +/*********************************************************************** + * Discover + **********************************************************************/ +device_addrs_t device::find(const device_addr_t &hint){ +    boost::mutex::scoped_lock lock(_device_mutex); + +    device_addrs_t device_addrs; + +    BOOST_FOREACH(const dev_fcn_reg_t &fcn, get_dev_fcn_regs()){ +        try{ +            device_addrs_t discovered_addrs = fcn.get<0>()(hint); +            device_addrs.insert( +                device_addrs.begin(), +                discovered_addrs.begin(), +                discovered_addrs.end() +            ); +        } +        catch(const std::exception &e){ +            UHD_MSG(error) << "Device discovery error: " << e.what() << std::endl; +        } +    } + +    return device_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +device::sptr device::make(const device_addr_t &hint, size_t which){ +    boost::mutex::scoped_lock lock(_device_mutex); + +    typedef boost::tuple<device_addr_t, make_t> dev_addr_make_t; +    std::vector<dev_addr_make_t> dev_addr_makers; + +    BOOST_FOREACH(const dev_fcn_reg_t &fcn, get_dev_fcn_regs()){ +        BOOST_FOREACH(device_addr_t dev_addr, fcn.get<0>()(hint)){ +            //append the discovered address and its factory function +            dev_addr_makers.push_back(dev_addr_make_t(dev_addr, fcn.get<1>())); +        } +    } + +    //check that we found any devices +    if (dev_addr_makers.size() == 0){ +        throw uhd::key_error(str( +            boost::format("No devices found for ----->\n%s") % hint.to_pp_string() +        )); +    } + +    //check that the which index is valid +    if (dev_addr_makers.size() <= which){ +        throw uhd::index_error(str( +            boost::format("No device at index %d for ----->\n%s") % which % hint.to_pp_string() +        )); +    } + +    //create a unique hash for the device address +    device_addr_t dev_addr; make_t maker; +    boost::tie(dev_addr, maker) = dev_addr_makers.at(which); +    size_t dev_hash = hash_device_addr(dev_addr); +    UHD_LOG << boost::format("Device hash: %u") % dev_hash << std::endl; + +    //copy keys that were in hint but not in dev_addr +    //this way, we can pass additional transport arguments +    BOOST_FOREACH(const std::string &key, hint.keys()){ +        if (not dev_addr.has_key(key)) dev_addr[key] = hint[key]; +    } + +    //map device address hash to created devices +    static uhd::dict<size_t, boost::weak_ptr<device> > hash_to_device; + +    //try to find an existing device +    try{ +        UHD_ASSERT_THROW(hash_to_device.has_key(dev_hash)); +        UHD_ASSERT_THROW(not hash_to_device[dev_hash].expired()); +        return hash_to_device[dev_hash].lock(); +    } +    //create and register a new device +    catch(const uhd::assertion_error &){ +        device::sptr dev = maker(dev_addr); +        hash_to_device[dev_hash] = dev; +        return dev; +    } +} diff --git a/host/lib/exception.cpp b/host/lib/exception.cpp new file mode 100644 index 000000000..ea056bd3b --- /dev/null +++ b/host/lib/exception.cpp @@ -0,0 +1,45 @@ +// +// 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/exception.hpp> +#include <boost/functional/hash.hpp> +#include <boost/format.hpp> + +using namespace uhd; + +exception::exception(const std::string &what): +    std::runtime_error(what){/* NOP */} + +#define make_exception_impl(name, class, base) \ +    class::class(const std::string &what): \ +        base(str(boost::format("%s: %s") % name % what)){} \ +    unsigned class::code(void) const{return boost::hash<std::string>()(#class) & 0xfff;} \ +    class *class::dynamic_clone(void) const{return new class(*this);} \ +    void class::dynamic_throw(void) const{throw *this;} + +make_exception_impl("AssertionError",        assertion_error,         exception) +make_exception_impl("LookupError",           lookup_error,            exception) +make_exception_impl("IndexError",            index_error,             lookup_error) +make_exception_impl("KeyError",              key_error,               lookup_error) +make_exception_impl("TypeError",             type_error,              exception) +make_exception_impl("ValueError",            value_error,             exception) +make_exception_impl("RuntimeError",          runtime_error,           exception) +make_exception_impl("NotImplementedError",   not_implemented_error,   runtime_error) +make_exception_impl("EnvironmentError",      environment_error,       exception) +make_exception_impl("IOError",               io_error,                environment_error) +make_exception_impl("OSError",               os_error,                environment_error) +make_exception_impl("SystemError",           system_error,            exception) diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt new file mode 100644 index 000000000..46cf5a06a --- /dev/null +++ b/host/lib/ic_reg_maps/CMakeLists.txt @@ -0,0 +1,100 @@ +# +# 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 +######################################################################## + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) +SET(LIBUHD_PYTHON_GEN_SOURCE_DEPS ${CMAKE_CURRENT_SOURCE_DIR}/common.py) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf4350_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/adf4350_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf4360_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/adf4360_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad9510_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/ad9510_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad9777_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/ad9777_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad5623_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/ad5623_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad7922_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/ad7922_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2829_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/max2829_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2118_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/max2118_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2112_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/max2112_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_max2112_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/max2112_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad9862_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/ad9862_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_ad9522_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/ad9522_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_ads62p44_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/ads62p44_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_tuner_4937di5_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/tuner_4937di5_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_tda18272hnm_regs.py +    ${CMAKE_CURRENT_BINARY_DIR}/tda18272hnm_regs.hpp +) + +UNSET(LIBUHD_PYTHON_GEN_SOURCE_DEPS) diff --git a/host/lib/ic_reg_maps/common.py b/host/lib/ic_reg_maps/common.py new file mode 100644 index 000000000..24f5bf8be --- /dev/null +++ b/host/lib/ic_reg_maps/common.py @@ -0,0 +1,196 @@ +# +# 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/>. +# + +import re +import sys +import math +from Cheetah.Template import Template + +COMMON_TMPL = """\ +#import time +/*********************************************************************** + * This file was generated by $file on $time.strftime("%c") + **********************************************************************/ + +\#ifndef INCLUDED_$(name.upper())_HPP +\#define INCLUDED_$(name.upper())_HPP + +\#include <uhd/config.hpp> +\#include <uhd/exception.hpp> +\#include <boost/cstdint.hpp> +\#include <set> + +class $(name)_t{ +public: +    #for $reg in $regs +    #if $reg.get_enums() +    enum $reg.get_type(){ +        #for $i, $enum in enumerate($reg.get_enums()) +        #set $end_comma = ',' if $i < len($reg.get_enums())-1 else '' +        $(reg.get_name().upper())_$(enum[0].upper()) = $enum[1]$end_comma +        #end for +    }; +    #end if +    $reg.get_type() $reg.get_name(); +    #end for + +    $(name)_t(void){ +        _state = NULL; +        #for $reg in $regs +        $reg.get_name() = $reg.get_default(); +        #end for +    } + +    ~$(name)_t(void){ +        delete _state; +    } + +    $body + +    void save_state(void){ +        if (_state == NULL) _state = new $(name)_t(); +        #for $reg in $regs +        _state->$reg.get_name() = this->$reg.get_name(); +        #end for +    } + +    template<typename T> std::set<T> get_changed_addrs(void){ +        if (_state == NULL) throw uhd::runtime_error("no saved state"); +        //check each register for changes +        std::set<T> addrs; +        #for $reg in $regs +        if(_state->$reg.get_name() != this->$reg.get_name()){ +            addrs.insert($reg.get_addr()); +        } +        #end for +        return addrs; +    } + +    #for $mreg in $mregs +    $mreg.get_type() get_$(mreg.get_name())(void){ +        return +        #set $shift = 0 +        #for $reg in $mreg.get_regs() +        ($(mreg.get_type())($reg.get_name() & $reg.get_mask()) << $shift) | +            #set $shift = $shift + $reg.get_bit_width() +        #end for +        0; +    } + +    void set_$(mreg.get_name())($mreg.get_type() reg){ +        #set $shift = 0 +        #for $reg in $mreg.get_regs() +        $reg.get_name() = (reg >> $shift) & $reg.get_mask(); +            #set $shift = $shift + $reg.get_bit_width() +        #end for +    } + +    #end for +private: +    $(name)_t *_state; +}; + +\#endif /* INCLUDED_$(name.upper())_HPP */ +""" + +def parse_tmpl(_tmpl_text, **kwargs): +    return str(Template(_tmpl_text, kwargs)) + +def to_num(arg): return int(eval(arg)) + +class reg: +    def __init__(self, reg_des): +        try: self.parse(reg_des) +        except Exception, e: +            raise Exception, 'Error parsing register description: "%s"\nWhat: %s'%(reg_des, e) + +    def parse(self, reg_des): +        x = re.match('^(\w*)\s*(\w*)\[(.*)\]\s*(\w*)\s*(.*)$', reg_des) +        name, addr, bit_range, default, enums = x.groups() + +        #store variables +        self._name = name +        self._addr = to_num(addr) +        if ':' in bit_range: self._addr_spec = sorted(map(int, bit_range.split(':'))) +        else: self._addr_spec = int(bit_range), int(bit_range) +        self._default = to_num(default) + +        #extract enum +        self._enums = list() +        if enums: +            enum_val = 0 +            for enum_str in map(str.strip, enums.split(',')): +                if '=' in enum_str: +                    enum_name, enum_val = enum_str.split('=') +                    enum_val = to_num(enum_val) +                else: enum_name = enum_str +                self._enums.append((enum_name, enum_val)) +                enum_val += 1 + +    def get_addr(self): return self._addr +    def get_enums(self): return self._enums +    def get_name(self): return self._name +    def get_default(self): +        for key, val in self.get_enums(): +            if val == self._default: return str.upper('%s_%s'%(self.get_name(), key)) +        return self._default +    def get_type(self): +        if self.get_enums(): return '%s_t'%self.get_name() +        return 'boost::uint%d_t'%max(2**math.ceil(math.log(self.get_bit_width(), 2)), 8) +    def get_shift(self): return self._addr_spec[0] +    def get_mask(self): return hex(int('1'*self.get_bit_width(), 2)) +    def get_bit_width(self): return self._addr_spec[1] - self._addr_spec[0] + 1 + +class mreg: +    def __init__(self, mreg_des, regs): +        try: self.parse(mreg_des, regs) +        except Exception, e: +            raise Exception, 'Error parsing meta register description: "%s"\nWhat: %s'%(mreg_des, e) + +    def parse(self, mreg_des, regs): +        x = re.match('^~(\w*)\s+(.*)\s*$', mreg_des) +        self._name, reg_names = x.groups() +        regs_dict = dict([(reg.get_name(), reg) for reg in regs]) +        self._regs = [regs_dict[reg_name] for reg_name in map(str.strip, reg_names.split(','))] + +    def get_name(self): return self._name +    def get_regs(self): return self._regs +    def get_bit_width(self): return sum(map(reg.get_bit_width, self._regs)) +    def get_type(self): +        return 'boost::uint%d_t'%max(2**math.ceil(math.log(self.get_bit_width(), 2)), 8) + +def generate(name, regs_tmpl, body_tmpl='', file=__file__, append=False): +    #evaluate the regs template and parse each line into a register +    regs = list(); mregs = list() +    for entry in parse_tmpl(regs_tmpl).splitlines(): +        if entry.startswith('~'): mregs.append(mreg(entry, regs)) +        else:                     regs.append(reg(entry)) + +    #evaluate the body template with the list of registers +    body = '\n    '.join(parse_tmpl(body_tmpl, regs=regs).splitlines()) + +    #evaluate the code template with the parsed registers and arguments +    code = parse_tmpl(COMMON_TMPL, +        name=name, +        regs=regs, +        mregs=mregs, +        body=body, +        file=file, +    ) + +    #write the generated code to file specified by argv1 +    open(sys.argv[1], 'a' if append else 'w').write(code) diff --git a/host/lib/ic_reg_maps/gen_ad5623_regs.py b/host/lib/ic_reg_maps/gen_ad5623_regs.py new file mode 100755 index 000000000..e653921ba --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad5623_regs.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +data             0[4:15]           0 +addr             0[16:18]          0       DAC_A=0, DAC_B=1, ALL=7 +cmd              0[19:21]          0       wr_input_n, up_dac_n, wr_input_n_up_all, wr_up_dac_chan_n, power_down, reset, load_ldac +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint32_t get_reg(void){ +    boost::uint32_t reg = 0; +    #for $reg in filter(lambda r: r.get_addr() == 0, $regs) +    reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +    #end for +    return reg; +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='ad5623_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_ad7922_regs.py b/host/lib/ic_reg_maps/gen_ad7922_regs.py new file mode 100755 index 000000000..5cec1924a --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad7922_regs.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +result           0[0:11]        0 +mod              0[12]          0 +chn              0[13]          0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint16_t get_reg(void){ +    boost::uint16_t reg = 0; +    #for $reg in filter(lambda r: r.get_addr() == 0, $regs) +    reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +    #end for +    return reg; +} + +void set_reg(boost::uint16_t reg){ +    #for $reg in filter(lambda r: r.get_addr() == 0, $regs) +    $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); +    #end for +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='ad7922_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_ad9510_regs.py b/host/lib/ic_reg_maps/gen_ad9510_regs.py new file mode 100755 index 000000000..83236c921 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad9510_regs.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## serial control port config +######################################################################## +long_instruction               0[4]          1       8bits, 16bits +soft_reset                     0[5]          0 +lsb_first                      0[6]          0       msb, lsb +sdo_inactive                   0[7]          0       active, inactive +######################################################################## +## pll settings +######################################################################## +acounter                       4[0:5]        0 +bcounter_msb                   5[0:4]        0 +bcounter_lsb                   6[0:7]        0 +lor_enable                     7[2]          0      enb, dis +lor_ildd                       7[5:6]        0      3cyc, 6cyc, 12cyc, 24cyc +charge_pump_mode               8[0:1]        0      3state, pump_up, pump_down, normal +pll_mux_control                8[2:5]        0      off, dld_high, ndiv, dld_low, rdiv, ald_nchan, acounter, prescaler, pfd_up, pfd_down, lor_high, 3state, ald_pchan, lor_lol_high, lor_lol_low, lor_low +pfd_polarity                   8[6]          0      neg, pos +reset_all_counters             9[0]          0 +ncounter_reset                 9[1]          0 +rcounter_reset                 9[2]          0 +cp_current_setting             9[4:6]        0      0_60ma, 1_2ma, 1_8ma, 2_4ma, 3_0ma, 3_6ma, 4_2ma, 4_8ma +pll_power_down                 0xA[0:1]      0      normal=0, async_pd=1, sync_pd=3 +prescaler_value                0xA[2:4]      0      div1, div2, 2_3, 4_5, 8_9, 16_17, 32_33, div3 +b_counter_bypass               0xA[6]        0 +ref_counter_msb                0xB[0:5]      0 +ref_counter_lsb                0xC[0:7]      0 +antibacklash_pw                0xD[0:1]      0      1_3ns, 2_9ns, 6_0ns +dld_window                     0xD[5]        0      9_5ns, 3_5ns +lock_detect_disable            0xD[6]        0      enb, dis +######################################################################## +## fine delay adjust +######################################################################## +#for $i, $o in ((5, 0), (6, 4)) +delay_control_out$i            $hex(0x34+$o)[0]    0 +ramp_current_out$i             $hex(0x35+$o)[0:2]  0   200ua, 400ua, 600ua, 800ua, 1000ua, 1200ua, 1400ua, 1600ua +ramp_capacitor_out$i           $hex(0x35+$o)[3:5]  0   4caps=0, 3caps=1, 2caps=3, 1cap=7 +delay_fine_adjust_out$i        $hex(0x36+$o)[1:5]  0 +#end for +######################################################################## +## outputs +######################################################################## +#for $i, $o in ((0, 0), (1, 1), (2, 2), (3, 3)) +power_down_lvpecl_out$i        $hex(0x3C+$o)[0:1]  0   normal, test, safe_pd, total_pd +output_level_lvpecl_out$i      $hex(0x3C+$o)[2:3]  2   500mv, 340mv, 810mv, 660mv +#end for +#for $i, $o in ((4, 0), (5, 1), (6, 2), (7, 3)) +power_down_lvds_cmos_out$i     $hex(0x40+$o)[0]    0 +output_level_lvds_out$i        $hex(0x40+$o)[1:2]  1   1_75ma, 3_5ma, 5_25ma, 7ma +lvds_cmos_select_out$i         $hex(0x40+$o)[3]    1   lvds, cmos +inverted_cmos_driver_out$i     $hex(0x40+$o)[4]    0   dis, enb +#end for +clock_select                   45[0]               1   clk2_drives, clk1_drives +clk1_power_down                45[1]               0 +clk2_power_down                45[2]               0 +prescaler_clock_pd             45[3]               0 +refin_power_down               45[4]               0 +all_clock_inputs_pd            45[5]               0 +######################################################################## +## dividers +######################################################################## +#for $i, $o in ((0, 0), (1, 2), (2, 4), (3, 6), (4, 8), (5, 10), (6, 12), (7, 14)) +divider_high_cycles_out$i      $hex(0x48+$o)[0:3]  0 +divider_low_cycles_out$i       $hex(0x48+$o)[4:7]  0 +phase_offset_out$i             $hex(0x49+$o)[0:3]  0 +start_out$i                    $hex(0x49+$o)[4]    0 +force_out$i                    $hex(0x49+$o)[5]    0 +nosync_out$i                   $hex(0x49+$o)[6]    0 +bypass_divider_out$i           $hex(0x49+$o)[7]    0 +#end for +######################################################################## +## function +######################################################################## +sync_detect_enable             58[0]               0    dis, enb +sync_select                    58[1]               0    1_to_0_5, 0_5_to_1 +soft_sync                      58[2]               0 +dist_power_down                58[3]               0 +sync_power_down                58[4]               0 +function_pin_select            58[5:6]             0    resetb, syncb, test, pdb +update_registers               0x5A[0]             0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint16_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return reg; +} + +boost::uint32_t get_write_reg(boost::uint16_t addr){ +    return (boost::uint32_t(addr) << 8) | get_reg(addr); +} + +boost::uint32_t get_read_reg(boost::uint16_t addr){ +    return (boost::uint32_t(addr) << 8) | (1 << 23); +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='ad9510_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_ad9522_regs.py b/host/lib/ic_reg_maps/gen_ad9522_regs.py new file mode 100755 index 000000000..1512da811 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad9522_regs.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +sdo_active                  0x000[7]                 0           sdio, sdo_sdio +lsb_first_addr_incr         0x000[6]                 0           msb, lsb +soft_reset                  0x000[5]                 0 +mirror                      0x000[3:0]               0 +readback_active_registers   0x004[0]                 0           buffer, active +pfd_polarity                0x010[7]                 0           pos, neg +cp_current                  0x010[6:4]               7           0_6ma, 1_2ma, 1_8ma, 2_4ma, 3_0ma, 3_6ma, 4_2ma, 4_8ma +cp_mode                     0x010[3:2]               3           high_imp, force_source, force_sink, normal +pll_power_down              0x010[1:0]               1           normal=0, async=1, sync=3 +r_counter_lsb               0x011[7:0]               1 +r_counter_msb               0x012[5:0]               0 +~r_counter                  r_counter_lsb, r_counter_msb +a_counter                   0x013[5:0]               0 +b_counter_lsb               0x014[7:0]               3 +b_counter_msb               0x015[4:0]               0 +~b_counter                  b_counter_lsb, b_counter_msb +set_cp_pin_to_vcp_2         0x016[7]                 0           normal, vcp_2 +reset_r_counter             0x016[6]                 0 +reset_a_and_b_counters      0x016[5]                 0 +reset_all_counters          0x016[4]                 0 +b_counter_bypass            0x016[3]                 0           normal, div1 +prescaler_p                 0x016[2:0]               6           div1, div2, div2_3, div4_5, div8_9, div16_17, div32_33, div3 +status_pin_control          0x017[7:2]               0 +antibacklash_pulse_width    0x017[1:0]               0           2_9ns, 1_3ns, 6_0ns +enb_cmos_ref_input_dc_off   0x018[7]                 0 +lock_detect_counter         0x018[6:5]               0           5cyc, 16cyc, 64cyc, 255cyc +digital_lock_detect_window  0x018[4]                 0           high_range, low_range +disable_digital_lock_detect 0x018[3]                 0           normal, disabled +vco_calibration_divider     0x018[2:1]               3           div2, div4, div8, div16 +vco_calibration_now         0x018[0]                 0 +r_a_b_counters_sync_pin_rst 0x019[7:6]               0           nothing, async, sync +r_path_delay                0x019[5:3]               0 +n_path_delay                0x019[2:0]               0 +enable_status_pin_divider   0x01A[7]                 0 +ref_freq_monitor_threshold  0x01A[6]                 0           1_02mhz, 6khz +ld_pin_control              0x01A[5:0]               0 +enable_vco_freq_monitor     0x01B[7]                 0 +enable_ref2_freq_monitor    0x01B[6]                 0 +enable_ref1_freq_monitor    0x01B[5]                 0 +refmon_pin_control          0x01B[4:0]               0 +disable_switchover_deglitch 0x01C[7]                 0 +select_ref                  0x01C[6]                 0           ref1, ref2 +use_ref_sel_pin             0x01C[5]                 0           register, ref_sel +enb_auto_ref_switchover     0x01C[4]                 0           manual, auto +stay_on_ref2                0x01C[3]                 0           return_ref1, stay_ref2 +enable_ref2                 0x01C[2]                 0 +enable_ref1                 0x01C[1]                 0 +enable_differential_ref     0x01C[0]                 0 +enb_stat_eeprom_at_stat_pin 0x01D[7]                 1 +enable_xtal_osc             0x01D[6]                 0 +enable_clock_doubler        0x01D[5]                 0 +disable_pll_status_reg      0x01D[4]                 0 +enable_ld_pin_comparator    0x01D[3]                 0 +enable_external_holdover    0x01D[1]                 0 +enable_holdover             0x01D[0]                 0 +external_zero_delay_fcds    0x01E[4:3]               0 +enable_external_zero_delay  0x01E[2]                 0 +enable_zero_delay           0x01E[1]                 0 +######################################################################## +vco_calibration_finished    0x01F[6]                 0 +holdover_active             0x01F[5]                 0 +ref2_selected               0x01F[4]                 0 +vco_freq_gt_thresh          0x01F[3]                 0 +ref2_freq_gt_thresh         0x01F[2]                 0 +ref1_freq_gt_thresh         0x01F[1]                 0 +digital_lock_detect         0x01F[0]                 0 +######################################################################## +#for $i in range(12) +#set $addr = ($i + 0x0F0) +out$(i)_format              $(addr)[7]             0             lvds, cmos +out$(i)_cmos_configuration  $(addr)[6:5]           3             off, a_on, b_on, ab_on +out$(i)_polarity            $(addr)[4:3]           0             lvds_a_non_b_inv=0, lvds_a_inv_b_non=1, cmos_ab_non=0, cmos_ab_inv=1, cmos_a_non_b_inv=2, cmos_a_inv_b_non=3 +out$(i)_lvds_diff_voltage   $(addr)[2:1]           1             1_75ma, 3_5ma, 5_25ma, 7_0ma +out$(i)_lvds_power_down     $(addr)[0]             0 +#end for +######################################################################## +#for $i in reversed(range(8)) +csdld_en_out_$i             0x0FC[$i]                0           ignore, async +#end for +######################################################################## +#for $i in reversed(range(4)) +csdld_en_out_$(8 + $i)      0x0FD[$i]                0           ignore, async +#end for +######################################################################## +#set $default_val = 0x7 +#for $i in range(4) +#set $addr0 = hex($i*3 + 0x190) +#set $addr1 = hex($i*3 + 0x191) +#set $addr2 = hex($i*3 + 0x192) +divider$(i)_low_cycles      $(addr0)[7:4]         $default_val +divider$(i)_high_cycles     $(addr0)[3:0]         $default_val +divider$(i)_bypass          $(addr1)[7]           0 +divider$(i)_ignore_sync     $(addr1)[6]           0 +divider$(i)_force_high      $(addr1)[5]           0 +divider$(i)_start_high      $(addr1)[4]           0 +divider$(i)_phase_offset    $(addr1)[3:0]         0 +channel$(i)_power_down      $(addr2)[2]           0 +disable_divider$(i)_ddc     $(addr2)[0]           0 +#set $default_val /= 2 +#end for +######################################################################## +vco_divider                  0x1E0[2:0]              2             div2, div3, div4, div5, div6, static, div1 +power_down_clock_input_sel   0x1E1[4]                0 +power_down_vco_clock_ifc     0x1E1[3]                0 +power_down_vco_and_clock     0x1E1[2]                0 +select_vco_or_clock          0x1E1[1]                0             external, vco +bypass_vco_divider           0x1E1[0]                0 +disable_power_on_sync        0x230[3]                0 +power_down_sync              0x230[2]                0 +power_down_dist_ref          0x230[1]                0 +soft_sync                    0x230[0]                0 +io_update                    0x232[0]                0 +soft_eeprom                  0xB02[1]                0 +enable_eeprom_write          0xB02[0]                0 +reg2eeprom                   0xB03[0]                0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint32_t get_reg(boost::uint16_t addr){ +    boost::uint32_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    if (addr == 0){ //mirror 4 bits in register 0 +        reg |= ((reg >> 7) & 0x1) << 0; +        reg |= ((reg >> 6) & 0x1) << 1; +        reg |= ((reg >> 5) & 0x1) << 2; +        reg |= ((reg >> 4) & 0x1) << 3; +    } +    return reg; +} + +void set_reg(boost::uint16_t addr, boost::uint32_t reg){ +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); +        #end for +        break; +    #end for +    } +} + +boost::uint32_t get_write_reg(boost::uint16_t addr){ +    return (boost::uint32_t(addr) << 8) | get_reg(addr); +} + +boost::uint32_t get_read_reg(boost::uint16_t addr){ +    return (boost::uint32_t(addr) << 8) | (1 << 23); +} + +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='ad9522_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_ad9777_regs.py b/host/lib/ic_reg_maps/gen_ad9777_regs.py new file mode 100755 index 000000000..47b61cf44 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad9777_regs.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +sdio_bidirectional      0[7]     0      input, io +lsb_msb_first           0[6]     0      msb, lsb +soft_reset              0[5]     0 +sleep_mode              0[4]     0 +power_down_mode         0[3]     0 +x_1r_2r_mode            0[2]     0      2r, 1r +pll_lock_indicator      0[1]     0 +######################################################################## +## address 1 +######################################################################## +filter_interp_rate      1[6:7]   0      1x, 2x, 4x, 8x +modulation_mode         1[4:5]   0      none, fs_2, fs_4, fs_8 +zero_stuff_mode         1[3]     0 +mix_mode                1[2]     1      complex, real +modulation_form         1[1]     0      e_minus_jwt, e_plus_jwt +data_clk_pll_lock_sel   1[0]     0      pll_lock, data_clk +######################################################################## +## address 2 +######################################################################## +signed_input_data       2[7]     0      signed, unsigned +two_port_mode           2[6]     0      two_port, one_port +dataclk_driver_strength 2[5]     0      weak, strong +dataclk_invert          2[4]     0 +oneportclk_invert       2[2]     0 +iqsel_invert            2[1]     0 +iq_first                2[0]     0      i_first, q_first +######################################################################## +## address 3 +######################################################################## +data_rate_clock_output  3[7]     0      pll_lock, spi_sdo +pll_divide_ratio        3[0:1]   0      div1, div2, div4, div8 +######################################################################## +## address 4 +######################################################################## +pll_state               4[7]     0      off, on +auto_cp_control         4[6]     0      auto, manual +pll_cp_control          4[0:2]   0      50ua=0, 100ua=1, 200ua=2, 400ua=3, 800ua=7 +######################################################################## +## address 5 and 9 +######################################################################## +idac_fine_gain_adjust   5[0:7]   0 +qdac_fine_gain_adjust   9[0:7]   0 +######################################################################## +## address 6 and A +######################################################################## +idac_coarse_gain_adjust 6[0:3]   0 +qdac_coarse_gain_adjust 0xA[0:3] 0 +######################################################################## +## address 7, 8 and B, C +######################################################################## +idac_offset_adjust_msb  7[0:7]   0 +idac_offset_adjust_lsb  8[0:1]   0 +~idac_offset_adjust     idac_offset_adjust_lsb, idac_offset_adjust_msb +idac_ioffset_direction  8[7]     0     out_a, out_b +qdac_offset_adjust_msb  0xB[0:7] 0 +qdac_offset_adjust_lsb  0xC[0:1] 0 +~qdac_offset_adjust     qdac_offset_adjust_lsb, qdac_offset_adjust_msb +qdac_ioffset_direction  0xC[7]   0     out_a, out_b +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return reg; +} + +boost::uint16_t get_write_reg(boost::uint8_t addr){ +    return (boost::uint16_t(addr) << 8) | get_reg(addr); +} + +boost::uint16_t get_read_reg(boost::uint8_t addr){ +    return (boost::uint16_t(addr) << 8) | (1 << 7); +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='ad9777_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_ad9862_regs.py b/host/lib/ic_reg_maps/gen_ad9862_regs.py new file mode 100755 index 000000000..00340224c --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ad9862_regs.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## General +######################################################################## +sdio_bidir         0[7]              0                  sdio_sdo, sdio +lsb_first          0[6]              0                  msb, lsb +soft_reset         0[5]              0 +######################################################################## +## Rx Power Down +######################################################################## +vref_diff_pd       1[7]              0 +vref_pd            1[6]              0 +rx_digital_pd      1[5]              0 +rx_channel_b_pd    1[4]              0 +rx_channel_a_pd    1[3]              0 +buffer_b_pd        1[2]              0 +buffer_a_pd        1[1]              0 +all_rx_pd          1[0]              0 +######################################################################## +## Rx A and B +######################################################################## +#for $x, $i in (('a', 2), ('b', 3)) +byp_buffer_$x      $(i)[7]           0 +rx_pga_$x          $(i)[0:4]         0 +#end for +######################################################################## +## Rx Misc +######################################################################## +hs_duty_cycle      4[2]              0 +shared_ref         4[1]              0 +clk_duty           4[0]              0 +######################################################################## +## RX I/F (INTERFACE) +######################################################################## +three_state        5[4]              0 +rx_retime          5[3]              0       clkout1, clkout2 +rx_twos_comp       5[2]              0 +inv_rxsync         5[1]              0 +mux_out            5[0]              0       rx_mux_mode=1, dual_port_mode=0 +######################################################################## +## RX Digital +######################################################################## +two_channel        6[3]              1       rx_b_dis, both_enb +rx_keep_ve         6[2]              0       pass_pos, pass_neg +rx_hilbert         6[1]              0       dis, enb +decimate           6[0]              0       dis, enb +######################################################################## +## TX Power Down +######################################################################## +alt_timing_mode    8[5]              0 +txoff_enable       8[4]              0 +tx_digital_pd      8[3]              0 +tx_analog_pd       8[0:2]            0        none=0, txb=4, txa=2, both=7 +######################################################################## +## Tx Offset and Gain +######################################################################## +#for $x, $i, $j, $k in (('a', 10, 11, 14), ('b', 12, 13, 15)) +dac_$(x)_offset_1_0   $(i)[6:7]           0 +dac_$(x)_offset_dir   $(i)[0]             0        neg_diff, pos_dif +dac_$(x)_offset_9_2   $(j)[0:7]           0 +dac_$(x)_coarse_gain  $(k)[6:7]           0 +dac_$(x)_fine_gain    $(k)[0:5]           0 +#end for +tx_pga_gain            16[0:7]            0 +######################################################################## +## Tx Misc +######################################################################## +tx_slave_enable        17[1]              0 +tx_pga_mode            17[0]              0        normal, fast +######################################################################## +## Tx IF (INTERFACE) +######################################################################## +tx_retime              18[6]              1        clkout1=1, clkout2=0 +qi_order               18[5]              0        iq, qi +inv_txsync             18[4]              0 +tx_twos_comp           18[3]              0 +inverse_samp           18[2]              0        rise, fall +edges                  18[1]              0        normal, both +interleaved            18[0]              0        single, interleaved +######################################################################## +## TX Digital +######################################################################## +two_data_paths         19[4]              0        single, both +tx_keep_ve             19[3]              0        pass_pos, pass_neg +tx_hilbert             19[2]              0        dis, enb +interp                 19[0:1]            0        1, 2, 4 +######################################################################## +## TX Modulator +######################################################################## +neg_fine_tune          20[5]              0        pos_shift, neg_shift +fine_mode              20[4]              0        bypass, nco +real_mix_mode          20[3]              0        complex, real +neg_coarse_tune        20[2]              0        pos_shift, neg_shift +coarse_mod             20[0:1]            0        bypass, fdac_4, fdac_8 +######################################################################## +## NCO Tuning Word +######################################################################## +ftw_7_0                21[0:7]            0 +ftw_15_8               22[0:7]            0 +ftw_23_16              23[0:7]            0 +######################################################################## +## DLL +######################################################################## +input_clk_ctrl         24[6]              0       internal, external +adc_div2               24[5]              0       normal, div2 +dll_mult               24[3:4]            0       1, 2, 4 +dll_pd                 24[2]              0 +dll_mode               24[0]              0       slow, fast +######################################################################## +## Clock Out +######################################################################## +clkout2_div_factor     25[6:7]            0       1, 2, 4, 8 +inv2                   25[5]              0       normal, inverted +inv1                   25[1]              0       normal, inverted +dis2                   25[4]              0       enb, dis +dis1                   25[0]              0       enb, dis +######################################################################## +## Aux ADC +######################################################################## +#for $x, $i in (('a2', 26), ('a1', 28), ('b2', 30), ('b1', 32)) +aux_adc_$(x)_1_0       $(i)[6:7]          0 +aux_adc_$(x)_9_2       $int(1+$i)[0:7]    0 +#end for +######################################################################## +## Aux ADC Control +######################################################################## +aux_spi                34[7]              0       dis, enb +sel_bnota              34[6]              0       adc_a, adc_b +#for $x, $i in (('b', 5), ('a', 2)) +refsel_$(x)            34[$i]             0       external, internal +select_$(x)            34[$int($i-1)]     0       aux_adc2, aux_adc1 +start_$(x)             34[$int($i-2)]     0 +#end for +######################################################################## +## Aux ADC Clock +######################################################################## +clk_4                  35[0]              0       1_2, 1_4 +######################################################################## +## Aux DAC +######################################################################## +#for $x, $i in (('a', 36), ('b', 37), ('c', 38)) +aux_dac_$x             $(i)[0:7]          0 +#end for +######################################################################## +## Aux DAC Update +######################################################################## +aux_dac_slave_enable   39[7]              0 +aux_dacupdate_c        39[2]              0 +aux_dacupdate_b        39[1]              0 +aux_dacupdate_a        39[0]              0 +######################################################################## +## AUX DAC Power Down +######################################################################## +aux_dac_pd_a           40[2]              0 +aux_dac_pd_b           40[1]              0 +aux_dac_pd_c           40[0]              0 +######################################################################## +## AUX DAC Control +######################################################################## +aux_dac_invert_a       41[2]              0 +aux_dac_invert_b       41[1]              0 +aux_dac_invert_c       41[0]              0 +######################################################################## +## Sig Delt +######################################################################## +sig_delt_3_0           42[4:7]            0 +sig_delt_11_4          43[0:7]            0 +######################################################################## +## ADC Low Power +######################################################################## +rx_low_power_mode_r49 49[0:7]             0 +rx_low_power_mode_r50 50[0:7]             0 +######################################################################## +## Chip ID +######################################################################## +chip_id                63[0:7]            0 +""" + +######################################################################## +# Header and Source templates below +######################################################################## +BODY_TMPL=""" +boost::uint8_t get_reg(boost::uint8_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in range(0, 63+1) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint16_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return reg; +} + +void set_reg(boost::uint8_t addr, boost::uint16_t reg){ +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); +        #end for +        break; +    #end for +    } +} + +boost::uint16_t get_write_reg(boost::uint8_t addr){ +    return (boost::uint16_t(addr) << 8) | get_reg(addr); +} + +boost::uint16_t get_read_reg(boost::uint8_t addr){ +    return (boost::uint16_t(addr) << 8) | (1 << 15); +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='ad9862_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_adf4350_regs.py b/host/lib/ic_reg_maps/gen_adf4350_regs.py new file mode 100755 index 000000000..e97772843 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_adf4350_regs.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +frac_12_bit             0[3:14]     0 +int_16_bit              0[15:30]    0x23 +##reserved              0[31]       0 +######################################################################## +## address 1 +######################################################################## +mod_12_bit              1[3:14]     0xfff +phase_12_bit            1[15:26]    0 +prescaler               1[27]       0       4_5, 8_9 +##reserved              1[28:31]    0 +######################################################################## +## address 2 +######################################################################## +counter_reset           2[3]        0       disabled, enabled +cp_three_state          2[4]        0       disabled, enabled +power_down              2[5]        0       disabled, enabled +pd_polarity             2[6]        1       negative, positive +ldp                     2[7]        0       10ns, 6ns +ldf                     2[8]        0       frac_n, int_n +#set $current_setting_enums = ', '.join(map(lambda x: '_'.join(("%0.2fma"%(round(x*31.27 + 31.27)/100)).split('.')), range(0,16))) +charge_pump_current     2[9:12]     5       $current_setting_enums +double_buffer           2[13]       0       disabled, enabled +r_counter_10_bit        2[14:23]    0 +reference_divide_by_2   2[24]       1       disabled, enabled +reference_doubler       2[25]       0       disabled, enabled +muxout                  2[26:28]    1       3state, dvdd, dgnd, rdiv, ndiv, analog_ld, dld, reserved +low_noise_and_spur      2[29:30]    3       low_noise, reserved0, reserved1, low_spur +######################################################################## +## address 3 +######################################################################## +clock_divider_12_bit    3[3:14]     0 +clock_div_mode          3[15:16]    0       clock_divider_off, fast_lock, resync_enable, reserved +##reserved              3[17]       0 +cycle_slip_reduction    3[18]       0       disabled, enabled +##reserved              3[19:20]    0 +##reserved              3[21:31]    0 +######################################################################## +## address 4 +######################################################################## +output_power            4[3:4]      3       m4dbm, m1dbm, 2dbm, 5dbm +rf_output_enable        4[5]        1       disabled, enabled +aux_output_power        4[6:7]      0       m4dbm, m1dbm, 2dbm, 5dbm +aux_output_enable       4[8]        0       disabled, enabled +aux_output_select       4[9]        1       divided, fundamental +mute_till_lock_detect   4[10]       0       mute_disabled, mute_enabled +vco_power_down          4[11]       0       vco_powered_up, vco_powered_down +band_select_clock_div   4[12:19]    0 +rf_divider_select       4[20:22]    0       div1, div2, div4, div8, div16 +feedback_select         4[23]       1       divided, fundamental +##reserved              4[24:31]    0 +######################################################################## +## address 5 +######################################################################## +##reserved              5[3:18]     0 +##reserved              5[19:20]    0 +##reserved              5[21]       0 +ld_pin_mode             5[22:23]    1       low0, dld, low, high +##reserved              5[24:31]    0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +enum addr_t{ +    ADDR_R0 = 0, +    ADDR_R1 = 1, +    ADDR_R2 = 2, +    ADDR_R3 = 3, +    ADDR_R4 = 4, +    ADDR_R5 = 5 +}; + +boost::uint32_t get_reg(boost::uint8_t addr){ +    boost::uint32_t reg = addr & 0x7; +    switch(addr){ +    #for $addr in range(5+1) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return reg; +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='adf4350_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_adf4360_regs.py b/host/lib/ic_reg_maps/gen_adf4360_regs.py new file mode 100755 index 000000000..3fd8707a7 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_adf4360_regs.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +core_power_level         0[2:3]     0          5ma, 10ma, 15ma, 20ma +counter_operation        0[4]       0          normal, reset +muxout_control           0[5:7]     0          3state, dld, ndiv, dvdd, rdiv, nchan_od_ld, sdo, dgnd +phase_detector_polarity  0[8]       0          neg, pos +charge_pump_output       0[9]       0          normal, 3state +cp_gain_0                0[10]      0          set1, set2 +mute_till_ld             0[11]      0          dis, enb +output_power_level       0[12:13]   0          3_5ma, 5_0ma, 7_5ma, 11_0ma +#set $current_setting_enums = ', '.join(map(lambda x: x+"ma", "0_31 0_62 0_93 1_25 1_56 1_87 2_18 2_50".split())) +current_setting1         0[14:16]   0          $current_setting_enums +current_setting2         0[17:19]   0          $current_setting_enums +power_down               0[20:21]   0          normal_op=0, async_pd=1, sync_pd=3 +prescaler_value          0[22:23]   0          8_9, 16_17, 32_33 +######################################################################## +## address 2 +######################################################################## +a_counter                2[2:6]     0 +b_counter                2[8:20]    0 +cp_gain_1                2[21]      0          set1, set2 +divide_by_2_output       2[22]      0          fund, div2 +divide_by_2_prescaler    2[23]      0          fund, div2 +######################################################################## +## address 1 +######################################################################## +r_counter                1[2:15]    0 +ablpw                    1[16:17]   0           3_0ns, 1_3ns, 6_0ns +lock_detect_precision    1[18]      0           3cycles, 5cycles +test_mode_bit            1[19]      0 +band_select_clock_div    1[20:21]   0           1, 2, 4, 8 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +enum addr_t{ +    ADDR_CONTROL = 0, +    ADDR_NCOUNTER = 2, +    ADDR_RCOUNTER = 1 +}; + +boost::uint32_t get_reg(addr_t addr){ +    boost::uint32_t reg = addr & 0x3; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint32_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return reg; +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='adf4360_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_ads62p44_regs.py b/host/lib/ic_reg_maps/gen_ads62p44_regs.py new file mode 100755 index 000000000..f0a84d940 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ads62p44_regs.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## address 0 +######################################################################## +reset                   0[1]     0       +serial_readout          0[0]     0       +######################################################################## +## address 16 +######################################################################## +clkout_strength         16[6:7]   0      weaker=1, default=0, stronger=3 +######################################################################## +## address 17 +######################################################################## +dataout_strength        17[0:1]   0      weaker=1, default=0, stronger=3, maximum=2 +lvds_current            17[2:3]   0      3_5ma, 2_5ma, 4_5ma, 1_75ma +lvds_current_double     17[4:5]   0      default, dblclk, dbldataclock +######################################################################## +## address 18 +######################################################################## +lvds_clk_term           18[0:2]   0      none, 300, 180, 110, 150, 100, 81, 60 +lvds_data_term          18[3:5]   0      none, 300, 180, 110, 150, 100, 81, 60 +######################################################################## +## address 19 +######################################################################## +offset_freeze           19[4]     0 +######################################################################## +## address 20 +######################################################################## +power_down              20[0:2]   0      normal, a_dis, b_dis, ab_dis, global_pd, a_sby, b_sby, mux +ref_select              20[3]     0      internal, external +coarse_gain             20[4]     0      0db, 3_5db +output_interface        20[5]     0      cmos, lvds +override                20[7]     0 +######################################################################## +## address 22 +######################################################################## +test_patterns           22[0:2]   0      normal, zeros, ones, toggle, ramp, custom +lvds_bytewise           22[3]     0 +data_format             22[4]     0      twos_complement, binary +######################################################################## +## address 23 +######################################################################## +fine_gain               23[0:3]   0 +######################################################################## +## address 24 and 25 +######################################################################## +custom_low              24[0:7]   0 +custom_high             25[0:5]   0 +######################################################################## +## address 26 +######################################################################## +gain_correction         26[0:3]   0 +offset_tc               26[4:6]   0      1_1s, 0_55s, 0_27s, 0_13s, 2_15s, 4_3s +low_latency             26[7]     0 +######################################################################## +## address 27 +######################################################################## +decimation              27[0:2]   0      decimate_2, decimate_4, decimate_1, decimate_8 +odd_tap_enable          27[3]     0 +filter_enable           27[4]     0 +filter_coeff_sel        27[5]     0      predefined, userdefined +offset_enable           27[7]     0 +######################################################################## +## address 29 +######################################################################## +decimation_filter_bands 29[0:1]   0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return reg; +} + +boost::uint16_t get_write_reg(boost::uint8_t addr){ +    return (boost::uint16_t(addr) << 8) | get_reg(addr); +} + +boost::uint16_t get_read_reg(boost::uint8_t addr){ +    return (boost::uint16_t(addr) << 8) | (1 << 7); +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='ads62p44_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_max2112_regs.py b/host/lib/ic_reg_maps/gen_max2112_regs.py new file mode 100755 index 000000000..c2fc4e3e2 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_max2112_regs.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing write registers +# name addr[bit range inclusive] default optional enums +######################################################################## +WRITE_REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## +######################################################################## +## N-Divider MSB (0) Write +######################################################################## +frac                  0[7]          1       invalid, frac +n_divider_msb         0[0:6]        0 +######################################################################## +## N-Divider LSB (1) Write +######################################################################## +n_divider_lsb         1[0:7]        0x23 +~n_divider            n_divider_lsb, n_divider_msb +######################################################################## +## Charge Pump (2) Write +######################################################################## +cpmp                  2[6:7]        0 +cplin                 2[4:5]        1 +f_divider_mmsb        2[0:3]        0x2 +######################################################################## +## F-Divider MSB (3) Write +######################################################################## +f_divider_msb         3[0:7]        0xF6 +######################################################################## +## F-Divider LSB (4) Write +######################################################################## +f_divider_lsb         4[0:7]        0x84  +~f_divider            f_divider_lsb, f_divider_msb, f_divider_mmsb +######################################################################## +## XTAL-Divider R-Divider (5) Write +######################################################################## +#set $xtal_divider_names = ', '.join(map(lambda x: 'div' + str(x), range(1,9))) +xtal_divider          5[5:7]        0       $xtal_divider_names +r_divider             5[0:4]        1        +######################################################################## +## PLL (6) Write +######################################################################## +d24                   6[7]          1       div2, div4  ## div2 for LO <= 1125M, div4 > 1125M +cps                   6[6]          1       i_cp_from_icp, i_cp_from_vas +icp                   6[5]          0       i_cp_600ua, i_cp_1200ua +##reserved            6[0:4]        0 +######################################################################## +## VCO (7) Write +######################################################################## +vco                   7[3:7]        0x19 +vas                   7[2]          1       disabled, enabled +adl                   7[1]          1       disabled, enabled +ade                   7[0]          1       disabled, enabled +######################################################################## +## LPF (8) Write +######################################################################## +lp                    8[0:7]        0x4B    ## map(lambda x: "%0.2f"%((4e6 + (x - 12) * 290e3)/1e6), range(255)) in MHz +######################################################################## +## Control (9) Write +######################################################################## +stby                  9[7]          0       normal, disable_sig_and_synth +##reserved            9[6]          0 +pwdn                  9[5]          0       normal, invalid +##reserved            9[4]          0 +bbg                   9[0:3]        0       ## Baseband Gain in dB +######################################################################## +## Shutdown (0xA) Write +######################################################################## +##reserved            0xA[7]        0 +pll_shutdown          0xA[6]        0       normal, shutdown +div_shutdown          0xA[5]        0       normal, shutdown +vco_shutdown          0xA[4]        0       normal, shutdown +bb_shutdown           0xA[3]        0       normal, shutdown +rfmix_shutdown        0xA[2]        0       normal, shutdown +rfvga_shutdown        0xA[1]        0       normal, shutdown +fe_shutdown           0xA[0]        0       normal, shutdown +######################################################################## +## Test (0xB) Write +######################################################################## +cptst                 0xB[5:7]      0 +##reserved            0xB[4]        0 +turbo                 0xB[3]        1 +ld_mux                0xB[0:2]      0       refout=0, invalid +""" + +######################################################################## +# Template for raw text data describing read registers +# name addr[bit range inclusive] default optional enums +######################################################################## +READ_REGS_TMPL="""\ +######################################################################## +## Status Byte-1 (0xC) Read +######################################################################## +por                   0xC[7]        0       read, reset +vasa                  0xC[6]        0       vas_fail, vas_win +vase                  0xC[5]        0       active, inactive +ld                    0xC[4]        0       unlocked, locked +##reserved            0xC[0:3]      0 +######################################################################## +## Status Byte-2 (0xD) Read +######################################################################## +vcosbr                0xD[3:7]      0       ## vco band readback +adc                   0xD[0:2]      0       ool0, lock0, vaslock0, vaslock1, vaslock2, vaslock3, lock1, ool1 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return boost::uint8_t(reg); +} + +void set_reg(boost::uint8_t addr, boost::uint8_t reg){ +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); +        #end for +        break; +    #end for +    } +} +""" + +SPLIT_REGS_HELPER_TMPL="""\ +#for $divname in ['n','f'] +void set_$(divname)_divider(boost::uint32_t $divname){ +    #for $regname in sorted(map(lambda r: r.get_name(), filter(lambda r: r.get_name().find(divname + '_divider') == 0, $regs))) +    #end for +} +#end for +""" +    #$regname = boost::uint8_t($divname & $regs[regname].get_mask()); +    #$divname = boost::uint32_t($divname >> $regs[regname].get_shift()); + +if __name__ == '__main__': +    import common; common.generate( +        name='max2112_write_regs', +        regs_tmpl=WRITE_REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) + +    import common; common.generate( +        name='max2112_read_regs', +        regs_tmpl=READ_REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +        append=True, +    ) diff --git a/host/lib/ic_reg_maps/gen_max2118_regs.py b/host/lib/ic_reg_maps/gen_max2118_regs.py new file mode 100755 index 000000000..506fbaec8 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_max2118_regs.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing write registers +# name addr[bit range inclusive] default optional enums +######################################################################## +WRITE_REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## +######################################################################## +## N-Divider MSB (0) Write +######################################################################## +div2                  0[7]          0       div4, div2 +n_divider_msb         0[0:6]        3 +######################################################################## +## N-Divider LSB (1) Write +######################################################################## +n_divider_lsb         1[0:7]        0xB6 +~n_divider            n_divider_lsb, n_divider_msb +######################################################################## +## R, Charge Pump, and VCO (2) Write +######################################################################## +#set $r_divider_names = ', '.join(map(lambda x: 'div' + str(2**(x+1)), range(0,8))) +r_divider             2[5:7]        1       $r_divider_names  +#set $cp_current_bias = ', '.join(map(lambda x: 'i_cp_%dua'%(50*2**x), range(0,4))) +cp_current            2[3:4]        3       $cp_current_bias +osc_band              2[0:2]        5 +######################################################################## +## I/Q Filter DAC (3) Write +######################################################################## +##unused              3[7]          0 +f_dac                 3[0:6]        0x7F    ## filter tuning dac, depends on m +######################################################################## +## LPF Divider DAC (4) Write +######################################################################## +adl_vco_adc_latch     4[7]          0       disabled, enabled +ade_vco_ade_read      4[6]          0       disabled, enabled +dl_output_drive       4[5]          0       iq_590m_vpp, iq_1_vpp +m_divider             4[0:4]        2       ## filter tuning counter +######################################################################## +## GC2 and Diag (5) Write +######################################################################## +diag                  5[5:7]        0       normal, cp_i_source, cp_i_sink, cp_high_z, unused, n_and_filt, r_and_gc2, m_div +gc2                   5[0:4]        0x1F    ## Step Size: 0-1: 0dB, 2-22: 1dB, 23-31: 0.5dB +""" + +######################################################################## +# Template for raw text data describing read registers +# name addr[bit range inclusive] default optional enums +######################################################################## +READ_REGS_TMPL="""\ +######################################################################## +## Status (0) Read +######################################################################## +pwr                   0[6]          0       not_reset, reset +adc                   0[2:4]        0       ## VCO tuning voltage, Lock Status +######################################################################## +## I/Q Filter DAC (1) Read +######################################################################## +filter_dac            1[0:6]        0       ## I/Q Filter tuning DAC, current +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return boost::uint8_t(reg); +} + +void set_reg(boost::uint8_t addr, boost::uint8_t reg){ +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); +        #end for +        break; +    #end for +    } +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='max2118_write_regs', +        regs_tmpl=WRITE_REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) + +    import common; common.generate( +        name='max2118_read_regs', +        regs_tmpl=READ_REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +        append=True, +    ) diff --git a/host/lib/ic_reg_maps/gen_max2829_regs.py b/host/lib/ic_reg_maps/gen_max2829_regs.py new file mode 100755 index 000000000..383131c18 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_max2829_regs.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## +######################################################################## +## Standby (2) +######################################################################## +_set_to_1_2_0         2[0]          1 +_set_to_1_2_1         2[1]          1 +_set_to_1_2_2         2[2]          1 +pa_bias_dac           2[10]         0 +voltage_ref           2[11]         0 +_set_to_1_2_12        2[12]         1 +mimo_select           2[13]         0                 normal, mimo +######################################################################## +## Integer Divider Ratio (3) +######################################################################## +int_div_ratio_word    3[0:7]        0xa2 +frac_div_ratio_lsb    3[12:13]      0 +######################################################################## +## Fractional Divider Ratio (4) +######################################################################## +frac_div_ratio_msb    4[0:13]       0 +######################################################################## +## Band Select and PLL (5) +######################################################################## +band_select           5[0]          0                 2_4ghz, 5ghz +ref_divider           5[1:3]        1 +pll_cp_select         5[5]          1                 2ma, 4ma +band_select_802_11a   5[6]          0                 4_9ghz_to_5_35ghz, 5_47ghz_to_5_875ghz +vco_bandswitch        5[7]          0                 disable, automatic +vco_spi_bandswitch    5[8]          0                 fsm, spi +vco_sub_band          5[9:10]       0 +_set_to_1_5_11        5[11]         1 +_set_to_1_5_12        5[12]         1 +band_sel_mimo         5[13]         0                 normal, mimo +######################################################################## +## Calibration (6) +######################################################################## +rx_cal_mode           6[0]          0                 dis, enb +tx_cal_mode           6[1]          0                 dis, enb +_set_to_1_6_10        6[10]         1 +iq_cal_gain           6[11:12]      3                 8db, 18db, 24db, 34db +######################################################################## +## Lowpass Filter (7) +######################################################################## +rx_lpf_fine_adj       7[0:2]        2                 90, 95, 100, 105, 110 +rx_lpf_coarse_adj     7[3:4]        1                 7_5mhz, 9_5mhz, 14mhz, 18mhz +tx_lpf_coarse_adj     7[5:6]        1                 12mhz=1, 18mhz=2, 24mhz=3 +rssi_high_bw          7[11]         0                 2mhz, 6mhz +######################################################################## +## Rx Control/RSSI (8) +######################################################################## +_set_to_1_8_0         8[0]          1 +rx_highpass           8[2]          1                 100hz, 30khz +_set_to_1_8_5         8[5]          1 +rssi_pin_fcn          8[8]          0                 rssi, temp +rssi_op_mode          8[10]         0                 rssi_rxhp, enabled +rssi_output_range     8[11]         0                 low, high +rx_vga_gain_spi       8[12]         0                 io, spi +######################################################################## +## Tx Linearity/Baseband Gain (9) +######################################################################## +tx_baseband_gain      9[0:1]        0                 0db, 2db, 3_5db, 5db +tx_upconv_linearity   9[2:3]        0                 50, 63, 78, 100 +tx_vga_linearity      9[6:7]        0                 50, 63, 78, 100 +pa_driver_linearity   9[8:9]        2                 50, 63, 78, 100 +tx_vga_gain_spi       9[10]         0                 io, spi +######################################################################## +## PA Bias DAC (10) +######################################################################## +pa_bias_dac_out_curr  10[0:5]       0 +pa_bias_dac_delay     10[6:9]       0xf +######################################################################## +## Rx Gain (11) +######################################################################## +rx_vga_gain           11[0:4]       0x1f +rx_lna_gain           11[5:6]       3 +######################################################################## +## Tx VGA Gain (12) +######################################################################## +tx_vga_gain           12[0:5]       0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint32_t get_reg(boost::uint8_t addr){ +    boost::uint16_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint16_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return (boost::uint32_t(reg) << 4) | (addr & 0xf); +} +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='max2829_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_tda18272hnm_regs.py b/host/lib/ic_reg_maps/gen_tda18272hnm_regs.py new file mode 100755 index 000000000..677a201de --- /dev/null +++ b/host/lib/ic_reg_maps/gen_tda18272hnm_regs.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing write registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## +######################################################################## +##  ID_byte_1 (0x00) Read +######################################################################## +##reserved as 1       0x00[7]       1 +ident_14_8            0x00[0:6]     0 +######################################################################## +##  ID_byte_2 (0x01) Read +######################################################################## +ident_7_0             0x01[0:7]     0 +##~ident                ident_7_0, ident_14_8 +######################################################################## +##  ID_byte_3 (0x02) Read +######################################################################## +major_rev             0x02[4:7]     0 +minor_rev             0x02[0:3]     0 +######################################################################## +##  Thermo_byte_1 (0x03) Read +######################################################################## +##reserved            0x03[7]       0 +tm_d                  0x03[0:6]     0   ## 22-127deg C junction temp +######################################################################## +##  Thermo_byte_2 (0x04) Write +######################################################################## +##reserved            0x04[1:7]     0 +tm_on                 0x04[0]       0   sensor_off, sensor_on +######################################################################## +##  Power_state_byte_1 (0x05) Read +######################################################################## +##reserved            0x05[2:7]     0 +por                   0x05[1]       0   read, reset +lo_lock               0x05[0]       0   unlocked, locked +######################################################################## +##  Power_state_byte_2 (0x06) Read/Write ## Standby modes +######################################################################## +##reserved            0x06[4:7]     0 +sm                    0x06[3]       0   normal, standby +sm_pll                0x06[2]       0   on, off +sm_lna                0x06[1]       0   on, off +##resevered as 0      0x06[0]       0 +## (sm, sm_pll, sm_lna) only valid values are 000, 100, 110, and 111 +######################################################################## +##  Input_Power_Level_byte (0x07) Read +######################################################################## +##reserved            0x07[7]       0 +power_level           0x07[0:6]     0   ## 40dB_Vrms to 110dB_Vrms +## Trigger power level calculation with MSM_byte_1 and MSM_byte_2 +######################################################################## +##  IRQ_status (0x08) Read/Write +######################################################################## +irq_status            0x08[7]       0   cleared, set +##reserved            0x08[6]       0 +irq_xtalcal_end       0x08[5]       0   false, true +irq_rssi_end          0x08[4]       0   false, true +irq_localc_end        0x08[3]       0   false, true +irq_rfcal_end         0x08[2]       0   false, true +irq_ircal_end         0x08[1]       0   false, true +irq_rccal_end         0x08[0]       0   false, true +######################################################################## +##  IRQ_enable (0x09) Read/Write +######################################################################## +irq_enable            0x09[7]       1   false, true +##reserved            0x09[6]       0 +irq_xtalcal_enable    0x09[5]       0   false, true +irq_rssi_enable       0x09[4]       0   false, true +irq_localc_enable     0x09[3]       0   false, true +irq_rfcal_enable      0x09[2]       0   false, true +irq_ircal_enable      0x09[1]       0   false, true +irq_rccal_enable      0x09[0]       0   false, true +######################################################################## +##  IRQ_clear (0x0a) Read/Write +######################################################################## +irq_clear             0x0a[7]       0   false, true +##reserved            0x0a[6]       0 +irq_xtalcal_clear     0x0a[5]       0   false, true +irq_rssi_clear        0x0a[4]       0   false, true +irq_localc_clear      0x0a[3]       0   false, true +irq_rfcal_clear       0x0a[2]       0   false, true +irq_ircal_clear       0x0a[1]       0   false, true +irq_rccal_clear       0x0a[0]       0   false, true +######################################################################## +##  IRQ_set (0x0b) Read +######################################################################## +irq_set               0x0b[7]       0   false, true +##reserved            0x0b[6]       0 +irq_xtalcal_set       0x0b[5]       0   false, true +irq_rssi_set          0x0b[4]       0   false, true +irq_localc_set        0x0b[3]       0   false, true +irq_rfcal_set         0x0b[2]       0   false, true +irq_ircal_set         0x0b[1]       0   false, true +irq_rccal_set         0x0b[0]       0   false, true +######################################################################## +##  AGC1_byte_1 (0x0c) Read/Write +######################################################################## +lt_enable             0x0c[7]       0 +agc1_6_15db           0x0c[6]       1 +##reserved            0x0c[4:5]     0 +agc1_top              0x0c[0:3]     0 +######################################################################## +##  AGC2_byte_1 (0x0d) Read +######################################################################## +##reserved            0x0d[5:7]     0 +agc2_top              0x0d[0:4]     0xf +######################################################################## +##  AGCK_byte_1 (0x0e) Read/Write +######################################################################## +agcs_up_step_assym    0x0e[6:7]     3 +agcs_up_step          0x0e[5]       1 +pulse_shaper_disable  0x0e[4]       0   vsync_pulse, 500us_pulse +agck_step             0x0e[2:3]     0 +agck_mode             0x0e[0:1]     1   analog_tv=1, digital_tv=2 +######################################################################## +##  RF_AGC_byte_1 (0x0f) Read/Write +######################################################################## +pd_rfagc_adapt        0x0f[7]       0   on, off +rfagc_adapt_top       0x0f[5:6]     0 +rfagc_low_bw          0x0f[4]       1 +rf_atten_3db          0x0f[3]       0   0db, 3db ## FIXME +agc3_top              0x0f[0:2]     1 +######################################################################## +##  IR_MIXER_byte_1 (0x10) Read/Write +######################################################################## +##reserved            0x10[4:7]     0 +agc4_top              0x10[0:3]     1 +######################################################################## +##  AGC5_byte_1 (0x11) Read/Write +######################################################################## +##reserved            0x11[7]       0 +agcs_do_step_assym    0x11[5:6]     2 +agc5_hpf              0x11[4]       1   off, on +agc5_top              0x11[0:3]     1 +######################################################################## +##  IF_AGC_byte (0x12) Read/Write +######################################################################## +##reserved            0x12[3:7]     0 +if_level              0x12[0:2]     0   0_5vpp=7, 0_6vpp=6, 0_7vpp=5, 0_85vpp=4, 0_8vpp=3, 1_0vpp=2, 1_25vpp=1, 2_0vpp=0 +######################################################################## +##  IF_byte_1 (0x13) Read/Write +######################################################################## +if_hp_fc              0x13[6:7]     0   0_4mhz, 0_85mhz, 1_0mhz, 1_5mhz +if_atsc_notch         0x13[5]       0   off, on +lp_fc_offset          0x13[3:4]     0   0_percent, m4_percent, m8_percent, forbidden +lp_fc                 0x13[0:2]     3   1_7mhz=4, 6_0mhz=0, 7_0mhz=1, 8_0mhz=2, 10_0mhz=3 +######################################################################## +##  Reference_byte (0x14) Read/Write +######################################################################## +i2c_clock_mode        0x14[7]       0 +digital_clock         0x14[6]       1   spread_off, spread_on +##reserved            0x14[5]       0 +xtalosc_anareg_en     0x14[4]       0 +##reserved            0x14[2:3]     0 +xtout                 0x14[0:1]     0   no=0, 16mhz=3 +######################################################################## +##  IF_Frequency_byte (0x15) Read/Write +######################################################################## +if_freq               0x15[0:7]     0   ## IF frequency = if_freq*50 (kHz) +######################################################################## +##  RF_Frequency_byte_1 (0x16) Read/Write +######################################################################## +##reserved            0x16[4:7]     0 +rf_freq_19_16         0x16[0:3]     0 +######################################################################## +##  RF_Frequency_byte_2 (0x17) Read/Write +######################################################################## +rf_freq_15_8          0x17[0:7]     0 +######################################################################## +##  RF_Frequency_byte_3 (0x18) Read/Write +######################################################################## +rf_freq_7_0           0x18[0:7]     0 +~rf_freq              rf_freq_7_0, rf_freq_15_8, rf_freq_19_16 +## RF Frequency = rf_freq (kHz) +######################################################################## +##  MSM_byte_1 (0x19) Read/Write +######################################################################## +rssi_meas             0x19[7]       0 +rf_cal_av             0x19[6]       0 +rf_cal                0x19[5]       0 +ir_cal_loop           0x19[4]       0 +ir_cal_image          0x19[3]       0 +ir_cal_wanted         0x19[2]       0 +rc_cal                0x19[1]       0 +calc_pll              0x19[0]       0 +######################################################################## +##  MSM_byte_2 (0x1a) Read +######################################################################## +##reserved            0x1a[2:7]     0 +xtalcal_launch        0x1a[1]       0 +msm_launch            0x1a[0]       0 +######################################################################## +##  PSM_byte_1 (0x1b) Read +######################################################################## +psm_agc1              0x1b[6:7]     0 +psm_stob              0x1b[5]       0 +psmrfpoly             0x1b[4]       0 +psm_mixer             0x1b[3]       0 +psm_ifpoly            0x1b[2]       0 +psm_lodriver          0x1b[0:1]     0 +######################################################################## +##  DCC_byte_1 (0x1c) Read +######################################################################## +dcc_bypass            0x1c[7]       0 +dcc_slow              0x1c[6]       0 +dcc_psm               0x1c[5]       0 +##reserved            0x1c[0:4]     0 +######################################################################## +##  FLO_Max_byte (0x1d) Read +######################################################################## +##reserved            0x1d[6:7]     0 +fmax_lo               0x1d[0:5]     0xA +######################################################################## +##  IR_Cal_byte_1 (0x1e) Read +######################################################################## +ir_loop               0x1e[6:7]     0 +ir_target             0x1e[3:5]     0 +ir_gstep              0x1e[0:2]     0 +######################################################################## +##  IR_Cal_byte_2 (0x1f) Read +######################################################################## +ir_corr_boost         0x1f[7]       0 +ir_freqlow_sel        0x1f[6]       0 +ir_mode_ram_store     0x1f[5]       0 +ir_freqlow            0x1f[0:4]     0 +######################################################################## +##  IR_Cal_byte_3 (0x20) Read +######################################################################## +##reserved            0x20[5:7]     0 +ir_freqmid            0x20[0:4]     0 +######################################################################## +##  IR_Cal_byte_4 (0x21) Read +######################################################################## +##reserved            0x21[5:7]     0 +coarse_ir_freqhigh    0x21[4]       0 +ir_freqhigh           0x21[0:3]     0 +######################################################################## +##  Vsync_Mgt_byte (0x22) Read +######################################################################## +pd_vsync_mgt          0x22[7]       0 +pd_ovld               0x22[6]       0 +pd_udld               0x22[5]       0 +agc_ovld_top          0x22[2:4]     0 +agc_ovld_timer        0x22[0:1]     0 +######################################################################## +##  IR_MIXER_byte_2 (0x23) Read/Write +######################################################################## +ir_mixer_loop_off     0x23[7]       0 +ir_mixer_do_step      0x23[5:6]     0 +##reserved            0x23[2:4]     0 +hi_pass               0x23[1]       0   disable, enable ## FIXME Logic Unclear +if_notch              0x23[0]       1   on, off +######################################################################## +##  AGC1_byte_2 (0x24) Read +######################################################################## +agc1_loop_off         0x24[7]       0 +agc1_do_step          0x24[5:6]     2 +force_agc1_gain       0x24[4]       0 +agc1_gain             0x24[0:3]     8 +######################################################################## +##  AGC5_byte_2 (0x25) Read +######################################################################## +agc5_loop_off         0x25[7]       0 +agc5_do_step          0x25[5:6]     0 +##reserved            0x25[4]       0 +force_agc5_gain       0x25[3]       0 +##reserved            0x25[2]       0 +agc5_gain             0x25[0:1]     2 +######################################################################## +##  RF_Cal_byte_1 (0x26) Read +######################################################################## +rfcal_offset_cprog0   0x26[6:7]     0 +rfcal_freq0           0x26[4:5]     0 +rfcal_offset_cprog1   0x26[2:3]     0 +rfcal_freq1           0x26[0:1]     0 +######################################################################## +##  RF_Cal_byte_2 (0x27) Read +######################################################################## +rfcal_offset_cprog2   0x27[6:7]     0 +rfcal_freq2           0x27[4:5]     0 +rfcal_offset_cprog3   0x27[2:3]     0 +rfcal_freq3           0x27[0:1]     0 +######################################################################## +##  RF_Cal_byte_3 (0x28) Read +######################################################################## +rfcal_offset_cprog4   0x28[6:7]     0 +rfcal_freq4           0x28[4:5]     0 +rfcal_offset_cprog5   0x28[2:3]     0 +rfcal_freq5           0x28[0:1]     0 +######################################################################## +##  RF_Cal_byte_4 (0x29) Read +######################################################################## +rfcal_offset_cprog6   0x29[6:7]     0 +rfcal_freq6           0x29[4:5]     0 +rfcal_offset_cprog7   0x29[2:3]     0 +rfcal_freq7           0x29[0:1]     0 +######################################################################## +##  RF_Cal_byte_5 (0x2a) Read +######################################################################## +rfcal_offset_cprog8   0x2a[6:7]     0 +rfcal_freq8           0x2a[4:5]     0 +rfcal_offset_cprog9   0x2a[2:3]     0 +rfcal_freq9           0x2a[0:1]     0 +######################################################################## +##  RF_Cal_byte_6 (0x2b) Read +######################################################################## +rfcal_offset_cprog10  0x2b[6:7]     0 +rfcal_freq10          0x2b[4:5]     0 +rfcal_offset_cprog11  0x2b[2:3]     0 +rfcal_freq11          0x2b[0:1]     0 +######################################################################## +##  RF_Filter_byte_1 (0x2c) Read +######################################################################## +rf_filter_bypass      0x2c[7]       0 +##reserved as 0       0x2c[6]       0 +agc2_loop_off         0x2c[5]       0 +force_agc2_gain       0x2c[4]       0 +rf_filter_gv          0x2c[2:3]     2 +rf_filter_band        0x2c[0:1]     0 +######################################################################## +##  RF_Filter_byte_2 (0x2d) Read +######################################################################## +rf_filter_cap         0x2d[0:7]     0 +######################################################################## +##  RF_Filter_byte_3 (0x2e) Read +######################################################################## +agc2_do_step          0x2e[6:7]     2 +gain_taper            0x2e[0:5]     0 +######################################################################## +##  RF_Band_Pass_Filter_byte (0x2f) Read +######################################################################## +rf_bpf_bypass         0x2f[7]       0 +##reserved            0x2f[3:6]     0 +rf_bpf                0x2f[0:2]     0 +######################################################################## +##  CP_Current_byte (0x30) Read +######################################################################## +##reserved            0x30[7]       0 +n_cp_current          0x30[0:6]     0x68 +######################################################################## +##  AGC_Det_Out_byte (0x31) Read +######################################################################## +up_agc5               0x31[7]       0 +do_agc5               0x31[6]       0 +up_agc4               0x31[5]       0 +do_agc4               0x31[4]       0 +up_agc2               0x31[3]       0 +do_agc2               0x31[2]       0 +up_agc1               0x31[1]       0 +do_agc1               0x31[0]       0 +######################################################################## +##  RF_AGC_Gain_byte_1 (0x32) Read +######################################################################## +#set $lna_gain_names = ', '.join(map(lambda x: {0: '', 1: 'm'}[3*x-12 < 0] + str(abs(3*x-12)) + 'db=' + str(x), range(0,10))) +##reserved            0x32[6:7]     0 +agc2_gain_read        0x32[4:5]     3   m11db, m8db, m5db, m2db +agc1_gain_read        0x32[0:3]     9   $lna_gain_names +######################################################################## +##  RF_AGC_Gain_byte_2 (0x33) Read +######################################################################## +#set $top_agc3_read_names = ', '.join(map(lambda x: str(int(round(1.92*x+94))) + 'dbuvrms=' + str(x), range(0,8))) +##reserved            0x33[3:7]     0 +top_agc3_read         0x33[0:2]     0   $top_agc3_read_names +######################################################################## +##  IF_AGC_Gain_byte (0x34) Read +######################################################################## +#set $lpf_gain_names = ', '.join(map(lambda x: str(3*x) + 'db=' + str(x), range(0,4))) +#set $ir_mixer_names = ', '.join(map(lambda x: str(3*x+2) + 'db=' + str(x), range(0,5))) +##reserved            0x34[5:7]     0 +agc5_gain_read        0x34[3:4]     3   $lpf_gain_names +agc4_gain_read        0x34[0:2]     4   $ir_mixer_names +######################################################################## +##  Power_byte_1 (0x35) Read +######################################################################## +rssi                  0x35[0:7]     0 +######################################################################## +##  Power_byte_2 (0x36) Read +######################################################################## +##reserved            0x36[6:7]     0 +rssi_av               0x36[5]       0 +##reserved            0x36[4]       0 +rssi_cap_reset_en     0x36[3]       1 +rssi_cap_val          0x36[2]       1 +rssi_ck_speed         0x36[1]       0 +rssi_dicho_not        0x36[0]       1 +######################################################################## +##  Misc_byte_1 (0x37) Read/Write +######################################################################## +rfcal_phi2            0x37[6:7]     1 +dds_polarity          0x37[5]       0 +rfcal_deltagain       0x37[1:4]     1 +irq_polarity          0x37[0]       0   raised_vcc, raised_low +######################################################################## +##  rfcal_log_1 (0x38) Read +######################################################################## +rfcal_log_1           0x38[0:7]     0 +######################################################################## +##  rfcal_log_2 (0x39) Read +######################################################################## +rfcal_log_2           0x39[0:7]     0 +######################################################################## +##  rfcal_log_3 (0x3a) Read +######################################################################## +rfcal_log_3           0x3a[0:7]     0 +######################################################################## +##  rfcal_log_4 (0x3b) Read +######################################################################## +rfcal_log_4           0x3b[0:7]     0 +######################################################################## +##  rfcal_log_5 (0x3c) Read +######################################################################## +rfcal_log_5           0x3c[0:7]     0 +######################################################################## +##  rfcal_log_6 (0x3d) Read +######################################################################## +rfcal_log_6           0x3d[0:7]     0 +######################################################################## +##  rfcal_log_7 (0x3e) Read +######################################################################## +rfcal_log_7           0x3e[0:7]     0 +######################################################################## +##  rfcal_log_8 (0x3f) Read +######################################################################## +rfcal_log_8           0x3f[0:7]     0 +######################################################################## +##  rfcal_log_9 (0x40) Read +######################################################################## +rfcal_log_9           0x40[0:7]     0 +######################################################################## +##  rfcal_log_10 (0x41) Read +######################################################################## +rfcal_log_10          0x41[0:7]     0 +######################################################################## +##  rfcal_log_11 (0x42) Read +######################################################################## +rfcal_log_11          0x42[0:7]     0 +######################################################################## +##  rfcal_log_12 (0x43) Read +######################################################################## +rfcal_log_12          0x43[0:7]     0 +## +## +######################################################################## +## FORBIDDEN ACCESS to 0x50-0x67 and 0xFE-0xFF +######################################################################## +######################################################################## +##  xtal_cal_dac (0x65) Write +######################################################################## +magic                 0x43[7]       1   untouched, xtal_cal_dac +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return boost::uint8_t(reg); +} + +void set_reg(boost::uint8_t addr, boost::uint8_t reg){ +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        $reg.get_name() = $(reg.get_type())((reg >> $reg.get_shift()) & $reg.get_mask()); +        #end for +        break; +    #end for +    } +} +""" + +SPLIT_REGS_HELPER_TMPL="""\ +#for $divname in ['n','f'] +void set_$(divname)_divider(boost::uint32_t $divname){ +    #for $regname in sorted(map(lambda r: r.get_name(), filter(lambda r: r.get_name().find(divname + '_divider') == 0, $regs))) +    #end for +} +#end for +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='tda18272hnm_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py b/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py new file mode 100644 index 000000000..73f7aa3db --- /dev/null +++ b/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## Divider byte 1 +######################################################################## +db1                   0[0:6]        0x00 +######################################################################## +## Divider byte 2 +######################################################################## +db2                   1[0:7]        0x00 +######################################################################## +## Control byte 1 +######################################################################## +cb7                   2[7]          0x01 +cp                    2[6]          0x00     low,high +os                    2[0]          0x00     on,off +rs                    2[1:2]        0x00     d512=3,d640=0,d1024=1 +test                  2[3:5]        0x01     normal=0x01,cpoff=0x02,cpsink=0x06,cpsrc=0x07,cptest1=0x04,cptest2=0x05 +######################################################################## +## Control byte 2 +######################################################################## +bandsel               3[4:7]        0x03     uhf=0x03,vhfhi=0x09,vhflo=0x0a +power                 3[3]          0x00     on,off +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return boost::uint8_t(reg); +} + +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='tuner_4937di5_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/property_tree.cpp b/host/lib/property_tree.cpp new file mode 100644 index 000000000..50e82a6ba --- /dev/null +++ b/host/lib/property_tree.cpp @@ -0,0 +1,181 @@ +// +// 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/property_tree.hpp> +#include <uhd/types/dict.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/make_shared.hpp> +#include <iostream> + +using namespace uhd; + +/*********************************************************************** + * Helper function to iterate through paths + **********************************************************************/ +#include <boost/tokenizer.hpp> +#define path_tokenizer(path) \ +    boost::tokenizer<boost::char_separator<char> > \ +    (path, boost::char_separator<char>("/")) + +/*********************************************************************** + * Property path implementation wrapper + **********************************************************************/ +fs_path::fs_path(void): std::string(){} +fs_path::fs_path(const char *p): std::string(p){} +fs_path::fs_path(const std::string &p): std::string(p){} + +std::string fs_path::leaf(void) const{ +    const size_t pos = this->rfind("/"); +    if (pos == std::string::npos) return *this; +    return this->substr(pos+1); +} + +fs_path fs_path::branch_path(void) const{ +    const size_t pos = this->rfind("/"); +    if (pos == std::string::npos) return *this; +    return fs_path(this->substr(0, pos)); +} + +fs_path uhd::operator/(const fs_path &lhs, const fs_path &rhs){ +    //strip trailing slash on left-hand-side +    if (not lhs.empty() and *lhs.rbegin() == '/'){ +        return fs_path(lhs.substr(0, lhs.size()-1)) / rhs; +    } + +    //strip leading slash on right-hand-side +    if (not rhs.empty() and *rhs.begin() == '/'){ +        return lhs / fs_path(rhs.substr(1)); +    } + +    return fs_path(lhs + "/" + rhs); +} + +/*********************************************************************** + * Property tree implementation + **********************************************************************/ +class property_tree_impl : public uhd::property_tree{ +public: + +    property_tree_impl(const fs_path &root = fs_path()): +        _root(root) +    { +        _guts = boost::make_shared<tree_guts_type>(); +    } + +    sptr subtree(const fs_path &path_) const{ +        const fs_path path = _root / path_; +        boost::mutex::scoped_lock lock(_guts->mutex); + +        property_tree_impl *subtree = new property_tree_impl(path); +        subtree->_guts = this->_guts; //copy the guts sptr +        return sptr(subtree); +    } + +    void remove(const fs_path &path_){ +        const fs_path path = _root / path_; +        boost::mutex::scoped_lock lock(_guts->mutex); + +        node_type *parent = NULL; +        node_type *node = &_guts->root; +        BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ +            if (not node->has_key(name)) throw_path_not_found(path); +            parent = node; +            node = &(*node)[name]; +        } +        if (parent == NULL) throw uhd::runtime_error("Cannot uproot"); +        parent->pop(fs_path(path.leaf())); +    } + +    bool exists(const fs_path &path_) const{ +        const fs_path path = _root / path_; +        boost::mutex::scoped_lock lock(_guts->mutex); + +        node_type *node = &_guts->root; +        BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ +            if (not node->has_key(name)) return false; +            node = &(*node)[name]; +        } +        return true; +    } + +    std::vector<std::string> list(const fs_path &path_) const{ +        const fs_path path = _root / path_; +        boost::mutex::scoped_lock lock(_guts->mutex); + +        node_type *node = &_guts->root; +        BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ +            if (not node->has_key(name)) throw_path_not_found(path); +            node = &(*node)[name]; +        } + +        return node->keys(); +    } + +    void _create(const fs_path &path_, const boost::shared_ptr<void> &prop){ +        const fs_path path = _root / path_; +        boost::mutex::scoped_lock lock(_guts->mutex); + +        node_type *node = &_guts->root; +        BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ +            if (not node->has_key(name)) (*node)[name] = node_type(); +            node = &(*node)[name]; +        } +        if (node->prop.get() != NULL) throw uhd::runtime_error("Cannot create! Property already exists at: " + path); +        node->prop = prop; +    } + +    boost::shared_ptr<void> &_access(const fs_path &path_) const{ +        const fs_path path = _root / path_; +        boost::mutex::scoped_lock lock(_guts->mutex); + +        node_type *node = &_guts->root; +        BOOST_FOREACH(const std::string &name, path_tokenizer(path)){ +            if (not node->has_key(name)) throw_path_not_found(path); +            node = &(*node)[name]; +        } +        if (node->prop.get() == NULL) throw uhd::runtime_error("Cannot access! Property uninitialized at: " + path); +        return node->prop; +    } + +private: +    void throw_path_not_found(const fs_path &path) const{ +        throw uhd::lookup_error("Path not found in tree: " + path); +    } + +    //basic structural node element +    struct node_type : uhd::dict<std::string, node_type>{ +        boost::shared_ptr<void> prop; +    }; + +    //tree guts which may be referenced in a subtree +    struct tree_guts_type{ +        node_type root; +        boost::mutex mutex; +    }; + +    //members, the tree and root prefix +    boost::shared_ptr<tree_guts_type> _guts; +    const fs_path _root; +}; + +/*********************************************************************** + * Property tree factory + **********************************************************************/ +uhd::property_tree::sptr uhd::property_tree::make(void){ +    return sptr(new property_tree_impl()); +} diff --git a/host/lib/transport/CMakeLists.txt b/host/lib/transport/CMakeLists.txt new file mode 100644 index 000000000..866ade75f --- /dev/null +++ b/host/lib/transport/CMakeLists.txt @@ -0,0 +1,110 @@ +# +# 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 +######################################################################## + +######################################################################## +# Setup libusb +######################################################################## +MESSAGE(STATUS "") +FIND_PACKAGE(USB1) + +LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF) + +IF(ENABLE_USB) +    MESSAGE(STATUS "USB support enabled via libusb.") +    INCLUDE_DIRECTORIES(${LIBUSB_INCLUDE_DIR}) +    LIBUHD_APPEND_LIBS(${LIBUSB_LIBRARIES}) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/libusb1_control.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/libusb1_zero_copy.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/libusb1_base.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/libusb1_base.hpp +    ) +ELSE(ENABLE_USB) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/usb_dummy_impl.cpp +    ) +ENDIF(ENABLE_USB) + +######################################################################## +# Setup defines for interface address discovery +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring interface address discovery...") +INCLUDE(CheckCXXSourceCompiles) +INCLUDE(CheckIncludeFileCXX) + +CHECK_CXX_SOURCE_COMPILES(" +    #include <ifaddrs.h> +    int main(){ +        struct ifaddrs *ifap; +        getifaddrs(&ifap); +        return 0; +    } +    " HAVE_GETIFADDRS +) + +CHECK_INCLUDE_FILE_CXX(winsock2.h HAVE_WINSOCK2_H) + +IF(HAVE_GETIFADDRS) +    MESSAGE(STATUS "  Interface address discovery supported through getifaddrs.") +    SET(IF_ADDRS_DEFS HAVE_GETIFADDRS) +ELSEIF(HAVE_WINSOCK2_H) +    MESSAGE(STATUS "  Interface address discovery supported through SIO_GET_INTERFACE_LIST.") +    SET(IF_ADDRS_DEFS HAVE_SIO_GET_INTERFACE_LIST) +ELSE() +    MESSAGE(STATUS "  Interface address discovery not supported.") +    SET(IF_ADDRS_DEFS HAVE_IF_ADDRS_DUMMY) +ENDIF() + +SET_SOURCE_FILES_PROPERTIES( +    ${CMAKE_CURRENT_SOURCE_DIR}/if_addrs.cpp +    PROPERTIES COMPILE_DEFINITIONS "${IF_ADDRS_DEFS}" +) + +######################################################################## +# Setup UDP +######################################################################## +IF(WIN32) +    LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_SOURCE_DIR}/udp_wsa_zero_copy.cpp) +ELSE() +    LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_SOURCE_DIR}/udp_zero_copy.cpp) +ENDIF() + +#On windows, the boost asio implementation uses the winsock2 library. +#Note: we exclude the .lib extension for cygwin and mingw platforms. +IF(WIN32) +    LIBUHD_APPEND_LIBS(ws2_32) +ENDIF() + +######################################################################## +# Append to the list of sources for lib uhd +######################################################################## +LIBUHD_PYTHON_GEN_SOURCE( +    ${CMAKE_CURRENT_SOURCE_DIR}/gen_vrt_if_packet.py +    ${CMAKE_CURRENT_BINARY_DIR}/vrt_if_packet.cpp +) + +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/buffer_pool.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/if_addrs.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/udp_simple.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/usb_zero_copy_wrapper.cpp +) diff --git a/host/lib/transport/buffer_pool.cpp b/host/lib/transport/buffer_pool.cpp new file mode 100644 index 000000000..971bbb75a --- /dev/null +++ b/host/lib/transport/buffer_pool.cpp @@ -0,0 +1,80 @@ +// +// Copyright 2011-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/buffer_pool.hpp> +#include <boost/shared_array.hpp> +#include <vector> + +using namespace uhd::transport; + +//! pad the byte count to a multiple of alignment +static size_t pad_to_boundary(const size_t bytes, const size_t alignment){ +    return bytes + (alignment - bytes)%alignment; +} + +/*********************************************************************** + * Buffer pool implementation + **********************************************************************/ +class buffer_pool_impl : public buffer_pool{ +public: +    buffer_pool_impl( +        const std::vector<ptr_type> &ptrs, +        boost::shared_array<char> mem +    ): _ptrs(ptrs), _mem(mem){ +        /* NOP */ +    } + +    ptr_type at(const size_t index) const{ +        return _ptrs.at(index); +    } + +    size_t size(void) const{ +        return _ptrs.size(); +    } + +private: +    std::vector<ptr_type> _ptrs; +    boost::shared_array<char> _mem; +}; + +/*********************************************************************** + * Buffer pool factor function + **********************************************************************/ +buffer_pool::sptr buffer_pool::make( +    const size_t num_buffs, +    const size_t buff_size, +    const size_t alignment +){ +    //1) pad the buffer size to be a multiple of alignment +    //2) pad the overall memory size for room after alignment +    //3) allocate the memory in one block of sufficient size +    const size_t padded_buff_size = pad_to_boundary(buff_size, alignment); +    boost::shared_array<char> mem(new char[padded_buff_size*num_buffs + alignment-1]); + +    //Fill a vector with boundary-aligned points in the memory +    const size_t mem_start = pad_to_boundary(size_t(mem.get()), alignment); +    std::vector<ptr_type> ptrs(num_buffs); +    for (size_t i = 0; i < num_buffs; i++){ +        ptrs[i] = ptr_type(mem_start + padded_buff_size*i); +    } + +    //Create a new buffer pool implementation with: +    // - the pre-computed pointers, and +    // - the reference to allocated memory. +    return sptr(new buffer_pool_impl(ptrs, mem)); +} + diff --git a/host/lib/transport/gen_vrt_if_packet.py b/host/lib/transport/gen_vrt_if_packet.py new file mode 100755 index 000000000..5f048d8c7 --- /dev/null +++ b/host/lib/transport/gen_vrt_if_packet.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python +# +# 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/>. +# + +""" +The vrt packer/unpacker code generator: + +This script will generate the pack and unpack routines that convert +metatdata into vrt headers and vrt headers into metadata. + +The generated code infers jump tables to speed-up the parsing time. +""" + +TMPL_TEXT = """ +#import time +/*********************************************************************** + * This file was generated by $file on $time.strftime("%c") + **********************************************************************/ + +\#include <uhd/exception.hpp> +\#include <uhd/transport/vrt_if_packet.hpp> +\#include <uhd/utils/byteswap.hpp> +\#include <boost/detail/endian.hpp> +\#include <vector> + +//define the endian macros to convert integers +\#ifdef BOOST_BIG_ENDIAN +    \#define BE_MACRO(x) (x) +    \#define LE_MACRO(x) uhd::byteswap(x) +\#else +    \#define BE_MACRO(x) uhd::byteswap(x) +    \#define LE_MACRO(x) (x) +\#endif + +using namespace uhd; +using namespace uhd::transport; + +typedef size_t pred_type; +typedef std::vector<pred_type> pred_table_type; +#define pred_table_index(hdr) ((hdr >> 20) & 0x1ff) + +static pred_table_type get_pred_unpack_table(void){ +    pred_table_type table(1 << 9, 0); //only 9 bits useful here (20-28) +    for (size_t i = 0; i < table.size(); i++){ +        boost::uint32_t vrt_hdr_word = i << 20; +        if(vrt_hdr_word & $hex(0x1 << 28)) table[i] |= $hex($sid_p); +        if(vrt_hdr_word & $hex(0x1 << 27)) table[i] |= $hex($cid_p); +        if(vrt_hdr_word & $hex(0x3 << 22)) table[i] |= $hex($tsi_p); +        if(vrt_hdr_word & $hex(0x3 << 20)) table[i] |= $hex($tsf_p); +        if(vrt_hdr_word & $hex(0x1 << 26)) table[i] |= $hex($tlr_p); +        if(vrt_hdr_word & $hex(0x1 << 24)) table[i] |= $hex($eob_p); +        if(vrt_hdr_word & $hex(0x1 << 25)) table[i] |= $hex($sob_p); +    } +    return table; +} + +static const pred_table_type pred_unpack_table(get_pred_unpack_table()); + +######################################################################## +#def gen_code($XE_MACRO, $suffix) +######################################################################## + +void vrt::if_hdr_pack_$(suffix)( +    boost::uint32_t *packet_buff, +    if_packet_info_t &if_packet_info +){ +    boost::uint32_t vrt_hdr_flags = 0; + +    pred_type pred = 0; +    if (if_packet_info.has_sid) pred |= $hex($sid_p); +    if (if_packet_info.has_cid) pred |= $hex($cid_p); +    if (if_packet_info.has_tsi) pred |= $hex($tsi_p); +    if (if_packet_info.has_tsf) pred |= $hex($tsf_p); +    if (if_packet_info.has_tlr) pred |= $hex($tlr_p); +    if (if_packet_info.eob)     pred |= $hex($eob_p); +    if (if_packet_info.sob)     pred |= $hex($sob_p); + +    switch(pred){ +    #for $pred in range(2**7) +    case $pred: +        #set $num_header_words = 1 +        #set $flags = 0 +        ########## Stream ID ########## +        #if $pred & $sid_p +            packet_buff[$num_header_words] = $(XE_MACRO)(if_packet_info.sid); +            #set $num_header_words += 1 +            #set $flags |= (0x1 << 28); +        #end if +        ########## Class ID ########## +        #if $pred & $cid_p +            packet_buff[$num_header_words] = 0; //not implemented +            #set $num_header_words += 1 +            packet_buff[$num_header_words] = 0; //not implemented +            #set $num_header_words += 1 +            #set $flags |= (0x1 << 27); +        #end if +        ########## Integer Time ########## +        #if $pred & $tsi_p +            packet_buff[$num_header_words] = $(XE_MACRO)(if_packet_info.tsi); +            #set $num_header_words += 1 +            #set $flags |= (0x3 << 22); +        #end if +        ########## Fractional Time ########## +        #if $pred & $tsf_p +            packet_buff[$num_header_words] = $(XE_MACRO)(boost::uint32_t(if_packet_info.tsf >> 32)); +            #set $num_header_words += 1 +            packet_buff[$num_header_words] = $(XE_MACRO)(boost::uint32_t(if_packet_info.tsf >> 0)); +            #set $num_header_words += 1 +            #set $flags |= (0x1 << 20); +        #end if +        ########## Trailer ########## +        #if $pred & $tlr_p +            //packet_buff[$num_header_words+if_packet_info.num_payload_words32] = $(XE_MACRO)(if_packet_info.tlr); +            #set $flags |= (0x1 << 26); +            #set $num_trailer_words = 1; +        #else +            #set $num_trailer_words = 0; +        #end if +        ########## Burst Flags ########## +        #if $pred & $eob_p +            #set $flags |= (0x1 << 24); +        #end if +        #if $pred & $sob_p +            #set $flags |= (0x1 << 25); +        #end if +        ########## Variables ########## +            if_packet_info.num_header_words32 = $num_header_words; +            if_packet_info.num_packet_words32 = $($num_header_words + $num_trailer_words) + if_packet_info.num_payload_words32; +            vrt_hdr_flags = $hex($flags); +        break; +    #end for +    } + +    //fill in complete header word +    packet_buff[0] = $(XE_MACRO)(boost::uint32_t(0 +        | (if_packet_info.packet_type << 29) +        | vrt_hdr_flags +        | ((if_packet_info.packet_count & 0xf) << 16) +        | (if_packet_info.num_packet_words32 & 0xffff) +    )); +} + +void vrt::if_hdr_unpack_$(suffix)( +    const boost::uint32_t *packet_buff, +    if_packet_info_t &if_packet_info +){ +    //extract vrt header +    boost::uint32_t vrt_hdr_word = $(XE_MACRO)(packet_buff[0]); +    size_t packet_words32 = vrt_hdr_word & 0xffff; + +    //failure case +    if (if_packet_info.num_packet_words32 < packet_words32) +        throw uhd::value_error("bad vrt header or packet fragment"); + +    //extract fields from the header +    if_packet_info.packet_type = if_packet_info_t::packet_type_t(vrt_hdr_word >> 29); +    if_packet_info.packet_count = (vrt_hdr_word >> 16) & 0xf; + +    const pred_type pred = pred_unpack_table[pred_table_index(vrt_hdr_word)]; + +    switch(pred){ +    #for $pred in range(2**7) +    case $pred: +        #set $has_time_spec = False +        #set $num_header_words = 1 +        ########## Stream ID ########## +        #if $pred & $sid_p +            if_packet_info.has_sid = true; +            if_packet_info.sid = $(XE_MACRO)(packet_buff[$num_header_words]); +            #set $num_header_words += 1 +        #else +            if_packet_info.has_sid = false; +        #end if +        ########## Class ID ########## +        #if $pred & $cid_p +            if_packet_info.has_cid = true; +            if_packet_info.cid = 0; //not implemented +            #set $num_header_words += 2 +        #else +            if_packet_info.has_cid = false; +        #end if +        ########## Integer Time ########## +        #if $pred & $tsi_p +            if_packet_info.has_tsi = true; +            if_packet_info.tsi = $(XE_MACRO)(packet_buff[$num_header_words]); +            #set $num_header_words += 1 +        #else +            if_packet_info.has_tsi = false; +        #end if +        ########## Fractional Time ########## +        #if $pred & $tsf_p +            if_packet_info.has_tsf = true; +            if_packet_info.tsf = boost::uint64_t($(XE_MACRO)(packet_buff[$num_header_words])) << 32; +            #set $num_header_words += 1 +            if_packet_info.tsf |= $(XE_MACRO)(packet_buff[$num_header_words]); +            #set $num_header_words += 1 +        #else +            if_packet_info.has_tsf = false; +        #end if +        ########## Trailer ########## +        #if $pred & $tlr_p +            if_packet_info.has_tlr = true; +            if_packet_info.tlr = $(XE_MACRO)(packet_buff[packet_words32-1]); +            #set $num_trailer_words = 1; +        #else +            if_packet_info.has_tlr = false; +            #set $num_trailer_words = 0; +        #end if +        ########## Burst Flags ########## +        #if $pred & $eob_p +            if_packet_info.eob = true; +        #else +            if_packet_info.eob = false; +        #end if +        #if $pred & $sob_p +            if_packet_info.sob = true; +        #else +            if_packet_info.sob = false; +        #end if +        ########## Variables ########## +            //another failure case +            if (packet_words32 < $($num_header_words + $num_trailer_words)) +                throw uhd::value_error("bad vrt header or invalid packet length"); +            if_packet_info.num_header_words32 = $num_header_words; +            if_packet_info.num_payload_words32 = packet_words32 - $($num_header_words + $num_trailer_words); +        break; +    #end for +    } +} + +######################################################################## +#end def +######################################################################## + +$gen_code("BE_MACRO", "be") +$gen_code("LE_MACRO", "le") +""" + +def parse_tmpl(_tmpl_text, **kwargs): +    from Cheetah.Template import Template +    return str(Template(_tmpl_text, kwargs)) + +if __name__ == '__main__': +    import sys +    open(sys.argv[1], 'w').write(parse_tmpl( +        TMPL_TEXT, +        file=__file__, +        sid_p = 0b0000001, +        cid_p = 0b0000010, +        tsi_p = 0b0000100, +        tsf_p = 0b0001000, +        tlr_p = 0b0010000, +        sob_p = 0b0100000, +        eob_p = 0b1000000, +    )) diff --git a/host/lib/transport/if_addrs.cpp b/host/lib/transport/if_addrs.cpp new file mode 100644 index 000000000..83a1ee56f --- /dev/null +++ b/host/lib/transport/if_addrs.cpp @@ -0,0 +1,110 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/if_addrs.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/cstdint.hpp> +#include <iostream> + +/*********************************************************************** + * Interface address discovery through ifaddrs api + **********************************************************************/ +#ifdef HAVE_GETIFADDRS +#include <ifaddrs.h> + +static boost::asio::ip::address_v4 sockaddr_to_ip_addr(sockaddr *addr){ +    return boost::asio::ip::address_v4(ntohl( +        reinterpret_cast<sockaddr_in*>(addr)->sin_addr.s_addr +    )); +} + +std::vector<uhd::transport::if_addrs_t> uhd::transport::get_if_addrs(void){ +    std::vector<if_addrs_t> if_addrs; +    struct ifaddrs *ifap; +    if (getifaddrs(&ifap) == 0){ +        for (struct ifaddrs *iter = ifap; iter != NULL; iter = iter->ifa_next){ +            //ensure that the entries are valid +            if (iter->ifa_addr == NULL) continue; +            if (iter->ifa_addr->sa_family != AF_INET) continue; +            if (iter->ifa_netmask->sa_family != AF_INET) continue; +            if (iter->ifa_broadaddr->sa_family != AF_INET) continue; + +            //append a new set of interface addresses +            if_addrs_t if_addr; +            if_addr.inet = sockaddr_to_ip_addr(iter->ifa_addr).to_string(); +            if_addr.mask = sockaddr_to_ip_addr(iter->ifa_netmask).to_string(); +            if_addr.bcast = sockaddr_to_ip_addr(iter->ifa_broadaddr).to_string(); +            if_addrs.push_back(if_addr); +        } +        freeifaddrs(ifap); +    } +    return if_addrs; +} + +#endif /* HAVE_GETIFADDRS */ + +/*********************************************************************** + * Interface address discovery through windows api + **********************************************************************/ +#ifdef HAVE_SIO_GET_INTERFACE_LIST +#include <winsock2.h> + +std::vector<uhd::transport::if_addrs_t> uhd::transport::get_if_addrs(void){ +    std::vector<if_addrs_t> if_addrs; +    SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0); +    if (sd == SOCKET_ERROR) { +        std::cerr << "Failed to get a socket. Error " << WSAGetLastError() << +            std::endl; return if_addrs; +    } + +    INTERFACE_INFO InterfaceList[20]; +    unsigned long nBytesReturned; +    if (WSAIoctl(sd, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList, +			sizeof(InterfaceList), &nBytesReturned, 0, 0) == SOCKET_ERROR) { +        std::cerr << "Failed calling WSAIoctl: error " << WSAGetLastError() << +				std::endl; +		return if_addrs; +    } + +    int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO); +    for (int i = 0; i < nNumInterfaces; ++i) { +        boost::uint32_t iiAddress = ntohl(reinterpret_cast<sockaddr_in&>(InterfaceList[i].iiAddress).sin_addr.s_addr); +        boost::uint32_t iiNetmask = ntohl(reinterpret_cast<sockaddr_in&>(InterfaceList[i].iiNetmask).sin_addr.s_addr); +        boost::uint32_t iiBroadcastAddress = (iiAddress & iiNetmask) | ~iiNetmask; + +        if_addrs_t if_addr; +        if_addr.inet = boost::asio::ip::address_v4(iiAddress).to_string(); +        if_addr.mask = boost::asio::ip::address_v4(iiNetmask).to_string(); +        if_addr.bcast = boost::asio::ip::address_v4(iiBroadcastAddress).to_string(); +        if_addrs.push_back(if_addr); +    } + +    return if_addrs; +} + +#endif /* HAVE_SIO_GET_INTERFACE_LIST */ + +/*********************************************************************** + * Interface address discovery not included + **********************************************************************/ +#ifdef HAVE_IF_ADDRS_DUMMY + +std::vector<uhd::transport::if_addrs_t> uhd::transport::get_if_addrs(void){ +    return std::vector<if_addrs_t>(); +} + +#endif /* HAVE_IF_ADDRS_DUMMY */ diff --git a/host/lib/transport/libusb1_base.cpp b/host/lib/transport/libusb1_base.cpp new file mode 100644 index 000000000..9b4290c08 --- /dev/null +++ b/host/lib/transport/libusb1_base.cpp @@ -0,0 +1,265 @@ +// +// 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 "libusb1_base.hpp" +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/foreach.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::transport; + +/*********************************************************************** + * libusb session + **********************************************************************/ +class libusb_session_impl : public libusb::session{ +public: +    libusb_session_impl(void){ +        UHD_ASSERT_THROW(libusb_init(&_context) == 0); +        libusb_set_debug(_context, debug_level); +    } + +    ~libusb_session_impl(void){ +        libusb_exit(_context); +    } + +    libusb_context *get_context(void) const{ +        return _context; +    } + +private: +    libusb_context *_context; +}; + +libusb::session::sptr libusb::session::get_global_session(void){ +    static boost::weak_ptr<session> global_session; + +    //not expired -> get existing session +    if (not global_session.expired()) return global_session.lock(); + +    //create a new global session +    sptr new_global_session(new libusb_session_impl()); +    global_session = new_global_session; +    return new_global_session; +} + +/*********************************************************************** + * libusb device + **********************************************************************/ +class libusb_device_impl : public libusb::device{ +public: +    libusb_device_impl(libusb_device *dev){ +        _session = libusb::session::get_global_session(); +        _dev = dev; +    } + +    ~libusb_device_impl(void){ +        libusb_unref_device(this->get()); +    } + +    libusb_device *get(void) const{ +        return _dev; +    } + +private: +    libusb::session::sptr _session; //always keep a reference to session +    libusb_device *_dev; +}; + +/*********************************************************************** + * libusb device list + **********************************************************************/ +class libusb_device_list_impl : public libusb::device_list{ +public: +    libusb_device_list_impl(void){ +        libusb::session::sptr sess = libusb::session::get_global_session(); + +        //allocate a new list of devices +        libusb_device** dev_list; +        ssize_t ret = libusb_get_device_list(sess->get_context(), &dev_list); +        if (ret < 0) throw uhd::os_error("cannot enumerate usb devices"); + +        //fill the vector of device references +        for (size_t i = 0; i < size_t(ret); i++) _devs.push_back( +            libusb::device::sptr(new libusb_device_impl(dev_list[i])) +        ); + +        //free the device list but dont unref (done in ~device) +        libusb_free_device_list(dev_list, false/*dont unref*/); +    } + +    size_t size(void) const{ +        return _devs.size(); +    } + +    libusb::device::sptr at(size_t i) const{ +        return _devs.at(i); +    } + +private: +    std::vector<libusb::device::sptr> _devs; +}; + +libusb::device_list::sptr libusb::device_list::make(void){ +    return sptr(new libusb_device_list_impl()); +} + +/*********************************************************************** + * libusb device descriptor + **********************************************************************/ +class libusb_device_descriptor_impl : public libusb::device_descriptor{ +public: +    libusb_device_descriptor_impl(libusb::device::sptr dev){ +        _dev = dev; +        UHD_ASSERT_THROW(libusb_get_device_descriptor(_dev->get(), &_desc) == 0); +    } + +    const libusb_device_descriptor &get(void) const{ +        return _desc; +    } + +    std::string get_ascii_serial(void) const{ +        if (this->get().iSerialNumber == 0) return ""; + +        libusb::device_handle::sptr handle( +            libusb::device_handle::get_cached_handle(_dev) +        ); + +        unsigned char buff[512]; +        ssize_t ret = libusb_get_string_descriptor_ascii( +            handle->get(), this->get().iSerialNumber, buff, sizeof(buff) +        ); +        if (ret < 0) return ""; //on error, just return empty string + +        return std::string((char *)buff, ret); +    } + +private: +    libusb::device::sptr _dev; //always keep a reference to device +    libusb_device_descriptor _desc; +}; + +libusb::device_descriptor::sptr libusb::device_descriptor::make(device::sptr dev){ +    return sptr(new libusb_device_descriptor_impl(dev)); +} + +/*********************************************************************** + * libusb device handle + **********************************************************************/ +class libusb_device_handle_impl : public libusb::device_handle{ +public: +    libusb_device_handle_impl(libusb::device::sptr dev){ +        _dev = dev; +        UHD_ASSERT_THROW(libusb_open(_dev->get(), &_handle) == 0); +    } + +    ~libusb_device_handle_impl(void){ +        //release all claimed interfaces +        for (size_t i = 0; i < _claimed.size(); i++){ +            libusb_release_interface(this->get(), _claimed[i]); +        } +        libusb_close(_handle); +    } + +    libusb_device_handle *get(void) const{ +        return _handle; +    } + +    void claim_interface(int interface){ +        UHD_ASSERT_THROW(libusb_claim_interface(this->get(), interface) == 0); +        _claimed.push_back(interface); +    } + +private: +    libusb::device::sptr _dev; //always keep a reference to device +    libusb_device_handle *_handle; +    std::vector<int> _claimed; +}; + +libusb::device_handle::sptr libusb::device_handle::get_cached_handle(device::sptr dev){ +    static uhd::dict<libusb_device *, boost::weak_ptr<device_handle> > handles; + +    //not expired -> get existing handle +    if (handles.has_key(dev->get()) and not handles[dev->get()].expired()){ +        return handles[dev->get()].lock(); +    } + +    //create a new cached handle +    try{ +        sptr new_handle(new libusb_device_handle_impl(dev)); +        handles[dev->get()] = new_handle; +        return new_handle; +    } +    catch(const uhd::exception &){ +        std::cerr << "USB open failed: see the application notes for your device." << std::endl; +        throw; +    } +} + +/*********************************************************************** + * libusb special handle + **********************************************************************/ +class libusb_special_handle_impl : public libusb::special_handle{ +public: +    libusb_special_handle_impl(libusb::device::sptr dev){ +        _dev = dev; +    } + +    libusb::device::sptr get_device(void) const{ +        return _dev; +    } + +    std::string get_serial(void) const{ +        return libusb::device_descriptor::make(this->get_device())->get_ascii_serial(); +    } + +    boost::uint16_t get_vendor_id(void) const{ +        return libusb::device_descriptor::make(this->get_device())->get().idVendor; +    } + +    boost::uint16_t get_product_id(void) const{ +        return libusb::device_descriptor::make(this->get_device())->get().idProduct; +    } + +private: +    libusb::device::sptr _dev; //always keep a reference to device +}; + +libusb::special_handle::sptr libusb::special_handle::make(device::sptr dev){ +    return sptr(new libusb_special_handle_impl(dev)); +} + +/*********************************************************************** + * list device handles implementations + **********************************************************************/ +std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list( +    boost::uint16_t vid, boost::uint16_t pid +){ +    std::vector<usb_device_handle::sptr> handles; + +    libusb::device_list::sptr dev_list = libusb::device_list::make(); +    for (size_t i = 0; i < dev_list->size(); i++){ +        usb_device_handle::sptr handle = libusb::special_handle::make(dev_list->at(i)); +        if (handle->get_vendor_id() == vid and handle->get_product_id() == pid){ +            handles.push_back(handle); +        } +    } + +    return handles; +} diff --git a/host/lib/transport/libusb1_base.hpp b/host/lib/transport/libusb1_base.hpp new file mode 100644 index 000000000..04c1d6574 --- /dev/null +++ b/host/lib/transport/libusb1_base.hpp @@ -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/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_LIBUSB_HPP +#define INCLUDED_LIBUHD_TRANSPORT_LIBUSB_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/transport/usb_device_handle.hpp> +#include <libusb.h> + +/*********************************************************************** + * Libusb object oriented smart pointer wrappers: + * The following wrappers provide allocation and automatic deallocation + * for various libusb data types and handles. The construction routines + * also store tables of already allocated structures to avoid multiple + * occurrences of opened handles (for example). + **********************************************************************/ +namespace uhd { namespace transport { + +namespace libusb { + +    /*! +     * This session class holds a global libusb context for this process. +     * The get global session call will create a new context if none exists. +     * When all references to session are destroyed, the context will be freed. +     */ +    class session : boost::noncopyable { +    public: +        typedef boost::shared_ptr<session> sptr; + +        /*! +         *   Level 0: no messages ever printed by the library (default) +         *   Level 1: error messages are printed to stderr +         *   Level 2: warning and error messages are printed to stderr +         *   Level 3: informational messages are printed to stdout, warning +         *            and error messages are printed to stderr +         */ +        static const int debug_level = 0; + +        //! get a shared pointer to the global session +        static sptr get_global_session(void); + +        //! get the underlying libusb context pointer +        virtual libusb_context *get_context(void) const = 0; +    }; + +    /*! +     * Holds a device pointer with a reference to the session. +     */ +    class device : boost::noncopyable { +    public: +        typedef boost::shared_ptr<device> sptr; + +        //! get the underlying device pointer +        virtual libusb_device *get(void) const = 0; +    }; + +    /*! +     * This device list class holds a device list that will be +     * automatically freed when the last reference is destroyed. +     */ +    class device_list : boost::noncopyable { +    public: +        typedef boost::shared_ptr<device_list> sptr; + +        //! make a new device list +        static sptr make(void); + +        //! the number of devices in this list +        virtual size_t size() const = 0; + +        //! get the device pointer at a particular index +        virtual device::sptr at(size_t index) const = 0; +    }; + +    /*! +     * Holds a device descriptor and a reference to the device. +     */ +    class device_descriptor : boost::noncopyable { +    public: +        typedef boost::shared_ptr<device_descriptor> sptr; + +        //! make a new descriptor from a device reference +        static sptr make(device::sptr); + +        //! get the underlying device descriptor +        virtual const libusb_device_descriptor &get(void) const = 0; + +        virtual std::string get_ascii_serial(void) const = 0; +    }; + +    /*! +     * Holds a device handle and a reference to the device. +     */ +    class device_handle : boost::noncopyable { +    public: +        typedef boost::shared_ptr<device_handle> sptr; + +        //! get a cached handle or make a new one given the device +        static sptr get_cached_handle(device::sptr); + +        //! get the underlying device handle +        virtual libusb_device_handle *get(void) const = 0; + +        /*! +         * Open USB interfaces for control using magic value +         * IN interface:      2 +         * OUT interface:     1 +         * Control interface: 0 +         */ +        virtual void claim_interface(int) = 0; +    }; + +    /*! +     * The special handle is our internal implementation of the +     * usb device handle which is used publicly to identify a device. +     */ +    class special_handle : public usb_device_handle { +    public: +        typedef boost::shared_ptr<special_handle> sptr; + +        //! make a new special handle from device +        static sptr make(device::sptr); + +        //! get the underlying device reference +        virtual device::sptr get_device(void) const = 0; +    }; + +} + +}} //namespace + +#endif /* INCLUDED_LIBUHD_TRANSPORT_LIBUSB_HPP */ diff --git a/host/lib/transport/libusb1_control.cpp b/host/lib/transport/libusb1_control.cpp new file mode 100644 index 000000000..bce3d4b0b --- /dev/null +++ b/host/lib/transport/libusb1_control.cpp @@ -0,0 +1,67 @@ +// +// 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 "libusb1_base.hpp" +#include <uhd/transport/usb_control.hpp> +#include <boost/thread/mutex.hpp> + +using namespace uhd::transport; + +const int libusb_timeout = 0; + +/*********************************************************************** + * libusb-1.0 implementation of USB control transport + **********************************************************************/ +class libusb_control_impl : public usb_control { +public: +    libusb_control_impl(libusb::device_handle::sptr handle): +        _handle(handle) +    { +        _handle->claim_interface(0 /* control interface */); +    } + +    ssize_t submit(boost::uint8_t request_type, +                  boost::uint8_t request, +                  boost::uint16_t value, +                  boost::uint16_t index, +                  unsigned char *buff, +                  boost::uint16_t length +    ){ +        boost::mutex::scoped_lock lock(_mutex); +        return libusb_control_transfer(_handle->get(), +                                       request_type, +                                       request, +                                       value, +                                       index, +                                       buff, +                                       length, +                                       libusb_timeout); +    } + +private: +    libusb::device_handle::sptr _handle; +    boost::mutex _mutex; +}; + +/*********************************************************************** + * USB control public make functions + **********************************************************************/ +usb_control::sptr usb_control::make(usb_device_handle::sptr handle){ +    return sptr(new libusb_control_impl(libusb::device_handle::get_cached_handle( +        boost::static_pointer_cast<libusb::special_handle>(handle)->get_device() +    ))); +} diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp new file mode 100644 index 000000000..0fa856d34 --- /dev/null +++ b/host/lib/transport/libusb1_zero_copy.cpp @@ -0,0 +1,303 @@ +// +// 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 "libusb1_base.hpp" +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/exception.hpp> +#include <boost/function.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/thread.hpp> +#include <list> + +using namespace uhd; +using namespace uhd::transport; + +static const size_t DEFAULT_NUM_XFERS = 16;     //num xfers +static const size_t DEFAULT_XFER_SIZE = 32*512; //bytes + +//! Define LIBUSB_CALL when its missing (non-windows) +#ifndef LIBUSB_CALL +    #define LIBUSB_CALL +#endif /*LIBUSB_CALL*/ + +/*! + * All libusb callback functions should be marked with the LIBUSB_CALL macro + * to ensure that they are compiled with the same calling convention as libusb. + */ + +//! 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 || lut->status == LIBUSB_TRANSFER_TIMED_OUT) libusb_free_transfer(lut); +    else UHD_MSG(error) << "libusb cancel_transfer unexpected status " << lut->status << std::endl; +} + +/*********************************************************************** + * Reusable managed receiver buffer: + *  - Associated with a particular libusb transfer struct. + *  - Submits the transfer to libusb in the release method. + **********************************************************************/ +class libusb_zero_copy_mrb : public managed_recv_buffer{ +public: +    libusb_zero_copy_mrb(libusb_transfer *lut): +        _lut(lut), _expired(true) { /* NOP */ } + +    void release(void){ +        if (_expired) return; +        UHD_ASSERT_THROW(libusb_submit_transfer(_lut) == 0); +        _expired = true; +    } + +    sptr get_new(void){ +        _expired = false; +        return make_managed_buffer(this); +    } + +private: +    const void *get_buff(void) const{return _lut->buffer;} +    size_t get_size(void) const{return _lut->actual_length;} + +    libusb_transfer *_lut; +    bool _expired; +}; + +/*********************************************************************** + * Reusable managed send buffer: + *  - Associated with a particular libusb transfer struct. + *  - Submits the transfer to libusb in the commit method. + **********************************************************************/ +class libusb_zero_copy_msb : public managed_send_buffer{ +public: +    libusb_zero_copy_msb(libusb_transfer *lut): +        _lut(lut), _expired(true) { /* NOP */ } + +    void commit(size_t len){ +        if (_expired) return; +        _lut->length = len; +        if(len == 0) libusb_async_tx_cb(_lut); +        else UHD_ASSERT_THROW(libusb_submit_transfer(_lut) == 0); +        _expired = true; +    } + +    sptr get_new(void){ +        _expired = false; +        return make_managed_buffer(this); +    } + +private: +    void *get_buff(void) const{return _lut->buffer;} +    size_t get_size(void) const{return _lut->length;} + +    libusb_transfer *_lut; +    bool _expired; +}; + +/*********************************************************************** + * USB zero_copy device class + **********************************************************************/ +class libusb_zero_copy_impl : public usb_zero_copy{ +public: + +    libusb_zero_copy_impl( +        libusb::device_handle::sptr handle, +        size_t recv_endpoint, +        size_t send_endpoint, +        const device_addr_t &hints +    ): +        _handle(handle), +        _recv_frame_size(size_t(hints.cast<double>("recv_frame_size", DEFAULT_XFER_SIZE))), +        _num_recv_frames(size_t(hints.cast<double>("num_recv_frames", DEFAULT_NUM_XFERS))), +        _send_frame_size(size_t(hints.cast<double>("send_frame_size", DEFAULT_XFER_SIZE))), +        _num_send_frames(size_t(hints.cast<double>("num_send_frames", DEFAULT_NUM_XFERS))), +        _recv_buffer_pool(buffer_pool::make(_num_recv_frames, _recv_frame_size)), +        _send_buffer_pool(buffer_pool::make(_num_send_frames, _send_frame_size)), +        _pending_recv_buffs(_num_recv_frames), +        _pending_send_buffs(_num_send_frames) +    { +        _handle->claim_interface(2 /*in interface*/); +        _handle->claim_interface(1 /*out interface*/); + +        //allocate libusb transfer structs and managed receive buffers +        for (size_t i = 0; i < get_num_recv_frames(); i++){ + +            libusb_transfer *lut = libusb_alloc_transfer(0); +            UHD_ASSERT_THROW(lut != NULL); + +            _mrb_pool.push_back(libusb_zero_copy_mrb(lut)); +            _callbacks.push_back(boost::bind( +                &libusb_zero_copy_impl::handle_recv, this, &_mrb_pool.back() +            )); + +            libusb_fill_bulk_transfer( +                lut,                                                    // transfer +                _handle->get(),                                         // dev_handle +                (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_rx_cb),             // callback +                static_cast<void *>(&_callbacks.back()),                // user_data +                0                                                       // timeout (ms) +            ); + +            _all_luts.push_back(lut); +            _mrb_pool.back().get_new(); +        } + +        //allocate libusb transfer structs and managed send buffers +        for (size_t i = 0; i < get_num_send_frames(); i++){ + +            libusb_transfer *lut = libusb_alloc_transfer(0); +            UHD_ASSERT_THROW(lut != NULL); + +            _msb_pool.push_back(libusb_zero_copy_msb(lut)); +            _callbacks.push_back(boost::bind( +                &libusb_zero_copy_impl::handle_send, this, &_msb_pool.back() +            )); + +            libusb_fill_bulk_transfer( +                lut,                                                    // transfer +                _handle->get(),                                         // dev_handle +                (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_tx_cb),             // callback +                static_cast<void *>(&_callbacks.back()),                // user_data +                0                                                       // timeout +            ); + +            _all_luts.push_back(lut); +            libusb_async_tx_cb(lut); +        } + +        //spawn the event handler threads +        const size_t concurrency = hints.cast<size_t>("concurrency_hint", 1); +        for (size_t i = 0; i < concurrency; i++) _event_loop_tasks.push_back(task::make( +            boost::bind(&libusb_zero_copy_impl::run_event_loop, this) +        )); +    } + +    ~libusb_zero_copy_impl(void){ +        //cancel and free all transfers +        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 +               && lut->status != LIBUSB_TRANSFER_TIMED_OUT) { +                boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +            } +        } +    } + +    managed_recv_buffer::sptr get_recv_buff(double timeout){ +        libusb_zero_copy_mrb *mrb = NULL; +        if (_pending_recv_buffs.pop_with_timed_wait(mrb, timeout)){ +            return mrb->get_new(); +        } +        return managed_recv_buffer::sptr(); +    } + +    managed_send_buffer::sptr get_send_buff(double timeout){ +        libusb_zero_copy_msb *msb = NULL; +        if (_pending_send_buffs.pop_with_timed_wait(msb, timeout)){ +            return msb->get_new(); +        } +        return managed_send_buffer::sptr(); +    } + +    size_t get_num_recv_frames(void) const { return _num_recv_frames; } +    size_t get_num_send_frames(void) const { return _num_send_frames; } + +    size_t get_recv_frame_size(void) const { return _recv_frame_size; } +    size_t get_send_frame_size(void) const { return _send_frame_size; } + +private: +    //! Handle a bound async callback for recv +    void handle_recv(libusb_zero_copy_mrb *mrb){ +        _pending_recv_buffs.push_with_haste(mrb); +    } + +    //! Handle a bound async callback for send +    void handle_send(libusb_zero_copy_msb *msb){ +        _pending_send_buffs.push_with_haste(msb); +    } + +    libusb::device_handle::sptr _handle; +    const size_t _recv_frame_size, _num_recv_frames; +    const size_t _send_frame_size, _num_send_frames; + +    //! Storage for transfer related objects +    buffer_pool::sptr _recv_buffer_pool, _send_buffer_pool; +    bounded_buffer<libusb_zero_copy_mrb *> _pending_recv_buffs; +    bounded_buffer<libusb_zero_copy_msb *> _pending_send_buffs; +    std::list<libusb_zero_copy_mrb> _mrb_pool; +    std::list<libusb_zero_copy_msb> _msb_pool; +    std::list<boost::function<void()> > _callbacks; + +    //! a list of all transfer structs we allocated +    std::list<libusb_transfer *> _all_luts; + +    //! event handler threads +    std::list<task::sptr> _event_loop_tasks; + +    void run_event_loop(void){ +        set_thread_priority_safe(); +        libusb_context *context = libusb::session::get_global_session()->get_context(); +        while (not boost::this_thread::interruption_requested()){ +            timeval tv; +            tv.tv_sec = 0; +            tv.tv_usec = 100000; //100ms +            libusb_handle_events_timeout(context, &tv); +        } +    } + +}; + +/*********************************************************************** + * USB zero_copy make functions + **********************************************************************/ +usb_zero_copy::sptr usb_zero_copy::make( +    usb_device_handle::sptr handle, +    size_t recv_endpoint, +    size_t send_endpoint, +    const device_addr_t &hints +){ +    libusb::device_handle::sptr dev_handle(libusb::device_handle::get_cached_handle( +        boost::static_pointer_cast<libusb::special_handle>(handle)->get_device() +    )); +    return sptr(new libusb_zero_copy_impl( +        dev_handle, recv_endpoint, send_endpoint, hints +    )); +} diff --git a/host/lib/transport/super_recv_packet_handler.hpp b/host/lib/transport/super_recv_packet_handler.hpp new file mode 100644 index 000000000..15bd78242 --- /dev/null +++ b/host/lib/transport/super_recv_packet_handler.hpp @@ -0,0 +1,587 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_SUPER_RECV_PACKET_HANDLER_HPP +#define INCLUDED_LIBUHD_TRANSPORT_SUPER_RECV_PACKET_HANDLER_HPP + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/convert.hpp> +#include <uhd/device.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/types/io_type.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/dynamic_bitset.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <vector> + +namespace uhd{ namespace transport{ namespace sph{ + +UHD_INLINE boost::uint32_t get_context_code( +    const boost::uint32_t *vrt_hdr, const vrt::if_packet_info_t &if_packet_info +){ +    //extract the context word (we dont know the endianness so mirror the bytes) +    boost::uint32_t word0 = vrt_hdr[if_packet_info.num_header_words32] | +              uhd::byteswap(vrt_hdr[if_packet_info.num_header_words32]); +    return word0 & 0xff; +} + +typedef boost::function<void(void)> handle_overflow_type; +static inline void handle_overflow_nop(void){} + +/*********************************************************************** + * Super receive packet handler + * + * A receive packet handler represents a group of channels. + * The channel group shares a common sample rate. + * All channels are received in unison in recv(). + **********************************************************************/ +class recv_packet_handler{ +public: +    typedef boost::function<managed_recv_buffer::sptr(double)> get_buff_type; +    typedef void(*vrt_unpacker_type)(const boost::uint32_t *, vrt::if_packet_info_t &); +    //typedef boost::function<void(const boost::uint32_t *, vrt::if_packet_info_t &)> vrt_unpacker_type; + +    /*! +     * Make a new packet handler for receive +     * \param size the number of transport channels +     */ +    recv_packet_handler(const size_t size = 1): +        _queue_error_for_next_call(false), +        _buffers_infos_index(0) +    { +        this->resize(size); +        set_alignment_failure_threshold(1000); +        this->set_scale_factor(1/32767.); +    } + +    //! Resize the number of transport channels +    void resize(const size_t size){ +        if (this->size() == size) return; +        _props.resize(size); +        //re-initialize all buffers infos by re-creating the vector +        _buffers_infos = std::vector<buffers_info_type>(4, buffers_info_type(size)); +    } + +    //! Get the channel width of this handler +    size_t size(void) const{ +        return _props.size(); +    } + +    //! Setup the vrt unpacker function and offset +    void set_vrt_unpacker(const vrt_unpacker_type &vrt_unpacker, const size_t header_offset_words32 = 0){ +        _vrt_unpacker = vrt_unpacker; +        _header_offset_words32 = header_offset_words32; +    } + +    /*! +     * Set the threshold for alignment failure. +     * How many packets throw out before giving up? +     * \param threshold number of packets per channel +     */ +    void set_alignment_failure_threshold(const size_t threshold){ +        _alignment_faulure_threshold = threshold*this->size(); +    } + +    //! Set the rate of ticks per second +    void set_tick_rate(const double rate){ +        _tick_rate = rate; +    } + +    //! Set the rate of samples per second +    void set_samp_rate(const double rate){ +        _samp_rate = rate; +    } + +    /*! +     * Set the function to get a managed buffer. +     * \param xport_chan which transport channel +     * \param get_buff the getter function +     */ +    void set_xport_chan_get_buff(const size_t xport_chan, const get_buff_type &get_buff){ +        _props.at(xport_chan).get_buff = get_buff; +    } + +    /*! +     * Setup the conversion functions (homogeneous across transports). +     * Here, we load a table of converters for all possible io types. +     * This makes the converter look-up an O(1) operation. +     * \param otw_type the channel data type +     * \param width the streams per channel (usually 1) +     */ +    void set_converter(const uhd::otw_type_t &otw_type, const size_t width = 1){ +        _io_buffs.resize(width); +        _converters.resize(128); +        for (size_t io_type = 0; io_type < _converters.size(); io_type++){ +            try{ +                _converters[io_type] = uhd::convert::get_converter_otw_to_cpu( +                    io_type_t::tid_t(io_type), otw_type, 1, width +                ); +            }catch(const uhd::value_error &){} //we expect this, not all io_types valid... +        } +        _bytes_per_item = otw_type.get_sample_size(); +    } + +    //! Set the transport channel's overflow handler +    void set_overflow_handler(const size_t xport_chan, const handle_overflow_type &handle_overflow){ +        _props.at(xport_chan).handle_overflow = handle_overflow; +    } + +    //! Get a scoped lock object for this instance +    boost::mutex::scoped_lock get_scoped_lock(void){ +        return boost::mutex::scoped_lock(_mutex); +    } + +    //! Set the scale factor used in float conversion +    void set_scale_factor(const double scale_factor){ +        _scale_factor = scale_factor; +    } + +    /******************************************************************* +     * Receive: +     * The entry point for the fast-path receive calls. +     * Dispatch into combinations of single packet receive calls. +     ******************************************************************/ +    UHD_INLINE size_t recv( +        const uhd::device::recv_buffs_type &buffs, +        const size_t nsamps_per_buff, +        uhd::rx_metadata_t &metadata, +        const uhd::io_type_t &io_type, +        uhd::device::recv_mode_t recv_mode, +        double timeout +    ){ +        boost::mutex::scoped_lock lock(_mutex); + +        //handle metadata queued from a previous receive +        if (_queue_error_for_next_call){ +            _queue_error_for_next_call = false; +            metadata = _queue_metadata; +            //We want to allow a full buffer recv to be cut short by a timeout, +            //but do not want to generate an inline timeout message packet. +            if (_queue_metadata.error_code != rx_metadata_t::ERROR_CODE_TIMEOUT) return 0; +        } + +        switch(recv_mode){ + +        //////////////////////////////////////////////////////////////// +        case uhd::device::RECV_MODE_ONE_PACKET:{ +        //////////////////////////////////////////////////////////////// +            return recv_one_packet(buffs, nsamps_per_buff, metadata, io_type, timeout); +        } + +        //////////////////////////////////////////////////////////////// +        case uhd::device::RECV_MODE_FULL_BUFF:{ +        //////////////////////////////////////////////////////////////// +            size_t accum_num_samps = recv_one_packet( +                buffs, nsamps_per_buff, metadata, io_type, timeout +            ); + +            //first recv had an error code set, return immediately +            if (metadata.error_code != rx_metadata_t::ERROR_CODE_NONE) return accum_num_samps; + +            //loop until buffer is filled or error code +            while(accum_num_samps < nsamps_per_buff){ +                size_t num_samps = recv_one_packet( +                    buffs, nsamps_per_buff - accum_num_samps, _queue_metadata, +                    io_type, timeout, accum_num_samps*io_type.size +                ); + +                //metadata had an error code set, store for next call and return +                if (_queue_metadata.error_code != rx_metadata_t::ERROR_CODE_NONE){ +                    _queue_error_for_next_call = true; +                    break; +                } +                accum_num_samps += num_samps; +            } +            return accum_num_samps; +        } + +        default: throw uhd::value_error("unknown recv mode"); +        }//switch(recv_mode) +    } + +private: + +    boost::mutex _mutex; +    vrt_unpacker_type _vrt_unpacker; +    size_t _header_offset_words32; +    double _tick_rate, _samp_rate; +    bool _queue_error_for_next_call; +    size_t _alignment_faulure_threshold; +    rx_metadata_t _queue_metadata; +    struct xport_chan_props_type{ +        xport_chan_props_type(void): +            packet_count(0), +            handle_overflow(&handle_overflow_nop) +        {} +        get_buff_type get_buff; +        size_t packet_count; +        handle_overflow_type handle_overflow; +    }; +    std::vector<xport_chan_props_type> _props; +    std::vector<void *> _io_buffs; //used in conversion +    size_t _bytes_per_item; //used in conversion +    std::vector<uhd::convert::function_type> _converters; //used in conversion +    double _scale_factor; + +    //! information stored for a received buffer +    struct per_buffer_info_type{ +        managed_recv_buffer::sptr buff; +        const boost::uint32_t *vrt_hdr; +        vrt::if_packet_info_t ifpi; +        time_spec_t time; +        const char *copy_buff; +    }; + +    //!information stored for a set of aligned buffers +    struct buffers_info_type : std::vector<per_buffer_info_type> { +        buffers_info_type(const size_t size): +            std::vector<per_buffer_info_type>(size), +            indexes_todo(size, true), +            alignment_time_valid(false), +            data_bytes_to_copy(0), +            fragment_offset_in_samps(0) +        {/* NOP */} +        boost::dynamic_bitset<> indexes_todo; //used in alignment logic +        time_spec_t alignment_time; //used in alignment logic +        bool alignment_time_valid; //used in alignment logic +        size_t data_bytes_to_copy; //keeps track of state +        size_t fragment_offset_in_samps; //keeps track of state +        rx_metadata_t metadata; //packet description +    }; + +    //! a circular queue of buffer infos +    std::vector<buffers_info_type> _buffers_infos; +    size_t _buffers_infos_index; +    buffers_info_type &get_curr_buffer_info(void){return _buffers_infos[_buffers_infos_index];} +    buffers_info_type &get_prev_buffer_info(void){return _buffers_infos[(_buffers_infos_index + 3)%4];} +    buffers_info_type &get_next_buffer_info(void){return _buffers_infos[(_buffers_infos_index + 1)%4];} +    void increment_buffer_info(void){_buffers_infos_index = (_buffers_infos_index + 1)%4;} + +    //! possible return options for the packet receiver +    enum packet_type{ +        PACKET_IF_DATA, +        PACKET_TIMESTAMP_ERROR, +        PACKET_INLINE_MESSAGE, +        PACKET_TIMEOUT_ERROR, +        PACKET_SEQUENCE_ERROR +    }; + +    /******************************************************************* +     * Get and process a single packet from the transport: +     * Receive a single packet at the given index. +     * Extract all the relevant info and store. +     * Check the info to determine the return code. +     ******************************************************************/ +    UHD_INLINE packet_type get_and_process_single_packet( +        const size_t index, +        buffers_info_type &prev_buffer_info, +        buffers_info_type &curr_buffer_info, +        double timeout +    ){ +        //get a single packet from the transport layer +        managed_recv_buffer::sptr &buff = curr_buffer_info[index].buff; +        buff = _props[index].get_buff(timeout); +        if (buff.get() == NULL) return PACKET_TIMEOUT_ERROR; + +        //bounds check before extract +        size_t num_packet_words32 = buff->size()/sizeof(boost::uint32_t); +        if (num_packet_words32 <= _header_offset_words32){ +            throw std::runtime_error("recv buffer smaller than vrt packet offset"); +        } + +        //extract packet info +        per_buffer_info_type &info = curr_buffer_info[index]; +        info.ifpi.num_packet_words32 = num_packet_words32 - _header_offset_words32; +        info.vrt_hdr = buff->cast<const boost::uint32_t *>() + _header_offset_words32; +        _vrt_unpacker(info.vrt_hdr, info.ifpi); +        info.time = time_spec_t(time_t(info.ifpi.tsi), size_t(info.ifpi.tsf), _tick_rate); //assumes has_tsi and has_tsf are true +        info.copy_buff = reinterpret_cast<const char *>(info.vrt_hdr + info.ifpi.num_header_words32); + +        //-------------------------------------------------------------- +        //-- Determine return conditions: +        //-- The order of these checks is HOLY. +        //-------------------------------------------------------------- + +        //1) check for inline IF message packets +        if (info.ifpi.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ +            return PACKET_INLINE_MESSAGE; +        } + +        //2) check for sequence errors +        #ifndef SRPH_DONT_CHECK_SEQUENCE +        const size_t expected_packet_count = _props[index].packet_count; +        _props[index].packet_count = (info.ifpi.packet_count + 1)%16; +        if (expected_packet_count != info.ifpi.packet_count){ +            return PACKET_SEQUENCE_ERROR; +        } +        #endif + +        //3) check for out of order timestamps +        if (info.ifpi.has_tsi and info.ifpi.has_tsf and prev_buffer_info[index].time > info.time){ +            return PACKET_TIMESTAMP_ERROR; +        } + +        //4) otherwise the packet is normal! +        return PACKET_IF_DATA; +    } + +    /******************************************************************* +     * Alignment check: +     * Check the received packet for alignment and mark accordingly. +     ******************************************************************/ +    UHD_INLINE void alignment_check( +        const size_t index, buffers_info_type &info +    ){ +        //if alignment time was not valid or if the sequence id is newer: +        //  use this index's time as the alignment time +        //  reset the indexes list and remove this index +        if (not info.alignment_time_valid or info[index].time > info.alignment_time){ +            info.alignment_time_valid = true; +            info.alignment_time = info[index].time; +            info.indexes_todo.set(); +            info.indexes_todo.reset(index); +            info.data_bytes_to_copy = info[index].ifpi.num_payload_words32*sizeof(boost::uint32_t); +        } + +        //if the sequence id matches: +        //  remove this index from the list and continue +        else if (info[index].time == info.alignment_time){ +            info.indexes_todo.reset(index); +        } + +        //if the sequence id is older: +        //  continue with the same index to try again +        //else if (info[index].time < info.alignment_time)... +    } + +    /******************************************************************* +     * Get aligned buffers: +     * Iterate through each index and try to accumulate aligned buffers. +     * Handle all of the edge cases like inline messages and errors. +     * The logic will throw out older packets until it finds a match. +     ******************************************************************/ +    UHD_INLINE void get_aligned_buffs(double timeout){ + +        increment_buffer_info(); //increment to next buffer +        buffers_info_type &prev_info = get_prev_buffer_info(); +        buffers_info_type &curr_info = get_curr_buffer_info(); +        buffers_info_type &next_info = get_next_buffer_info(); + +        //Loop until we get a message of an aligned set of buffers: +        // - Receive a single packet and extract its info. +        // - Handle the packet type yielded by the receive. +        // - Check the timestamps for alignment conditions. +        size_t iterations = 0; +        while (curr_info.indexes_todo.any()){ + +            //get the index to process for this iteration +            const size_t index = curr_info.indexes_todo.find_first(); +            packet_type packet; + +            //receive a single packet from the transport +            try{ +                packet = get_and_process_single_packet( +                    index, prev_info, curr_info, timeout +                ); +            } + +            //handle the case when the get packet throws +            catch(const std::exception &e){ +                UHD_MSG(error) << boost::format( +                    "The receive packet handler caught an exception.\n%s" +                ) % e.what() << std::endl; +                std::swap(curr_info, next_info); //save progress from curr -> next +                curr_info.metadata.has_time_spec = false; +                curr_info.metadata.time_spec = time_spec_t(0.0); +                curr_info.metadata.more_fragments = false; +                curr_info.metadata.fragment_offset = 0; +                curr_info.metadata.start_of_burst = false; +                curr_info.metadata.end_of_burst = false; +                curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_BAD_PACKET; +                return; +            } + +            switch(packet){ +            case PACKET_IF_DATA: +                alignment_check(index, curr_info); +                break; + +            case PACKET_TIMESTAMP_ERROR: +                //If the user changes the device time while streaming or without flushing, +                //we can receive a packet that comes before the previous packet in time. +                //This could cause the alignment logic to discard future received packets. +                //Therefore, when this occurs, we reset the info to restart from scratch. +                if (curr_info.alignment_time_valid and curr_info.alignment_time != curr_info[index].time){ +                    curr_info.alignment_time_valid = false; +                } +                alignment_check(index, curr_info); +                break; + +            case PACKET_INLINE_MESSAGE: +                std::swap(curr_info, next_info); //save progress from curr -> next +                curr_info.metadata.has_time_spec = next_info[index].ifpi.has_tsi and next_info[index].ifpi.has_tsf; +                curr_info.metadata.time_spec = next_info[index].time; +                curr_info.metadata.more_fragments = false; +                curr_info.metadata.fragment_offset = 0; +                curr_info.metadata.start_of_burst = false; +                curr_info.metadata.end_of_burst = false; +                curr_info.metadata.error_code = rx_metadata_t::error_code_t(get_context_code(next_info[index].vrt_hdr, next_info[index].ifpi)); +                if (curr_info.metadata.error_code == rx_metadata_t::ERROR_CODE_OVERFLOW){ +                    _props[index].handle_overflow(); +                    UHD_MSG(fastpath) << "O"; +                } +                return; + +            case PACKET_TIMEOUT_ERROR: +                std::swap(curr_info, next_info); //save progress from curr -> next +                curr_info.metadata.has_time_spec = false; +                curr_info.metadata.time_spec = time_spec_t(0.0); +                curr_info.metadata.more_fragments = false; +                curr_info.metadata.fragment_offset = 0; +                curr_info.metadata.start_of_burst = false; +                curr_info.metadata.end_of_burst = false; +                curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_TIMEOUT; +                return; + +            case PACKET_SEQUENCE_ERROR: +                alignment_check(index, curr_info); +                std::swap(curr_info, next_info); //save progress from curr -> next +                curr_info.metadata.has_time_spec = prev_info.metadata.has_time_spec; +                curr_info.metadata.time_spec = prev_info.metadata.time_spec + time_spec_t(0, +                    prev_info[index].ifpi.num_payload_words32*sizeof(boost::uint32_t)/_bytes_per_item, _samp_rate); +                curr_info.metadata.more_fragments = false; +                curr_info.metadata.fragment_offset = 0; +                curr_info.metadata.start_of_burst = false; +                curr_info.metadata.end_of_burst = false; +                curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_OVERFLOW; +                UHD_MSG(fastpath) << "O"; +                return; + +            } + +            //too many iterations: detect alignment failure +            if (iterations++ > _alignment_faulure_threshold){ +                UHD_MSG(error) << boost::format( +                    "The receive packet handler failed to time-align packets.\n" +                    "%u received packets were processed by the handler.\n" +                    "However, a timestamp match could not be determined.\n" +                ) % iterations << std::endl; +                std::swap(curr_info, next_info); //save progress from curr -> next +                curr_info.metadata.has_time_spec = false; +                curr_info.metadata.time_spec = time_spec_t(0.0); +                curr_info.metadata.more_fragments = false; +                curr_info.metadata.fragment_offset = 0; +                curr_info.metadata.start_of_burst = false; +                curr_info.metadata.end_of_burst = false; +                curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_ALIGNMENT; +                return; +            } + +        } + +        //set the metadata from the buffer information at index zero +        curr_info.metadata.has_time_spec = curr_info[0].ifpi.has_tsi and curr_info[0].ifpi.has_tsf; +        curr_info.metadata.time_spec = curr_info[0].time; +        curr_info.metadata.more_fragments = false; +        curr_info.metadata.fragment_offset = 0; +        /* TODO SOB on RX not supported in hardware +        static const int tlr_sob_flags = (1 << 21) | (1 << 9); //enable and indicator bits +        curr_info.metadata.start_of_burst = curr_info[0].ifpi.has_tlr and (int(curr_info[0].ifpi.tlr & tlr_sob_flags) != 0); +        */ +        curr_info.metadata.start_of_burst = false; +        static const int tlr_eob_flags = (1 << 20) | (1 << 8); //enable and indicator bits +        curr_info.metadata.end_of_burst   = curr_info[0].ifpi.has_tlr and (int(curr_info[0].ifpi.tlr & tlr_eob_flags) != 0); +        curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_NONE; + +    } + +    /******************************************************************* +     * Receive a single packet: +     * Handles fragmentation, messages, errors, and copy-conversion. +     * When no fragments are available, call the get aligned buffers. +     * Then copy-convert available data into the user's IO buffers. +     ******************************************************************/ +    UHD_INLINE size_t recv_one_packet( +        const uhd::device::recv_buffs_type &buffs, +        const size_t nsamps_per_buff, +        uhd::rx_metadata_t &metadata, +        const uhd::io_type_t &io_type, +        double timeout, +        const size_t buffer_offset_bytes = 0 +    ){ +        //get the next buffer if the current one has expired +        if (get_curr_buffer_info().data_bytes_to_copy == 0){ + +            //reset current buffer info members for reuse +            get_curr_buffer_info().fragment_offset_in_samps = 0; +            get_curr_buffer_info().alignment_time_valid = false; +            get_curr_buffer_info().indexes_todo.set(); + +            //perform receive with alignment logic +            get_aligned_buffs(timeout); +        } + +        buffers_info_type &info = get_curr_buffer_info(); +        metadata = info.metadata; + +        //interpolate the time spec (useful when this is a fragment) +        metadata.time_spec += time_spec_t(0, info.fragment_offset_in_samps, _samp_rate); + +        //extract the number of samples available to copy +        const size_t nsamps_available = info.data_bytes_to_copy/_bytes_per_item; +        const size_t nsamps_to_copy = std::min(nsamps_per_buff*_io_buffs.size(), nsamps_available); +        const size_t bytes_to_copy = nsamps_to_copy*_bytes_per_item; +        const size_t nsamps_to_copy_per_io_buff = nsamps_to_copy/_io_buffs.size(); + +        size_t buff_index = 0; +        BOOST_FOREACH(per_buffer_info_type &buff_info, info){ + +            //fill a vector with pointers to the io buffers +            BOOST_FOREACH(void *&io_buff, _io_buffs){ +                io_buff = reinterpret_cast<char *>(buffs[buff_index++]) + buffer_offset_bytes; +            } + +            //copy-convert the samples from the recv buffer +            _converters[io_type.tid](buff_info.copy_buff, _io_buffs, nsamps_to_copy_per_io_buff, _scale_factor); + +            //update the rx copy buffer to reflect the bytes copied +            buff_info.copy_buff += bytes_to_copy; +        } +        //update the copy buffer's availability +        info.data_bytes_to_copy -= bytes_to_copy; + +        //setup the fragment flags and offset +        metadata.more_fragments = info.data_bytes_to_copy != 0; +        metadata.fragment_offset = info.fragment_offset_in_samps; +        info.fragment_offset_in_samps += nsamps_to_copy; //set for next call + +        return nsamps_to_copy_per_io_buff; +    } +}; + +}}} //namespace + +#endif /* INCLUDED_LIBUHD_TRANSPORT_SUPER_RECV_PACKET_HANDLER_HPP */ diff --git a/host/lib/transport/super_send_packet_handler.hpp b/host/lib/transport/super_send_packet_handler.hpp new file mode 100644 index 000000000..d5d9e6fe3 --- /dev/null +++ b/host/lib/transport/super_send_packet_handler.hpp @@ -0,0 +1,294 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_SUPER_SEND_PACKET_HANDLER_HPP +#define INCLUDED_LIBUHD_TRANSPORT_SUPER_SEND_PACKET_HANDLER_HPP + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/convert.hpp> +#include <uhd/device.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/types/io_type.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/thread/thread_time.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <iostream> +#include <vector> + +namespace uhd{ namespace transport{ namespace sph{ + +/*********************************************************************** + * Super send packet handler + * + * A send packet handler represents a group of channels. + * The channel group shares a common sample rate. + * All channels are sent in unison in send(). + **********************************************************************/ +class send_packet_handler{ +public: +    typedef boost::function<managed_send_buffer::sptr(double)> get_buff_type; +    typedef void(*vrt_packer_type)(boost::uint32_t *, vrt::if_packet_info_t &); +    //typedef boost::function<void(boost::uint32_t *, vrt::if_packet_info_t &)> vrt_packer_type; + +    /*! +     * Make a new packet handler for send +     * \param size the number of transport channels +     */ +    send_packet_handler(const size_t size = 1): +        _next_packet_seq(0) +    { +        this->resize(size); +        this->set_scale_factor(32767.); +    } + +    //! Resize the number of transport channels +    void resize(const size_t size){ +        if (this->size() == size) return; +        _props.resize(size); +        static const boost::uint64_t zero = 0; +        _zero_buffs.resize(size, &zero); +    } + +    //! Get the channel width of this handler +    size_t size(void) const{ +        return _props.size(); +    } + +    //! Setup the vrt packer function and offset +    void set_vrt_packer(const vrt_packer_type &vrt_packer, const size_t header_offset_words32 = 0){ +        _vrt_packer = vrt_packer; +        _header_offset_words32 = header_offset_words32; +    } + +    //! Set the rate of ticks per second +    void set_tick_rate(const double rate){ +        _tick_rate = rate; +    } + +    //! Set the rate of samples per second +    void set_samp_rate(const double rate){ +        _samp_rate = rate; +    } + +    /*! +     * Set the function to get a managed buffer. +     * \param xport_chan which transport channel +     * \param get_buff the getter function +     */ +    void set_xport_chan_get_buff(const size_t xport_chan, const get_buff_type &get_buff){ +        _props.at(xport_chan).get_buff = get_buff; +    } + +    /*! +     * Setup the conversion functions (homogeneous across transports). +     * Here, we load a table of converters for all possible io types. +     * This makes the converter look-up an O(1) operation. +     * \param otw_type the channel data type +     * \param width the streams per channel (usually 1) +     */ +    void set_converter(const uhd::otw_type_t &otw_type, const size_t width = 1){ +        _io_buffs.resize(width); +        _converters.resize(128); +        for (size_t io_type = 0; io_type < _converters.size(); io_type++){ +            try{ +                _converters[io_type] = uhd::convert::get_converter_cpu_to_otw( +                    io_type_t::tid_t(io_type), otw_type, 1, width +                ); +            }catch(const uhd::value_error &){} //we expect this, not all io_types valid... +        } +        _bytes_per_item = otw_type.get_sample_size(); +    } + +    /*! +     * Set the maximum number of samples per host packet. +     * Ex: A USRP1 in dual channel mode would be half. +     * \param num_samps the maximum samples in a packet +     */ +    void set_max_samples_per_packet(const size_t num_samps){ +        _max_samples_per_packet = num_samps; +    } + +    //! Get a scoped lock object for this instance +    boost::mutex::scoped_lock get_scoped_lock(void){ +        return boost::mutex::scoped_lock(_mutex); +    } + +    //! Set the scale factor used in float conversion +    void set_scale_factor(const double scale_factor){ +        _scale_factor = scale_factor; +    } + +    /******************************************************************* +     * Send: +     * The entry point for the fast-path send calls. +     * Dispatch into combinations of single packet send calls. +     ******************************************************************/ +    UHD_INLINE size_t send( +        const uhd::device::send_buffs_type &buffs, +        const size_t nsamps_per_buff, +        const uhd::tx_metadata_t &metadata, +        const uhd::io_type_t &io_type, +        uhd::device::send_mode_t send_mode, +        double timeout +    ){ +        boost::mutex::scoped_lock lock(_mutex); + +        //translate the metadata to vrt if packet info +        vrt::if_packet_info_t if_packet_info; +        if_packet_info.has_sid = false; +        if_packet_info.has_cid = false; +        if_packet_info.has_tlr = false; +        if_packet_info.has_tsi = metadata.has_time_spec; +        if_packet_info.has_tsf = metadata.has_time_spec; +        if_packet_info.tsi     = boost::uint32_t(metadata.time_spec.get_full_secs()); +        if_packet_info.tsf     = boost::uint64_t(metadata.time_spec.get_tick_count(_tick_rate)); +        if_packet_info.sob     = metadata.start_of_burst; +        if_packet_info.eob     = metadata.end_of_burst; + +        if (nsamps_per_buff <= _max_samples_per_packet) send_mode = uhd::device::SEND_MODE_ONE_PACKET; +        switch(send_mode){ + +        //////////////////////////////////////////////////////////////// +        case uhd::device::SEND_MODE_ONE_PACKET:{ +        //////////////////////////////////////////////////////////////// + +            //TODO remove this code when sample counts of zero are supported by hardware +            if (nsamps_per_buff == 0) return send_one_packet( +                _zero_buffs, 1, if_packet_info, io_type, timeout +            ); + +            return send_one_packet( +                buffs, +                std::min(nsamps_per_buff, _max_samples_per_packet), +                if_packet_info, io_type, timeout +            ); +        } + +        //////////////////////////////////////////////////////////////// +        case uhd::device::SEND_MODE_FULL_BUFF:{ +        //////////////////////////////////////////////////////////////// +            size_t total_num_samps_sent = 0; + +            //false until final fragment +            if_packet_info.eob = false; + +            const size_t num_fragments = (nsamps_per_buff-1)/_max_samples_per_packet; +            const size_t final_length = ((nsamps_per_buff-1)%_max_samples_per_packet)+1; + +            //loop through the following fragment indexes +            for (size_t i = 0; i < num_fragments; i++){ + +                //send a fragment with the helper function +                const size_t num_samps_sent = send_one_packet( +                    buffs, _max_samples_per_packet, +                    if_packet_info, io_type, timeout, +                    total_num_samps_sent*io_type.size +                ); +                total_num_samps_sent += num_samps_sent; +                if (num_samps_sent == 0) return total_num_samps_sent; + +                //setup metadata for the next fragment +                const time_spec_t time_spec = metadata.time_spec + time_spec_t(0, total_num_samps_sent, _samp_rate); +                if_packet_info.tsi = boost::uint32_t(time_spec.get_full_secs()); +                if_packet_info.tsf = boost::uint64_t(time_spec.get_tick_count(_tick_rate)); +                if_packet_info.sob = false; + +            } + +            //send the final fragment with the helper function +            if_packet_info.eob = metadata.end_of_burst; +            return total_num_samps_sent + send_one_packet( +                buffs, final_length, +                if_packet_info, io_type, timeout, +                total_num_samps_sent*io_type.size +            ); +        } + +        default: throw uhd::value_error("unknown send mode"); +        }//switch(send_mode) +    } + +private: + +    boost::mutex _mutex; +    vrt_packer_type _vrt_packer; +    size_t _header_offset_words32; +    double _tick_rate, _samp_rate; +    struct xport_chan_props_type{ +        get_buff_type get_buff; +    }; +    std::vector<xport_chan_props_type> _props; +    std::vector<const void *> _io_buffs; //used in conversion +    size_t _bytes_per_item; //used in conversion +    std::vector<uhd::convert::function_type> _converters; //used in conversion +    size_t _max_samples_per_packet; +    std::vector<const void *> _zero_buffs; +    size_t _next_packet_seq; +    double _scale_factor; + +    /******************************************************************* +     * Send a single packet: +     ******************************************************************/ +    UHD_INLINE size_t send_one_packet( +        const uhd::device::send_buffs_type &buffs, +        const size_t nsamps_per_buff, +        vrt::if_packet_info_t &if_packet_info, +        const uhd::io_type_t &io_type, +        double timeout, +        const size_t buffer_offset_bytes = 0 +    ){ +        //load the rest of the if_packet_info in here +        if_packet_info.num_payload_words32 = (nsamps_per_buff*_io_buffs.size()*_bytes_per_item)/sizeof(boost::uint32_t); +        if_packet_info.packet_count = _next_packet_seq; + +        size_t buff_index = 0; +        BOOST_FOREACH(xport_chan_props_type &props, _props){ +            managed_send_buffer::sptr buff = props.get_buff(timeout); +            if (buff.get() == NULL) return 0; //timeout + +            //fill a vector with pointers to the io buffers +            BOOST_FOREACH(const void *&io_buff, _io_buffs){ +                io_buff = reinterpret_cast<const char *>(buffs[buff_index++]) + buffer_offset_bytes; +            } +            boost::uint32_t *otw_mem = buff->cast<boost::uint32_t *>() + _header_offset_words32; + +            //pack metadata into a vrt header +            _vrt_packer(otw_mem, if_packet_info); +            otw_mem += if_packet_info.num_header_words32; + +            //copy-convert the samples into the send buffer +            _converters[io_type.tid](_io_buffs, otw_mem, nsamps_per_buff, _scale_factor); + +            //commit the samples to the zero-copy interface +            size_t num_bytes_total = (_header_offset_words32+if_packet_info.num_packet_words32)*sizeof(boost::uint32_t); +            buff->commit(num_bytes_total); + +        } +        _next_packet_seq++; //increment sequence after commits +        return nsamps_per_buff; +    } +}; + +}}} //namespace + +#endif /* INCLUDED_LIBUHD_TRANSPORT_SUPER_SEND_PACKET_HANDLER_HPP */ diff --git a/host/lib/transport/udp_common.hpp b/host/lib/transport/udp_common.hpp new file mode 100644 index 000000000..47775d9c4 --- /dev/null +++ b/host/lib/transport/udp_common.hpp @@ -0,0 +1,53 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_TRANSPORT_VRT_PACKET_HANDLER_HPP +#define INCLUDED_LIBUHD_TRANSPORT_VRT_PACKET_HANDLER_HPP + +#include <uhd/config.hpp> +#include <boost/asio.hpp> + +namespace uhd{ namespace transport{ + +    typedef boost::shared_ptr<boost::asio::ip::udp::socket> socket_sptr; + +    /*! +     * Wait for the socket to become ready for a receive operation. +     * \param sock_fd the open socket file descriptor +     * \param timeout the timeout duration in seconds +     * \return true when the socket is ready for receive +     */ +    UHD_INLINE bool wait_for_recv_ready(int sock_fd, double timeout){ +        //setup timeval for timeout +        timeval tv; +        //If the tv_usec > 1 second on some platforms, select will +        //error EINVAL: An invalid timeout interval was specified. +        tv.tv_sec = int(timeout); +        tv.tv_usec = int(timeout*1000000)%1000000; + +        //setup rset for timeout +        fd_set rset; +        FD_ZERO(&rset); +        FD_SET(sock_fd, &rset); + +        //call select with timeout on receive socket +        return ::select(sock_fd+1, &rset, NULL, NULL, &tv) > 0; +    } + +}} //namespace uhd::transport + +#endif /* INCLUDED_LIBUHD_TRANSPORT_VRT_PACKET_HANDLER_HPP */ diff --git a/host/lib/transport/udp_simple.cpp b/host/lib/transport/udp_simple.cpp new file mode 100644 index 000000000..ed778fbf4 --- /dev/null +++ b/host/lib/transport/udp_simple.cpp @@ -0,0 +1,83 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "udp_common.hpp" +#include <uhd/transport/udp_simple.hpp> +#include <uhd/utils/log.hpp> +#include <boost/format.hpp> + +using namespace uhd::transport; +namespace asio = boost::asio; + +/*********************************************************************** + * UDP simple implementation: connected and broadcast + **********************************************************************/ +class udp_simple_impl : public udp_simple{ +public: +    udp_simple_impl( +        const std::string &addr, const std::string &port, bool bcast, bool connect +    ):_connected(connect){ +        UHD_LOG << boost::format("Creating udp transport for %s %s") % addr % port << std::endl; + +        //resolve the address +        asio::ip::udp::resolver resolver(_io_service); +        asio::ip::udp::resolver::query query(asio::ip::udp::v4(), addr, port); +        _receiver_endpoint = *resolver.resolve(query); + +        //create and open the socket +        _socket = socket_sptr(new asio::ip::udp::socket(_io_service)); +        _socket->open(asio::ip::udp::v4()); + +        //allow broadcasting +        _socket->set_option(asio::socket_base::broadcast(bcast)); + +        //connect the socket +        if (connect) _socket->connect(_receiver_endpoint); + +    } + +    size_t send(const asio::const_buffer &buff){ +        if (_connected) return _socket->send(asio::buffer(buff)); +        return _socket->send_to(asio::buffer(buff), _receiver_endpoint); +    } + +    size_t recv(const asio::mutable_buffer &buff, double timeout){ +        if (not wait_for_recv_ready(_socket->native(), timeout)) return 0; +        return _socket->receive(asio::buffer(buff)); +    } + +private: +    bool                    _connected; +    asio::io_service        _io_service; +    socket_sptr             _socket; +    asio::ip::udp::endpoint _receiver_endpoint; +}; + +/*********************************************************************** + * UDP public make functions + **********************************************************************/ +udp_simple::sptr udp_simple::make_connected( +    const std::string &addr, const std::string &port +){ +    return sptr(new udp_simple_impl(addr, port, false, true /* no bcast, connect */)); +} + +udp_simple::sptr udp_simple::make_broadcast( +    const std::string &addr, const std::string &port +){ +    return sptr(new udp_simple_impl(addr, port, true, false /* bcast, no connect */)); +} diff --git a/host/lib/transport/udp_wsa_zero_copy.cpp b/host/lib/transport/udp_wsa_zero_copy.cpp new file mode 100644 index 000000000..1acaa7eb1 --- /dev/null +++ b/host/lib/transport/udp_wsa_zero_copy.cpp @@ -0,0 +1,306 @@ +// +// 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 "udp_common.hpp" +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/udp_simple.hpp> //mtu +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <boost/format.hpp> +#include <vector> + +using namespace uhd; +using namespace uhd::transport; +namespace asio = boost::asio; + +//A reasonable number of frames for send/recv and async/sync +static const size_t DEFAULT_NUM_FRAMES = 32; + +/*********************************************************************** + * Check registry for correct fast-path setting + **********************************************************************/ +#include <atlbase.h> //CRegKey +static void check_registry_for_fast_send_threshold(const size_t mtu){ +    static bool warned = false; +    if (warned) return; //only allow one printed warning per process + +    CRegKey reg_key; +    DWORD threshold = 1024; //system default when threshold is not specified +    if ( +        reg_key.Open(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\AFD\\Parameters", KEY_READ) != ERROR_SUCCESS or +        reg_key.QueryDWORDValue("FastSendDatagramThreshold", threshold) != ERROR_SUCCESS or threshold < mtu +    ){ +        UHD_MSG(warning) << boost::format( +            "The MTU (%d) is larger than the FastSendDatagramThreshold (%d)!\n" +            "This will negatively affect the transmit performance.\n" +            "See the transport application notes for more detail.\n" +        ) % mtu % threshold << std::endl; +        warned = true; +    } +    reg_key.Close(); +} + +/*********************************************************************** + * Static initialization to take care of WSA init and cleanup + **********************************************************************/ +struct uhd_wsa_control{ +    uhd_wsa_control(void){ +        WSADATA wsaData; +        WSAStartup(MAKEWORD(2, 2), &wsaData); /*windows socket startup */ +    } + +    ~uhd_wsa_control(void){ +        WSACleanup(); +    } +}; + +/*********************************************************************** + * Reusable managed receiver buffer: + *  - Initialize with memory and a release callback. + *  - Call get new with a length in bytes to re-use. + **********************************************************************/ +class udp_zero_copy_asio_mrb : public managed_recv_buffer{ +public: +    udp_zero_copy_asio_mrb(void *mem, bounded_buffer<udp_zero_copy_asio_mrb *> &pending): +        _mem(mem), _len(0), _pending(pending){/* NOP */} + +    void release(void){ +        if (_len == 0) return; +        _pending.push_with_haste(this); +        _len = 0; +    } + +    sptr get_new(size_t len){ +        _len = len; +        return make_managed_buffer(this); +    } + +    template <class T> T cast(void) const{return static_cast<T>(_mem);} + +private: +    const void *get_buff(void) const{return _mem;} +    size_t get_size(void) const{return _len;} + +    void *_mem; +    size_t _len; +    bounded_buffer<udp_zero_copy_asio_mrb *> &_pending; +}; + +/*********************************************************************** + * Reusable managed send buffer: + *  - committing the buffer calls the asynchronous socket send + *  - getting a new buffer performs the blocking wait for completion + **********************************************************************/ +class udp_zero_copy_asio_msb : public managed_send_buffer{ +public: +    udp_zero_copy_asio_msb(void *mem, int sock_fd, const size_t frame_size): +        _sock_fd(sock_fd), _frame_size(frame_size), _committed(false) +    { +        _wsa_buff.buf = reinterpret_cast<char *>(mem); +        ZeroMemory(&_overlapped, sizeof(_overlapped)); +        _overlapped.hEvent = WSACreateEvent(); +        UHD_ASSERT_THROW(_overlapped.hEvent != WSA_INVALID_EVENT); +        this->commit(0); //makes buffer available via get_new +    } + +    ~udp_zero_copy_asio_msb(void){ +        WSACloseEvent(_overlapped.hEvent); +    } + +    UHD_INLINE void commit(size_t len){ +        if (_committed) return; +        _committed = true; +        _wsa_buff.len = len; +        if (len == 0) WSASetEvent(_overlapped.hEvent); +        else WSASend(_sock_fd, &_wsa_buff, 1, NULL, 0, &_overlapped, NULL); +    } + +    UHD_INLINE sptr get_new(const double timeout, size_t &index){ +        const DWORD result = WSAWaitForMultipleEvents( +            1, &_overlapped.hEvent, true, DWORD(timeout*1000), true +        ); +        if (result == WSA_WAIT_TIMEOUT) return managed_send_buffer::sptr(); +        index++; //advances the caller's buffer + +        WSAResetEvent(_overlapped.hEvent); +        _committed = false; +        _wsa_buff.len = _frame_size; +        return make_managed_buffer(this); +    } + +private: +    void *get_buff(void) const{return _wsa_buff.buf;} +    size_t get_size(void) const{return _wsa_buff.len;} + +    int _sock_fd; +    const size_t _frame_size; +    bool _committed; +    WSAOVERLAPPED _overlapped; +    WSABUF _wsa_buff; +}; + +/*********************************************************************** + * Zero Copy UDP implementation with WSA: + * + *   This is not a true zero copy implementation as each + *   send and recv requires a copy operation to/from userspace. + * + *   For receive, use a blocking recv() call on the socket. + *   This has better performance than the overlapped IO. + *   For send, use overlapped IO to submit async sends. + **********************************************************************/ +class udp_zero_copy_wsa_impl : public udp_zero_copy{ +public: +    typedef boost::shared_ptr<udp_zero_copy_wsa_impl> sptr; + +    udp_zero_copy_wsa_impl( +        const std::string &addr, +        const std::string &port, +        const device_addr_t &hints +    ): +        _recv_frame_size(size_t(hints.cast<double>("recv_frame_size", udp_simple::mtu))), +        _num_recv_frames(size_t(hints.cast<double>("num_recv_frames", DEFAULT_NUM_FRAMES))), +        _send_frame_size(size_t(hints.cast<double>("send_frame_size", udp_simple::mtu))), +        _num_send_frames(size_t(hints.cast<double>("num_send_frames", DEFAULT_NUM_FRAMES))), +        _recv_buffer_pool(buffer_pool::make(_num_recv_frames, _recv_frame_size)), +        _send_buffer_pool(buffer_pool::make(_num_send_frames, _send_frame_size)), +        _pending_recv_buffs(_num_recv_frames), +        _next_send_buff_index(0) +    { +        check_registry_for_fast_send_threshold(this->get_send_frame_size()); +        UHD_MSG(status) << boost::format("Creating WSA UDP transport for %s:%s") % addr % port << std::endl; +        static uhd_wsa_control uhd_wsa; //makes wsa start happen via lazy initialization + +        UHD_ASSERT_THROW(_num_send_frames <= WSA_MAXIMUM_WAIT_EVENTS); + +        //resolve the address +        asio::io_service io_service; +        asio::ip::udp::resolver resolver(io_service); +        asio::ip::udp::resolver::query query(asio::ip::udp::v4(), addr, port); +        asio::ip::udp::endpoint receiver_endpoint = *resolver.resolve(query); + +        //create the socket +        _sock_fd = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED); +        if (_sock_fd == INVALID_SOCKET){ +            const DWORD error = WSAGetLastError(); +            throw uhd::os_error(str(boost::format("WSASocket() failed with error %d") % error)); +        } + +        //set the socket non-blocking for recv +        u_long mode = 1; +        ioctlsocket(_sock_fd, FIONBIO, &mode); + +        //resize the socket buffers +        const int recv_buff_size = int(hints.cast<double>("recv_buff_size", 0.0)); +        const int send_buff_size = int(hints.cast<double>("send_buff_size", 0.0)); +        if (recv_buff_size > 0) setsockopt(_sock_fd, SOL_SOCKET, SO_RCVBUF, (const char *)&recv_buff_size, sizeof(recv_buff_size)); +        if (send_buff_size > 0) setsockopt(_sock_fd, SOL_SOCKET, SO_SNDBUF, (const char *)&send_buff_size, sizeof(send_buff_size)); + +        //connect the socket so we can send/recv +        const asio::ip::udp::endpoint::data_type &servaddr = *receiver_endpoint.data(); +        if (WSAConnect(_sock_fd, (const struct sockaddr *)&servaddr, sizeof(servaddr), NULL, NULL, NULL, NULL) != 0){ +            const DWORD error = WSAGetLastError(); +            closesocket(_sock_fd); +            throw uhd::os_error(str(boost::format("WSAConnect() failed with error %d") % error)); +        } + +        //allocate re-usable managed receive buffers +        for (size_t i = 0; i < get_num_recv_frames(); i++){ +            _mrb_pool.push_back(boost::shared_ptr<udp_zero_copy_asio_mrb>( +                new udp_zero_copy_asio_mrb(_recv_buffer_pool->at(i), _pending_recv_buffs) +            )); +            _pending_recv_buffs.push_with_haste(_mrb_pool.back().get()); +        } + +        //allocate re-usable managed send buffers +        for (size_t i = 0; i < get_num_send_frames(); i++){ +            _msb_pool.push_back(boost::shared_ptr<udp_zero_copy_asio_msb>( +                new udp_zero_copy_asio_msb(_send_buffer_pool->at(i), _sock_fd, get_send_frame_size()) +            )); +        } +    } + +    ~udp_zero_copy_wsa_impl(void){ +        closesocket(_sock_fd); +    } + +    /******************************************************************* +     * Receive implementation: +     * +     * Perform a non-blocking receive for performance, +     * and then fall back to a blocking receive with timeout. +     * Return the managed receive buffer with the new length. +     * When the caller is finished with the managed buffer, +     * the managed receive buffer is released back into the queue. +     ******************************************************************/ +    managed_recv_buffer::sptr get_recv_buff(double timeout){ +        udp_zero_copy_asio_mrb *mrb = NULL; +        if (_pending_recv_buffs.pop_with_timed_wait(mrb, timeout)){ + +            ssize_t ret = ::recv(_sock_fd, mrb->cast<char *>(), _recv_frame_size, 0); +            if (ret > 0) return mrb->get_new(ret); + +            if (wait_for_recv_ready(_sock_fd, timeout)) return mrb->get_new( +                ::recv(_sock_fd, mrb->cast<char *>(), _recv_frame_size, 0) +            ); + +            _pending_recv_buffs.push_with_haste(mrb); //timeout: return the managed buffer to the queue +        } +        return managed_recv_buffer::sptr(); +    } + +    size_t get_num_recv_frames(void) const {return _num_recv_frames;} +    size_t get_recv_frame_size(void) const {return _recv_frame_size;} + +    /******************************************************************* +     * Send implementation: +     * Block on the managed buffer's get call and advance the index. +     ******************************************************************/ +    managed_send_buffer::sptr get_send_buff(double timeout){ +        if (_next_send_buff_index == _num_send_frames) _next_send_buff_index = 0; +        return _msb_pool[_next_send_buff_index]->get_new(timeout, _next_send_buff_index); +    } + +    size_t get_num_send_frames(void) const {return _num_send_frames;} +    size_t get_send_frame_size(void) const {return _send_frame_size;} + +private: +    //memory management -> buffers and fifos +    const size_t _recv_frame_size, _num_recv_frames; +    const size_t _send_frame_size, _num_send_frames; +    buffer_pool::sptr _recv_buffer_pool, _send_buffer_pool; +    std::vector<boost::shared_ptr<udp_zero_copy_asio_msb> > _msb_pool; +    std::vector<boost::shared_ptr<udp_zero_copy_asio_mrb> > _mrb_pool; +    bounded_buffer<udp_zero_copy_asio_mrb *> _pending_recv_buffs; +    size_t _next_send_buff_index; + +    //socket guts +    SOCKET                  _sock_fd; +}; + +/*********************************************************************** + * UDP zero copy make function + **********************************************************************/ +udp_zero_copy::sptr udp_zero_copy::make( +    const std::string &addr, +    const std::string &port, +    const device_addr_t &hints +){ +    return sptr(new udp_zero_copy_wsa_impl(addr, port, hints)); +} diff --git a/host/lib/transport/udp_zero_copy.cpp b/host/lib/transport/udp_zero_copy.cpp new file mode 100644 index 000000000..bc73b96a8 --- /dev/null +++ b/host/lib/transport/udp_zero_copy.cpp @@ -0,0 +1,282 @@ +// +// 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 "udp_common.hpp" +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/udp_simple.hpp> //mtu +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <boost/format.hpp> +#include <list> + +using namespace uhd; +using namespace uhd::transport; +namespace asio = boost::asio; + +//A reasonable number of frames for send/recv and async/sync +static const size_t DEFAULT_NUM_FRAMES = 32; + +/*********************************************************************** + * Reusable managed receiver buffer: + *  - Initialize with memory and a release callback. + *  - Call get new with a length in bytes to re-use. + **********************************************************************/ +class udp_zero_copy_asio_mrb : public managed_recv_buffer{ +public: +    udp_zero_copy_asio_mrb(void *mem, bounded_buffer<udp_zero_copy_asio_mrb *> &pending): +        _mem(mem), _len(0), _pending(pending){/* NOP */} + +    void release(void){ +        if (_len == 0) return; +        _pending.push_with_haste(this); +        _len = 0; +    } + +    sptr get_new(size_t len){ +        _len = len; +        return make_managed_buffer(this); +    } + +    template <class T> T cast(void) const{return static_cast<T>(_mem);} + +private: +    const void *get_buff(void) const{return _mem;} +    size_t get_size(void) const{return _len;} + +    void *_mem; +    size_t _len; +    bounded_buffer<udp_zero_copy_asio_mrb *> &_pending; +}; + +/*********************************************************************** + * Reusable managed send buffer: + *  - Initialize with memory and a commit callback. + *  - Call get new with a length in bytes to re-use. + **********************************************************************/ +class udp_zero_copy_asio_msb : public managed_send_buffer{ +public: +    udp_zero_copy_asio_msb(void *mem, bounded_buffer<udp_zero_copy_asio_msb *> &pending, int sock_fd): +        _mem(mem), _len(0), _pending(pending), _sock_fd(sock_fd){/* NOP */} + +    void commit(size_t len){ +        if (_len == 0) return; +        ::send(_sock_fd, this->cast<const char *>(), len, 0); +        _pending.push_with_haste(this); +        _len = 0; +    } + +    sptr get_new(size_t len){ +        _len = len; +        return make_managed_buffer(this); +    } + +private: +    void *get_buff(void) const{return _mem;} +    size_t get_size(void) const{return _len;} + +    void *_mem; +    size_t _len; +    bounded_buffer<udp_zero_copy_asio_msb *> &_pending; +    int _sock_fd; +}; + +/*********************************************************************** + * Zero Copy UDP implementation with ASIO: + *   This is the portable zero copy implementation for systems + *   where a faster, platform specific solution is not available. + *   However, it is not a true zero copy implementation as each + *   send and recv requires a copy operation to/from userspace. + **********************************************************************/ +class udp_zero_copy_asio_impl : public udp_zero_copy{ +public: +    typedef boost::shared_ptr<udp_zero_copy_asio_impl> sptr; + +    udp_zero_copy_asio_impl( +        const std::string &addr, +        const std::string &port, +        const device_addr_t &hints +    ): +        _recv_frame_size(size_t(hints.cast<double>("recv_frame_size", udp_simple::mtu))), +        _num_recv_frames(size_t(hints.cast<double>("num_recv_frames", DEFAULT_NUM_FRAMES))), +        _send_frame_size(size_t(hints.cast<double>("send_frame_size", udp_simple::mtu))), +        _num_send_frames(size_t(hints.cast<double>("num_send_frames", DEFAULT_NUM_FRAMES))), +        _recv_buffer_pool(buffer_pool::make(_num_recv_frames, _recv_frame_size)), +        _send_buffer_pool(buffer_pool::make(_num_send_frames, _send_frame_size)), +        _pending_recv_buffs(_num_recv_frames), +        _pending_send_buffs(_num_send_frames) +    { +        UHD_LOG << boost::format("Creating udp transport for %s %s") % addr % port << std::endl; + +        //resolve the address +        asio::ip::udp::resolver resolver(_io_service); +        asio::ip::udp::resolver::query query(asio::ip::udp::v4(), addr, port); +        asio::ip::udp::endpoint receiver_endpoint = *resolver.resolve(query); + +        //create, open, and connect the socket +        _socket = socket_sptr(new asio::ip::udp::socket(_io_service)); +        _socket->open(asio::ip::udp::v4()); +        _socket->connect(receiver_endpoint); +        _sock_fd = _socket->native(); + +        //allocate re-usable managed receive buffers +        for (size_t i = 0; i < get_num_recv_frames(); i++){ +            _mrb_pool.push_back(udp_zero_copy_asio_mrb( +                _recv_buffer_pool->at(i), _pending_recv_buffs +            )); +            _pending_recv_buffs.push_with_haste(&_mrb_pool.back()); +        } + +        //allocate re-usable managed send buffers +        for (size_t i = 0; i < get_num_send_frames(); i++){ +            _msb_pool.push_back(udp_zero_copy_asio_msb( +                _send_buffer_pool->at(i), _pending_send_buffs, _sock_fd +            )); +            _pending_send_buffs.push_with_haste(&_msb_pool.back()); +        } +    } + +    //get size for internal socket buffer +    template <typename Opt> size_t get_buff_size(void) const{ +        Opt option; +        _socket->get_option(option); +        return option.value(); +    } + +    //set size for internal socket buffer +    template <typename Opt> size_t resize_buff(size_t num_bytes){ +        Opt option(num_bytes); +        _socket->set_option(option); +        return get_buff_size<Opt>(); +    } + +    /******************************************************************* +     * Receive implementation: +     * +     * Perform a non-blocking receive for performance, +     * and then fall back to a blocking receive with timeout. +     * Return the managed receive buffer with the new length. +     * When the caller is finished with the managed buffer, +     * the managed receive buffer is released back into the queue. +     ******************************************************************/ +    managed_recv_buffer::sptr get_recv_buff(double timeout){ +        udp_zero_copy_asio_mrb *mrb = NULL; +        if (_pending_recv_buffs.pop_with_timed_wait(mrb, timeout)){ + +            #ifdef MSG_DONTWAIT //try a non-blocking recv() if supported +            ssize_t ret = ::recv(_sock_fd, mrb->cast<char *>(), _recv_frame_size, MSG_DONTWAIT); +            if (ret > 0) return mrb->get_new(ret); +            #endif + +            if (wait_for_recv_ready(_sock_fd, timeout)) return mrb->get_new( +                ::recv(_sock_fd, mrb->cast<char *>(), _recv_frame_size, 0) +            ); + +            _pending_recv_buffs.push_with_haste(mrb); //timeout: return the managed buffer to the queue +        } +        return managed_recv_buffer::sptr(); +    } + +    size_t get_num_recv_frames(void) const {return _num_recv_frames;} +    size_t get_recv_frame_size(void) const {return _recv_frame_size;} + +    /******************************************************************* +     * Send implementation: +     * +     * Get a managed receive buffer immediately with max length set. +     * The caller will fill the buffer and commit it when finished. +     * The commit routine will perform a blocking send operation, +     * and push the managed send buffer back into the queue. +     ******************************************************************/ +    managed_send_buffer::sptr get_send_buff(double timeout){ +        udp_zero_copy_asio_msb *msb = NULL; +        if (_pending_send_buffs.pop_with_timed_wait(msb, timeout)){ +            return msb->get_new(_send_frame_size); +        } +        return managed_send_buffer::sptr(); +    } + +    size_t get_num_send_frames(void) const {return _num_send_frames;} +    size_t get_send_frame_size(void) const {return _send_frame_size;} + +private: +    //memory management -> buffers and fifos +    const size_t _recv_frame_size, _num_recv_frames; +    const size_t _send_frame_size, _num_send_frames; +    buffer_pool::sptr _recv_buffer_pool, _send_buffer_pool; +    bounded_buffer<udp_zero_copy_asio_mrb *> _pending_recv_buffs; +    bounded_buffer<udp_zero_copy_asio_msb *> _pending_send_buffs; +    std::list<udp_zero_copy_asio_msb> _msb_pool; +    std::list<udp_zero_copy_asio_mrb> _mrb_pool; + +    //asio guts -> socket and service +    asio::io_service        _io_service; +    socket_sptr             _socket; +    int                     _sock_fd; +}; + +/*********************************************************************** + * UDP zero copy make function + **********************************************************************/ +template<typename Opt> static void resize_buff_helper( +    udp_zero_copy_asio_impl::sptr udp_trans, +    const size_t target_size, +    const std::string &name +){ +    std::string help_message; +    #if defined(UHD_PLATFORM_LINUX) +        help_message = str(boost::format( +            "Please run: sudo sysctl -w net.core.%smem_max=%d\n" +        ) % ((name == "recv")?"r":"w") % target_size); +    #endif /*defined(UHD_PLATFORM_LINUX)*/ + +    //resize the buffer if size was provided +    if (target_size > 0){ +        size_t actual_size = udp_trans->resize_buff<Opt>(target_size); +        UHD_LOG << boost::format( +            "Target %s sock buff size: %d bytes\n" +            "Actual %s sock buff size: %d bytes" +        ) % name % target_size % name % actual_size << std::endl; +        if (actual_size < target_size) UHD_MSG(warning) << boost::format( +            "The %s buffer could not be resized sufficiently.\n" +            "Target sock buff size: %d bytes.\n" +            "Actual sock buff size: %d bytes.\n" +            "See the transport application notes on buffer resizing.\n%s" +        ) % name % target_size % actual_size % help_message; +    } +} + +udp_zero_copy::sptr udp_zero_copy::make( +    const std::string &addr, +    const std::string &port, +    const device_addr_t &hints +){ +    udp_zero_copy_asio_impl::sptr udp_trans( +        new udp_zero_copy_asio_impl(addr, port, hints) +    ); + +    //extract buffer size hints from the device addr +    size_t recv_buff_size = size_t(hints.cast<double>("recv_buff_size", 0.0)); +    size_t send_buff_size = size_t(hints.cast<double>("send_buff_size", 0.0)); + +    //call the helper to resize send and recv buffers +    resize_buff_helper<asio::socket_base::receive_buffer_size>(udp_trans, recv_buff_size, "recv"); +    resize_buff_helper<asio::socket_base::send_buffer_size>   (udp_trans, send_buff_size, "send"); + +    return udp_trans; +} diff --git a/host/lib/transport/usb_dummy_impl.cpp b/host/lib/transport/usb_dummy_impl.cpp new file mode 100644 index 000000000..930678405 --- /dev/null +++ b/host/lib/transport/usb_dummy_impl.cpp @@ -0,0 +1,39 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/transport/usb_device_handle.hpp> +#include <uhd/transport/usb_control.hpp> +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/exception.hpp> + +using namespace uhd; +using namespace uhd::transport; + +std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list(boost::uint16_t, boost::uint16_t){ +    return std::vector<usb_device_handle::sptr>(); //empty list +} + +usb_control::sptr usb_control::make(usb_device_handle::sptr){ +    throw uhd::not_implemented_error("no usb support -> usb_control::make not implemented"); +} + +usb_zero_copy::sptr usb_zero_copy::make( +    usb_device_handle::sptr, +    size_t, size_t, const device_addr_t & +){ +    throw uhd::not_implemented_error("no usb support -> usb_zero_copy::make not implemented"); +} 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/types/CMakeLists.txt b/host/lib/types/CMakeLists.txt new file mode 100644 index 000000000..957dfd345 --- /dev/null +++ b/host/lib/types/CMakeLists.txt @@ -0,0 +1,92 @@ +# +# 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/>. +# + +######################################################################## +# Setup defines for high resolution timing +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring high resolution timing...") +INCLUDE(CheckCXXSourceCompiles) + +SET(CMAKE_REQUIRED_LIBRARIES -lrt) +CHECK_CXX_SOURCE_COMPILES(" +    #include <ctime> +    int main(){ +        timespec ts; +        return clock_gettime(CLOCK_MONOTONIC, &ts); +    } +    " HAVE_CLOCK_GETTIME +) +UNSET(CMAKE_REQUIRED_LIBRARIES) + +INCLUDE(CheckCXXSourceCompiles) +CHECK_CXX_SOURCE_COMPILES(" +    #include <mach/mach_time.h> +    int main(){ +        mach_timebase_info_data_t info; +        mach_timebase_info(&info); +        mach_absolute_time(); +        return 0; +    } +    " HAVE_MACH_ABSOLUTE_TIME +) + +CHECK_CXX_SOURCE_COMPILES(" +    #include <Windows.h> +    int main(){ +        LARGE_INTEGER value; +        QueryPerformanceCounter(&value); +        QueryPerformanceFrequency(&value); +        return 0; +    } +    " HAVE_QUERY_PERFORMANCE_COUNTER +) + +IF(HAVE_CLOCK_GETTIME) +    MESSAGE(STATUS "  High resolution timing supported through clock_gettime.") +    SET(TIME_SPEC_DEFS HAVE_CLOCK_GETTIME) +    LIBUHD_APPEND_LIBS("-lrt") +ELSEIF(HAVE_MACH_ABSOLUTE_TIME) +    MESSAGE(STATUS "  High resolution timing supported through mach_absolute_time.") +    SET(TIME_SPEC_DEFS HAVE_MACH_ABSOLUTE_TIME) +ELSEIF(HAVE_QUERY_PERFORMANCE_COUNTER) +    MESSAGE(STATUS "  High resolution timing supported through QueryPerformanceCounter.") +    SET(TIME_SPEC_DEFS HAVE_QUERY_PERFORMANCE_COUNTER) +ELSE() +    MESSAGE(STATUS "  High resolution timing supported though microsec_clock.") +    SET(TIME_SPEC_DEFS HAVE_MICROSEC_CLOCK) +ENDIF() + +SET_SOURCE_FILES_PROPERTIES( +    ${CMAKE_CURRENT_SOURCE_DIR}/time_spec.cpp +    PROPERTIES COMPILE_DEFINITIONS "${TIME_SPEC_DEFS}" +) + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/clock_config.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/device_addr.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/mac_addr.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/ranges.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/sensors.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/serial.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/time_spec.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/tune.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/types.cpp +) diff --git a/host/lib/types/clock_config.cpp b/host/lib/types/clock_config.cpp new file mode 100644 index 000000000..c150c5cc3 --- /dev/null +++ b/host/lib/types/clock_config.cpp @@ -0,0 +1,44 @@ +// +// 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/types/clock_config.hpp> + +using namespace uhd; + +clock_config_t clock_config_t::external(void){ +    clock_config_t clock_config; +    clock_config.ref_source = clock_config_t::REF_SMA; +    clock_config.pps_source = clock_config_t::PPS_SMA; +    clock_config.pps_polarity = clock_config_t::PPS_POS; +    return clock_config; +} + +clock_config_t clock_config_t::internal(void){ +    clock_config_t clock_config; +    clock_config.ref_source = clock_config_t::REF_INT; +    clock_config.pps_source = clock_config_t::PPS_SMA; +    clock_config.pps_polarity = clock_config_t::PPS_POS; +    return clock_config; +} + +clock_config_t::clock_config_t(void): +    ref_source(REF_INT), +    pps_source(PPS_SMA), +    pps_polarity(PPS_POS) +{ +    /* NOP */ +} diff --git a/host/lib/types/device_addr.cpp b/host/lib/types/device_addr.cpp new file mode 100644 index 000000000..45d885adc --- /dev/null +++ b/host/lib/types/device_addr.cpp @@ -0,0 +1,122 @@ +// +// 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/types/device_addr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/tokenizer.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/regex.hpp> +#include <stdexcept> +#include <sstream> + +using namespace uhd; + +static const std::string arg_delim = ","; +static const std::string pair_delim = "="; + +static std::string trim(const std::string &in){ +    return boost::algorithm::trim_copy(in); +} + +#define tokenizer(inp, sep) \ +    boost::tokenizer<boost::char_separator<char> > \ +    (inp, boost::char_separator<char>(sep.c_str())) + +device_addr_t::device_addr_t(const std::string &args){ +    BOOST_FOREACH(const std::string &pair, tokenizer(args, arg_delim)){ +        if (trim(pair) == "") continue; +        std::string key; +        BOOST_FOREACH(const std::string &tok, tokenizer(pair, pair_delim)){ +            if (key.empty()) key = tok; +            else{ +                this->set(trim(key), trim(tok)); +                goto continue_next_arg; +            } +        } +        throw uhd::value_error("invalid args string: "+args); +        continue_next_arg: continue; +    } +} + +std::string device_addr_t::to_pp_string(void) const{ +    if (this->size() == 0) return "Empty Device Address"; + +    std::stringstream ss; +    ss << "Device Address:" << std::endl; +    BOOST_FOREACH(std::string key, this->keys()){ +        ss << boost::format("    %s: %s") % key % this->get(key) << std::endl; +    } +    return ss.str(); +} + +std::string device_addr_t::to_string(void) const{ +    std::string args_str; +    size_t count = 0; +    BOOST_FOREACH(const std::string &key, this->keys()){ +        args_str += ((count++)? arg_delim : "") + key + pair_delim + this->get(key); +    } +    return args_str; +} + +#include <uhd/utils/msg.hpp> + +device_addrs_t uhd::separate_device_addr(const device_addr_t &dev_addr){ +    //------------ support old deprecated way and print warning -------- +    if (dev_addr.has_key("addr") and not dev_addr["addr"].empty()){ +        std::vector<std::string> addrs; boost::split(addrs, dev_addr["addr"], boost::is_any_of(" ")); +        if (addrs.size() > 1){ +            device_addr_t fixed_dev_addr = dev_addr; +            fixed_dev_addr.pop("addr"); +            for (size_t i = 0; i < addrs.size(); i++){ +                fixed_dev_addr[str(boost::format("addr%d") % i)] = addrs[i]; +            } +            UHD_MSG(warning) << +                "addr = <space separated list of ip addresses> is deprecated.\n" +                "To address a multi-device, use multiple <key><index> = <val>.\n" +                "See the USRP-NXXX application notes. Two device example:\n" +                "    addr0 = 192.168.10.2\n" +                "    addr1 = 192.168.10.3\n" +            ; +            return separate_device_addr(fixed_dev_addr); +        } +    } +    //------------------------------------------------------------------ +    device_addrs_t dev_addrs; +    BOOST_FOREACH(const std::string &key, dev_addr.keys()){ +        boost::cmatch matches; +        if (not boost::regex_match(key.c_str(), matches, boost::regex("^(\\D+)(\\d*)$"))){ +            throw std::runtime_error("unknown key format: " + key); +        } +        std::string key_part(matches[1].first, matches[1].second); +        std::string num_part(matches[2].first, matches[2].second); +        size_t num = (num_part.empty())? 0 : boost::lexical_cast<size_t>(num_part); +        dev_addrs.resize(std::max(num+1, dev_addrs.size())); +        dev_addrs[num][key_part] = dev_addr[key]; +    } +    return dev_addrs; +} + +device_addr_t uhd::combine_device_addrs(const device_addrs_t &dev_addrs){ +    device_addr_t dev_addr; +    for (size_t i = 0; i < dev_addrs.size(); i++){ +        BOOST_FOREACH(const std::string &key, dev_addrs[i].keys()){ +            dev_addr[str(boost::format("%s%d") % key % i)] = dev_addrs[i][key]; +        } +    } +    return dev_addr; +} diff --git a/host/lib/types/mac_addr.cpp b/host/lib/types/mac_addr.cpp new file mode 100644 index 000000000..a5cb90f97 --- /dev/null +++ b/host/lib/types/mac_addr.cpp @@ -0,0 +1,75 @@ +// +// 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/types/mac_addr.hpp> +#include <uhd/exception.hpp> +#include <boost/tokenizer.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/cstdint.hpp> +#include <sstream> + +using namespace uhd; + +mac_addr_t::mac_addr_t(const byte_vector_t &bytes) : _bytes(bytes){ +    UHD_ASSERT_THROW(_bytes.size() == 6); +} + +mac_addr_t mac_addr_t::from_bytes(const byte_vector_t &bytes){ +    return mac_addr_t(bytes); +} + +mac_addr_t mac_addr_t::from_string(const std::string &mac_addr_str){ + +    byte_vector_t bytes; + +    try{ +        if (mac_addr_str.size() != 17){ +            throw uhd::value_error("expected exactly 17 characters"); +        } + +        //split the mac addr hex string at the colons +        boost::tokenizer<boost::char_separator<char> > hex_num_toks( +            mac_addr_str, boost::char_separator<char>(":")); +        BOOST_FOREACH(const std::string &hex_str, hex_num_toks){ +            int hex_num; +            std::istringstream iss(hex_str); +            iss >> std::hex >> hex_num; +            bytes.push_back(boost::uint8_t(hex_num)); +        } + +    } +    catch(std::exception const& e){ +        throw uhd::value_error(str( +            boost::format("Invalid mac address: %s\n\t%s") % mac_addr_str % e.what() +        )); +    } + +    return mac_addr_t::from_bytes(bytes); +} + +byte_vector_t mac_addr_t::to_bytes(void) const{ +    return _bytes; +} + +std::string mac_addr_t::to_string(void) const{ +    std::string addr = ""; +    BOOST_FOREACH(boost::uint8_t byte, this->to_bytes()){ +        addr += str(boost::format("%s%02x") % ((addr == "")?"":":") % int(byte)); +    } +    return addr; +} diff --git a/host/lib/types/ranges.cpp b/host/lib/types/ranges.cpp new file mode 100644 index 000000000..6e39bc688 --- /dev/null +++ b/host/lib/types/ranges.cpp @@ -0,0 +1,163 @@ +// +// Copyright 2011-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/types/ranges.hpp> +#include <uhd/exception.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/foreach.hpp> +#include <algorithm> +#include <sstream> + +using namespace uhd; + +/*********************************************************************** + * range_t implementation code + **********************************************************************/ +struct range_t::impl{ +    impl(double start, double stop, double step): +        start(start), stop(stop), step(step) +    { +        /* NOP */ +    } +    double start, stop, step; +}; + +range_t::range_t(double value): +    _impl(UHD_PIMPL_MAKE(impl, (value, value, 0))) +{ +    /* NOP */ +} + +range_t::range_t( +    double start, double stop, double step +): +    _impl(UHD_PIMPL_MAKE(impl, (start, stop, step))) +{ +    if (stop < start){ +        throw uhd::value_error("cannot make range where stop < start"); +    } +} + +double range_t::start(void) const{ +    return _impl->start; +} + +double range_t::stop(void) const{ +    return _impl->stop; +} + +double range_t::step(void) const{ +    return _impl->step; +} + +const std::string range_t::to_pp_string(void) const{ +    std::stringstream ss; +    ss << "(" << this->start(); +    if (this->start() != this->stop()) ss << ", " << this->stop(); +    if (this->step() != 0) ss << ", " << this->step(); +    ss << ")"; +    return ss.str(); +} + +/*********************************************************************** + * meta_range_t implementation code + **********************************************************************/ +void check_meta_range_monotonic(const meta_range_t &mr){ +    if (mr.empty()){ +        throw uhd::value_error("meta-range cannot be empty"); +    } +    for (size_t i = 1; i < mr.size(); i++){ +        if (mr.at(i).start() < mr.at(i-1).stop()){ +            throw uhd::value_error("meta-range is not monotonic"); +        } +    } +} + +meta_range_t::meta_range_t(void){ +    /* NOP */ +} + +meta_range_t::meta_range_t( +    double start, double stop, double step +): +    std::vector<range_t > (1, range_t(start, stop, step)) +{ +    /* NOP */ +} + +double meta_range_t::start(void) const{ +    check_meta_range_monotonic(*this); +    double min_start = this->front().start(); +    BOOST_FOREACH(const range_t &r, (*this)){ +        min_start = std::min(min_start, r.start()); +    } +    return min_start; +} + +double meta_range_t::stop(void) const{ +    check_meta_range_monotonic(*this); +    double max_stop = this->front().stop(); +    BOOST_FOREACH(const range_t &r, (*this)){ +        max_stop = std::max(max_stop, r.stop()); +    } +    return max_stop; +} + +double meta_range_t::step(void) const{ +    check_meta_range_monotonic(*this); +    std::vector<double> non_zero_steps; +    range_t last = this->front(); +    BOOST_FOREACH(const range_t &r, (*this)){ +        //steps at each range +        if (r.step() > 0) non_zero_steps.push_back(r.step()); +        //and steps in-between ranges +        double ibtw_step = r.start() - last.stop(); +        if (ibtw_step > 0) non_zero_steps.push_back(ibtw_step); +        //store ref to last +        last = r; +    } +    if (non_zero_steps.empty()) return 0; //all zero steps, its zero... +    return *std::min_element(non_zero_steps.begin(), non_zero_steps.end()); +} + +double meta_range_t::clip(double value, bool clip_step) const{ +    check_meta_range_monotonic(*this); +    double last_stop = this->front().stop(); +    BOOST_FOREACH(const range_t &r, (*this)){ +        //in-between ranges, clip to nearest +        if (value < r.start()){ +            return (std::abs(value - r.start()) < std::abs(value - last_stop))? +                r.start() : last_stop; +        } +        //in this range, clip here +        if (value <= r.stop()){ +            if (not clip_step or r.step() == 0) return value; +            return boost::math::round((value - r.start())/r.step())*r.step() + r.start(); +        } +        //continue on to the next range +        last_stop = r.stop(); +    } +    return last_stop; +} + +const std::string meta_range_t::to_pp_string(void) const{ +    std::stringstream ss; +    BOOST_FOREACH(const range_t &r, (*this)){ +        ss << r.to_pp_string() << std::endl; +    } +    return ss.str(); +} diff --git a/host/lib/types/sensors.cpp b/host/lib/types/sensors.cpp new file mode 100644 index 000000000..52a63d14c --- /dev/null +++ b/host/lib/types/sensors.cpp @@ -0,0 +1,94 @@ +// +// Copyright 2011-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/types/sensors.hpp> +#include <uhd/exception.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> + +using namespace uhd; + +sensor_value_t::sensor_value_t( +    const std::string &name, +    bool value, +    const std::string &utrue, +    const std::string &ufalse +): +    name(name), value(value?"true":"false"), +    unit(value?utrue:ufalse), type(BOOLEAN) +{ +    /* NOP */ +} + +sensor_value_t::sensor_value_t( +    const std::string &name, +    signed value, +    const std::string &unit, +    const std::string &formatter +): +    name(name), value(str(boost::format(formatter) % value)), +    unit(unit), type(INTEGER) +{ +    /* NOP */ +} + +sensor_value_t::sensor_value_t( +    const std::string &name, +    double value, +    const std::string &unit, +    const std::string &formatter +): +    name(name), value(str(boost::format(formatter) % value)), +    unit(unit), type(REALNUM) +{ +    /* NOP */ +} + +sensor_value_t::sensor_value_t( +    const std::string &name, +    const std::string &value, +    const std::string &unit +): +    name(name), value(value), +    unit(unit), type(STRING) +{ +    /* NOP */ +} + +std::string sensor_value_t::to_pp_string(void) const{ +    switch(type){ +    case BOOLEAN: +        return str(boost::format("%s: %s") % name % unit); +    case INTEGER: +    case REALNUM: +    case STRING: +        return str(boost::format("%s: %s %s") % name % value % unit); +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +bool sensor_value_t::to_bool(void) const{ +    return value == "true"; +} + +signed sensor_value_t::to_int(void) const{ +    return boost::lexical_cast<signed>(value); +} + +double sensor_value_t::to_real(void) const{ +    return boost::lexical_cast<double>(value); +} diff --git a/host/lib/types/serial.cpp b/host/lib/types/serial.cpp new file mode 100644 index 000000000..9e9d32954 --- /dev/null +++ b/host/lib/types/serial.cpp @@ -0,0 +1,78 @@ +// +// 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/types/serial.hpp> +#include <boost/thread.hpp> //for sleeping +#include <boost/assign/list_of.hpp> + +using namespace uhd; + +spi_config_t::spi_config_t(edge_t edge): +    mosi_edge(edge), +    miso_edge(edge) +{ +    /* NOP */ +} + +void i2c_iface::write_eeprom( +    boost::uint8_t addr, +    boost::uint8_t offset, +    const byte_vector_t &bytes +){ +    for (size_t i = 0; i < bytes.size(); i++){ +        //write a byte at a time, its easy that way +        byte_vector_t cmd = boost::assign::list_of(offset+i)(bytes[i]); +        this->write_i2c(addr, cmd); +        boost::this_thread::sleep(boost::posix_time::milliseconds(10)); //worst case write +    } +} + +byte_vector_t i2c_iface::read_eeprom( +    boost::uint8_t addr, +    boost::uint8_t offset, +    size_t num_bytes +){ +    byte_vector_t bytes; +    for (size_t i = 0; i < num_bytes; i++){ +        //do a zero byte write to start read cycle +        this->write_i2c(addr, byte_vector_t(1, offset+i)); +        bytes.push_back(this->read_i2c(addr, 1).at(0)); +    } +    return bytes; +} + +boost::uint32_t spi_iface::read_spi( +    int which_slave, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    return transact_spi( +        which_slave, config, data, num_bits, true +    ); +} + +void spi_iface::write_spi( +    int which_slave, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    transact_spi( +        which_slave, config, data, num_bits, false +    ); +} diff --git a/host/lib/types/time_spec.cpp b/host/lib/types/time_spec.cpp new file mode 100644 index 000000000..a785332c2 --- /dev/null +++ b/host/lib/types/time_spec.cpp @@ -0,0 +1,154 @@ +// +// 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/types/time_spec.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; + +/*********************************************************************** + * Time spec system time + **********************************************************************/ + +/*! + * Creates a time spec from system counts: + * TODO make part of API as a static factory function + * The counts type is 64 bits and will overflow the ticks type of long. + * Therefore, divmod the counts into seconds + sub-second counts first. + */ +#include <inttypes.h> //imaxdiv, intmax_t +static UHD_INLINE time_spec_t time_spec_t_from_counts(intmax_t counts, intmax_t freq){ +    imaxdiv_t divres = imaxdiv(counts, freq); +    return time_spec_t(time_t(divres.quot), double(divres.rem)/freq); +} + +#ifdef HAVE_CLOCK_GETTIME +#include <time.h> +time_spec_t time_spec_t::get_system_time(void){ +    timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); +    return time_spec_t(ts.tv_sec, ts.tv_nsec, 1e9); +} +#endif /* HAVE_CLOCK_GETTIME */ + + +#ifdef HAVE_MACH_ABSOLUTE_TIME +#include <mach/mach_time.h> +time_spec_t time_spec_t::get_system_time(void){ +    mach_timebase_info_data_t info; mach_timebase_info(&info); +    intmax_t nanosecs = mach_absolute_time()*info.numer/info.denom; +    return time_spec_t_from_counts(nanosecs, intmax_t(1e9)); +} +#endif /* HAVE_MACH_ABSOLUTE_TIME */ + + +#ifdef HAVE_QUERY_PERFORMANCE_COUNTER +#include <Windows.h> +time_spec_t time_spec_t::get_system_time(void){ +    LARGE_INTEGER counts, freq; +    QueryPerformanceCounter(&counts); +    QueryPerformanceFrequency(&freq); +    return time_spec_t_from_counts(counts.QuadPart, freq.QuadPart); +} +#endif /* HAVE_QUERY_PERFORMANCE_COUNTER */ + + +#ifdef HAVE_MICROSEC_CLOCK +#include <boost/date_time/posix_time/posix_time.hpp> +namespace pt = boost::posix_time; +time_spec_t time_spec_t::get_system_time(void){ +    pt::ptime time_now = pt::microsec_clock::universal_time(); +    pt::time_duration time_dur = time_now - pt::from_time_t(0); +    return time_spec_t( +        time_t(time_dur.total_seconds()), +        long(time_dur.fractional_seconds()), +        double(pt::time_duration::ticks_per_second()) +    ); +} +#endif /* HAVE_MICROSEC_CLOCK */ + +/*********************************************************************** + * Time spec constructors + **********************************************************************/ +time_spec_t::time_spec_t(double secs): +    _full_secs(0), +    _frac_secs(secs) +{ +    /* NOP */ +} + +time_spec_t::time_spec_t(time_t full_secs, double frac_secs): +    _full_secs(full_secs), +    _frac_secs(frac_secs) +{ +    /* NOP */ +} + +time_spec_t::time_spec_t(time_t full_secs, long tick_count, double tick_rate): +    _full_secs(full_secs), +    _frac_secs(tick_count/tick_rate) +{ +    /* NOP */ +} + +/*********************************************************************** + * Time spec accessors + **********************************************************************/ +long time_spec_t::get_tick_count(double tick_rate) const{ +    return boost::math::iround(this->get_frac_secs()*tick_rate); +} + +double time_spec_t::get_real_secs(void) const{ +    return this->_full_secs + this->_frac_secs; +} + +time_t time_spec_t::get_full_secs(void) const{ +    return this->_full_secs + time_t(this->_frac_secs); +} + +double time_spec_t::get_frac_secs(void) const{ +    return this->_frac_secs - time_t(this->_frac_secs); +} + +/*********************************************************************** + * Time spec math overloads + **********************************************************************/ +time_spec_t &time_spec_t::operator+=(const time_spec_t &rhs){ +    this->_full_secs += rhs.get_full_secs(); +    this->_frac_secs += rhs.get_frac_secs(); +    return *this; +} + +time_spec_t &time_spec_t::operator-=(const time_spec_t &rhs){ +    this->_full_secs -= rhs.get_full_secs(); +    this->_frac_secs -= rhs.get_frac_secs(); +    return *this; +} + +bool uhd::operator==(const time_spec_t &lhs, const time_spec_t &rhs){ +    return +        lhs.get_full_secs() == rhs.get_full_secs() and +        lhs.get_frac_secs() == rhs.get_frac_secs() +    ; +} + +bool uhd::operator<(const time_spec_t &lhs, const time_spec_t &rhs){ +    return ( +        (lhs.get_full_secs() < rhs.get_full_secs()) or ( +        (lhs.get_full_secs() == rhs.get_full_secs()) and +        (lhs.get_frac_secs() < rhs.get_frac_secs()) +    )); +} diff --git a/host/lib/types/tune.cpp b/host/lib/types/tune.cpp new file mode 100644 index 000000000..154f0990f --- /dev/null +++ b/host/lib/types/tune.cpp @@ -0,0 +1,52 @@ +// +// 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/types/tune_request.hpp> +#include <uhd/types/tune_result.hpp> +#include <boost/format.hpp> + +using namespace uhd; + +tune_request_t::tune_request_t(double target_freq): +    target_freq(target_freq), +    rf_freq_policy(POLICY_AUTO), +    dsp_freq_policy(POLICY_AUTO) +{ +    /* NOP */ +} + +tune_request_t::tune_request_t(double target_freq, double lo_off): +    target_freq(target_freq), +    rf_freq_policy(POLICY_MANUAL), +    rf_freq(target_freq + lo_off), +    dsp_freq_policy(POLICY_AUTO) +{ +    /* NOP */ +} + +std::string tune_result_t::to_pp_string(void) const{ +    return str(boost::format( +        "Tune Result:\n" +        "    Target RF  Freq: %f (MHz)\n" +        "    Actual RF  Freq: %f (MHz)\n" +        "    Target DSP Freq: %f (MHz)\n" +        "    Actual DSP Freq: %f (MHz)\n" +    ) +        % (target_rf_freq/1e6)  % (actual_rf_freq/1e6) +        % (target_dsp_freq/1e6) % (actual_dsp_freq/1e6) +    ); +} diff --git a/host/lib/types/types.cpp b/host/lib/types/types.cpp new file mode 100644 index 000000000..7c65d2997 --- /dev/null +++ b/host/lib/types/types.cpp @@ -0,0 +1,91 @@ +// +// 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/types/stream_cmd.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/io_type.hpp> +#include <boost/cstdint.hpp> +#include <stdexcept> +#include <complex> +#include <vector> + +using namespace uhd; + +/*********************************************************************** + * stream command + **********************************************************************/ +stream_cmd_t::stream_cmd_t(const stream_mode_t &stream_mode): +    stream_mode(stream_mode), +    num_samps(0), +    stream_now(true) +{ +    /* NOP */ +} + +/*********************************************************************** + * metadata + **********************************************************************/ +tx_metadata_t::tx_metadata_t(void): +    has_time_spec(false), +    time_spec(time_spec_t()), +    start_of_burst(false), +    end_of_burst(false) +{ +    /* NOP */ +} + +/*********************************************************************** + * otw type + **********************************************************************/ +size_t otw_type_t::get_sample_size(void) const{ +    return (this->width * 2) / 8; +} + +otw_type_t::otw_type_t(void): +    width(0), +    shift(0), +    byteorder(BO_NATIVE) +{ +    /* NOP */ +} + +/*********************************************************************** + * io type + **********************************************************************/ +static std::vector<size_t> get_tid_size_table(void){ +    std::vector<size_t> table(128, 0); +    table[size_t(io_type_t::COMPLEX_FLOAT64)] = sizeof(std::complex<double>); +    table[size_t(io_type_t::COMPLEX_FLOAT32)] = sizeof(std::complex<float>); +    table[size_t(io_type_t::COMPLEX_INT16)]   = sizeof(std::complex<boost::int16_t>); +    table[size_t(io_type_t::COMPLEX_INT8)]    = sizeof(std::complex<boost::int8_t>); +    return table; +} + +static const std::vector<size_t> tid_size_table(get_tid_size_table()); + +io_type_t::io_type_t(tid_t tid): +    size(tid_size_table[size_t(tid) & 0x7f]), tid(tid) +{ +    /* NOP */ +} + +io_type_t::io_type_t(size_t size): +    size(size), tid(CUSTOM_TYPE) +{ +    /* NOP */ +} diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt new file mode 100644 index 000000000..8ae379f73 --- /dev/null +++ b/host/lib/usrp/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# 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 +######################################################################## +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/dboard_base.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/dboard_eeprom.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/dboard_id.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/dboard_manager.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/gps_ctrl.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/mboard_eeprom.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/subdev_spec.cpp +) + +INCLUDE_SUBDIRECTORY(cores) +INCLUDE_SUBDIRECTORY(dboard) +INCLUDE_SUBDIRECTORY(common) +INCLUDE_SUBDIRECTORY(usrp1) +INCLUDE_SUBDIRECTORY(usrp2) +INCLUDE_SUBDIRECTORY(b100) +INCLUDE_SUBDIRECTORY(e100) diff --git a/host/lib/usrp/README b/host/lib/usrp/README new file mode 100644 index 000000000..344209179 --- /dev/null +++ b/host/lib/usrp/README @@ -0,0 +1,15 @@ +######################################################################## +# lib USRP directories: +######################################################################## + +dboard: +    Daughterboard implementation code for all USRP daughterboards + +usrp1: +    Implementation code for the USB-based USRP Classic motherboard. + +usrp2: +    Implementation code for USRP2, USRP-N200, and USRP-N210. + +usrp_e100: +    Implementation code for USRP-E100. diff --git a/host/lib/usrp/b100/CMakeLists.txt b/host/lib/usrp/b100/CMakeLists.txt new file mode 100644 index 000000000..1237f52d1 --- /dev/null +++ b/host/lib/usrp/b100/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the B100 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF) + +IF(ENABLE_B100) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/b100_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/b100_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp +    ) +ENDIF(ENABLE_B100) diff --git a/host/lib/usrp/b100/b100_ctrl.cpp b/host/lib/usrp/b100/b100_ctrl.cpp new file mode 100644 index 000000000..e08b47ce4 --- /dev/null +++ b/host/lib/usrp/b100/b100_ctrl.cpp @@ -0,0 +1,252 @@ +// +// 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 "b100_ctrl.hpp" +#include <uhd/transport/bounded_buffer.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/utils/tasks.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/serial.hpp> +#include "ctrl_packet.hpp" +#include <boost/thread/thread.hpp> +#include <boost/bind.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::zero_copy_if::sptr ctrl_transport): +        sync_ctrl_fifo(2), +        _ctrl_transport(ctrl_transport), +        _seq(0) +    { +        viking_marauder = task::make(boost::bind(&b100_ctrl_impl::viking_marauder_loop, this)); +    } + +    int write(boost::uint32_t addr, const ctrl_data_t &data); +    ctrl_data_t read(boost::uint32_t addr, size_t len); + +    bool get_ctrl_data(ctrl_data_t &pkt_data, double timeout); + +    void poke32(wb_addr_type addr, boost::uint32_t data){ +        boost::mutex::scoped_lock lock(_ctrl_mutex); + +        ctrl_data_t words(2); +        words[0] = data & 0x0000FFFF; +        words[1] = data >> 16; +        this->write(addr, words); +    } + +    boost::uint32_t peek32(wb_addr_type addr){ +        boost::mutex::scoped_lock lock(_ctrl_mutex); + +        ctrl_data_t words = this->read(addr, 2); +        return boost::uint32_t((boost::uint32_t(words[1]) << 16) | words[0]); +    } + +    void poke16(wb_addr_type addr, boost::uint16_t data){ +        boost::mutex::scoped_lock lock(_ctrl_mutex); + +        ctrl_data_t words(1); +        words[0] = data; +        this->write(addr, words); +    } + +    boost::uint16_t peek16(wb_addr_type addr){ +        boost::mutex::scoped_lock lock(_ctrl_mutex); + +        ctrl_data_t words = this->read(addr, 1); +        return boost::uint16_t(words[0]); +    } + +    void set_async_cb(const async_cb_type &async_cb){ +        boost::mutex::scoped_lock lock(_async_mutex); +        _async_cb = async_cb; +    } + +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(void); +    bounded_buffer<ctrl_data_t> sync_ctrl_fifo; +    async_cb_type _async_cb; +    task::sptr viking_marauder; + +    uhd::transport::zero_copy_if::sptr _ctrl_transport; +    boost::uint8_t _seq; +    boost::mutex _ctrl_mutex, _async_mutex; +}; + +/*********************************************************************** + * 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)]; + +    //flush anything that might be in the queue +    while (get_ctrl_data(pkt.data, 0.0)){ +        UHD_MSG(error) << "B100: control read found unexpected packet." << std::endl; +    } + +    pack_ctrl_pkt(pkt_buff, pkt); +    send_pkt(pkt_buff); + +    //block with timeout waiting for the response to appear +    if (not get_ctrl_data(pkt.data, 0.1)) throw uhd::runtime_error( +        "B100: timeout waiting for control response packet." +    ); + +    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(void){ +    set_thread_priority_safe(); + +    while (not boost::this_thread::interruption_requested()){ +        managed_recv_buffer::sptr rbuf = _ctrl_transport->get_recv_buff(1.0); +        if(rbuf.get() == NULL) 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)) { +                UHD_MSG(error) +                    << "Sequence error on control channel." << std::endl +                    << "Exiting control loop." << std::endl +                ; +                return; +            } +            if(pkt.pkt_meta.len > (CTRL_PACKET_LENGTH - CTRL_PACKET_HEADER_LENGTH)) { +                UHD_MSG(error) +                    << "Control channel packet length too long" << std::endl +                    << "Exiting control loop." << std::endl +                ; +                return; +            } + +            //push it onto the queue +            sync_ctrl_fifo.push_with_pop_on_full(pkt.data); +        } +        else{ //otherwise let the async callback handle it +            boost::mutex::scoped_lock lock(_async_mutex); +            if (not _async_cb.empty()) _async_cb(rbuf); +        } +    } +} + +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); +} + +/*********************************************************************** + * Public make function for b100_ctrl interface + **********************************************************************/ +b100_ctrl::sptr b100_ctrl::make(uhd::transport::zero_copy_if::sptr ctrl_transport){ +    return sptr(new b100_ctrl_impl(ctrl_transport)); +} diff --git a/host/lib/usrp/b100/b100_ctrl.hpp b/host/lib/usrp/b100/b100_ctrl.hpp new file mode 100644 index 000000000..74884d525 --- /dev/null +++ b/host/lib/usrp/b100/b100_ctrl.hpp @@ -0,0 +1,70 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CTRL_HPP +#define INCLUDED_B100_CTRL_HPP + +#include "wb_iface.hpp" +#include <uhd/transport/usb_zero_copy.hpp> +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include "ctrl_packet.hpp" +#include <boost/function.hpp> + +class b100_ctrl : boost::noncopyable, public wb_iface{ +public: +    typedef boost::shared_ptr<b100_ctrl> sptr; +    typedef boost::function<void(uhd::transport::managed_recv_buffer::sptr)> async_cb_type; + +    /*! +     * 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::zero_copy_if::sptr ctrl_transport); + +    //! set an async callback for messages +    virtual void set_async_cb(const async_cb_type &async_cb) = 0; + +    /*! +     * 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; + +}; + +#endif /* INCLUDED_B100_CTRL_HPP */ diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp new file mode 100644 index 000000000..ea9c91c50 --- /dev/null +++ b/host/lib/usrp/b100/b100_impl.cpp @@ -0,0 +1,467 @@ +// +// 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 "b100_impl.hpp" +#include "b100_ctrl.hpp" +#include "fpga_regs_standard.h" +#include "usrp_i2c_addr.h" +#include "usrp_commands.h" +#include <uhd/transport/usb_control.hpp> +#include "ctrl_packet.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; + +    //Return an empty list of addresses when an address is specified, +    //since an address is intended for a different, non-USB, device. +    if (hint.has_key("addr")) 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)) { +        //extract the firmware path for the b100 +        std::string b100_fw_image; +        try{ +            b100_fw_image = find_image_path(hint.get("fw", B100_FW_FILE_NAME)); +        } +        catch(...){ +            UHD_MSG(warning) << boost::format( +                "Could not locate B100 firmware.\n" +                "Please install the images package.\n" +            ); +            return b100_addrs; +        } +        UHD_LOG << "the  firmware image: " << b100_fw_image << std::endl; + +        usb_control::sptr control; +        try{control = usb_control::make(handle);} +        catch(const uhd::exception &){continue;} //ignore claimed + +        fx2_ctrl::make(control)->usrp_load_firmware(b100_fw_image); +    } + +    //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"; +        new_addr["serial"] = handle->get_serial(); + +        //Attempt to read the name from the EEPROM and perform filtering. +        try{ +            usb_control::sptr control = usb_control::make(handle); +            fx2_ctrl::sptr fx2_ctrl = fx2_ctrl::make(control); +            const mboard_eeprom_t mb_eeprom = mboard_eeprom_t(*fx2_ctrl, mboard_eeprom_t::MAP_B000); +            new_addr["name"] = mb_eeprom["name"]; +        } +        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"] = ""; +        } + +        //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){ +    return device::sptr(new b100_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_b100_device){ +    device::register_device(&b100_find, &b100_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +b100_impl::b100_impl(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"] : B100_FPGA_FILE_NAME +    ); + +    //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 = fx2_ctrl::make(fx2_transport); +    this->check_fw_compat(); //check after making fx2 +    //-- setup clock after making fx2 and before loading fpga --// +    _clock_ctrl = b100_clock_ctrl::make(_fx2_ctrl, device_addr.cast<double>("master_clock_rate", B100_DEFAULT_TICK_RATE)); +    _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"); + +    _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"; + +    _ctrl_transport = usb_zero_copy::make( +        handle, +        8, +        4, +        ctrl_xport_args +    ); + +    //////////////////////////////////////////////////////////////////// +    // Create controller objects +    //////////////////////////////////////////////////////////////////// +    _fpga_ctrl = b100_ctrl::make(_ctrl_transport); +    this->enable_gpif(true); //TODO best place to put this? +    this->check_fpga_compat(); //check after making control +    _fpga_i2c_ctrl = i2c_core_100::make(_fpga_ctrl, B100_REG_SLAVE(3)); +    _fpga_spi_ctrl = spi_core_100::make(_fpga_ctrl, B100_REG_SLAVE(2)); + +    //////////////////////////////////////////////////////////////////// +    // Initialize the properties tree +    //////////////////////////////////////////////////////////////////// +    _tree = property_tree::make(); +    _tree->create<std::string>("/name").set("B-Series Device"); +    const fs_path mb_path = "/mboards/0"; +    _tree->create<std::string>(mb_path / "name").set("B100 (B-Hundo)"); +    _tree->create<std::string>(mb_path / "load_eeprom") +        .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + +    //////////////////////////////////////////////////////////////////// +    // setup the mboard eeprom +    //////////////////////////////////////////////////////////////////// +    const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, mboard_eeprom_t::MAP_B000); +    _tree->create<mboard_eeprom_t>(mb_path / "eeprom") +        .set(mb_eeprom) +        .subscribe(boost::bind(&b100_impl::set_mb_eeprom, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create clock control objects +    //////////////////////////////////////////////////////////////////// +    //^^^ clock created up top, just reg props here... ^^^ +    _tree->create<double>(mb_path / "tick_rate") +        .publish(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) +        .subscribe(boost::bind(&b100_impl::update_tick_rate, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create codec control objects +    //////////////////////////////////////////////////////////////////// +    _codec_ctrl = b100_codec_ctrl::make(_fpga_spi_ctrl); +    const fs_path rx_codec_path = mb_path / "rx_codecs/A"; +    const fs_path tx_codec_path = mb_path / "tx_codecs/A"; +    _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); +    _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(b100_codec_ctrl::rx_pga_gain_range); +    _tree->create<double>(rx_codec_path / "gains/pga/value") +        .coerce(boost::bind(&b100_impl::update_rx_codec_gain, this, _1)); +    _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); +    _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(b100_codec_ctrl::tx_pga_gain_range); +    _tree->create<double>(tx_codec_path / "gains/pga/value") +        .subscribe(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) +        .publish(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); + +    //////////////////////////////////////////////////////////////////// +    // and do the misc mboard sensors +    //////////////////////////////////////////////////////////////////// +    //none for now... +    _tree->create<int>(mb_path / "sensors"); //phony property so this dir exists + +    //////////////////////////////////////////////////////////////////// +    // create frontend control objects +    //////////////////////////////////////////////////////////////////// +    _rx_fe = rx_frontend_core_200::make(_fpga_ctrl, B100_REG_SR_ADDR(B100_SR_RX_FRONT)); +    _tx_fe = tx_frontend_core_200::make(_fpga_ctrl, B100_REG_SR_ADDR(B100_SR_TX_FRONT)); +    //TODO lots of properties to expose here for frontends +    _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") +        .subscribe(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1)); +    _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") +        .subscribe(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create rx dsp control objects +    //////////////////////////////////////////////////////////////////// +    _rx_dsps.push_back(rx_dsp_core_200::make( +        _fpga_ctrl, B100_REG_SR_ADDR(B100_SR_RX_DSP0), B100_REG_SR_ADDR(B100_SR_RX_CTRL0), B100_RX_SID_BASE + 0 +    )); +    _rx_dsps.push_back(rx_dsp_core_200::make( +        _fpga_ctrl, B100_REG_SR_ADDR(B100_SR_RX_DSP1), B100_REG_SR_ADDR(B100_SR_RX_CTRL1), B100_RX_SID_BASE + 1 +    )); +    for (size_t dspno = 0; dspno < _rx_dsps.size(); dspno++){ +        _rx_dsps[dspno]->set_link_rate(B100_LINK_RATE_BPS); +        _tree->access<double>(mb_path / "tick_rate") +            .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); +        fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); +        _tree->create<double>(rx_dsp_path / "rate/value") +            .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) +            .subscribe(boost::bind(&b100_impl::update_rx_samp_rate, this, _1)); +        _tree->create<double>(rx_dsp_path / "freq/value") +            .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); +        _tree->create<meta_range_t>(rx_dsp_path / "freq/range") +            .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); +        _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") +            .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); +    } + +    //////////////////////////////////////////////////////////////////// +    // create tx dsp control objects +    //////////////////////////////////////////////////////////////////// +    _tx_dsp = tx_dsp_core_200::make( +        _fpga_ctrl, B100_REG_SR_ADDR(B100_SR_TX_DSP), B100_REG_SR_ADDR(B100_SR_TX_CTRL), B100_TX_ASYNC_SID +    ); +    _tx_dsp->set_link_rate(B100_LINK_RATE_BPS); +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); +    _tree->create<double>(mb_path / "tx_dsps/0/rate/value") +        .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) +        .subscribe(boost::bind(&b100_impl::update_tx_samp_rate, this, _1)); +    _tree->create<double>(mb_path / "tx_dsps/0/freq/value") +        .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); +    _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") +        .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + +    //////////////////////////////////////////////////////////////////// +    // create time control objects +    //////////////////////////////////////////////////////////////////// +    time64_core_200::readback_bases_type time64_rb_bases; +    time64_rb_bases.rb_secs_now = B100_REG_RB_TIME_NOW_SECS; +    time64_rb_bases.rb_ticks_now = B100_REG_RB_TIME_NOW_TICKS; +    time64_rb_bases.rb_secs_pps = B100_REG_RB_TIME_PPS_SECS; +    time64_rb_bases.rb_ticks_pps = B100_REG_RB_TIME_PPS_TICKS; +    _time64 = time64_core_200::make( +        _fpga_ctrl, B100_REG_SR_ADDR(B100_SR_TIME64), time64_rb_bases +    ); +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); +    _tree->create<time_spec_t>(mb_path / "time/now") +        .publish(boost::bind(&time64_core_200::get_time_now, _time64)) +        .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); +    _tree->create<time_spec_t>(mb_path / "time/pps") +        .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) +        .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); +    //setup time source props +    _tree->create<std::string>(mb_path / "time_source/value") +        .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); +    _tree->create<std::vector<std::string> >(mb_path / "time_source/options") +        .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); +    //setup reference source props +    _tree->create<std::string>(mb_path / "clock_source/value") +        .subscribe(boost::bind(&b100_impl::update_clock_source, this, _1)); +    static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); +    _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); + +    //////////////////////////////////////////////////////////////////// +    // create dboard control objects +    //////////////////////////////////////////////////////////////////// + +    //read the dboard eeprom to extract the dboard ids +    dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; +    rx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_RX_A); +    tx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_A); +    gdb_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_A ^ 5); + +    //create the properties and register subscribers +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") +        .set(rx_db_eeprom) +        .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1)); +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") +        .set(tx_db_eeprom) +        .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1)); +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") +        .set(gdb_eeprom) +        .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1)); + +    //create a new dboard interface and manager +    _dboard_iface = make_b100_dboard_iface(_fpga_ctrl, _fpga_i2c_ctrl, _fpga_spi_ctrl, _clock_ctrl, _codec_ctrl); +    _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); +    _dboard_manager = dboard_manager::make( +        rx_db_eeprom.id, +        ((gdb_eeprom.id == dboard_id_t::none())? tx_db_eeprom : gdb_eeprom).id, +        _dboard_iface +    ); +    BOOST_FOREACH(const std::string &name, _dboard_manager->get_rx_subdev_names()){ +        dboard_manager::populate_prop_tree_from_subdev( +            _tree->subtree(mb_path / "dboards/A/rx_frontends" / name), +            _dboard_manager->get_rx_subdev(name) +        ); +    } +    BOOST_FOREACH(const std::string &name, _dboard_manager->get_tx_subdev_names()){ +        dboard_manager::populate_prop_tree_from_subdev( +            _tree->subtree(mb_path / "dboards/A/tx_frontends" / name), +            _dboard_manager->get_tx_subdev(name) +        ); +    } + +    //initialize io handling +    this->io_init(); + +    //////////////////////////////////////////////////////////////////// +    // do some post-init tasks +    //////////////////////////////////////////////////////////////////// +    _tree->access<double>(mb_path / "tick_rate").update() //update and then subscribe the clock callback +        .subscribe(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + +    //and now that the tick rate is set, init the host rates to something +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ +        _tree->access<double>(mb_path / "rx_dsps" / name / "rate" / "value").set(1e6); +    } +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ +        _tree->access<double>(mb_path / "tx_dsps" / name / "rate" / "value").set(1e6); +    } + +    _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(subdev_spec_t("A:"+_dboard_manager->get_rx_subdev_names()[0])); +    _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(subdev_spec_t("A:"+_dboard_manager->get_tx_subdev_names()[0])); +    _tree->access<std::string>(mb_path / "clock_source/value").set("internal"); +    _tree->access<std::string>(mb_path / "time_source/value").set("none"); +} + +b100_impl::~b100_impl(void){ +    //set an empty async callback now that we deconstruct +    _fpga_ctrl->set_async_cb(b100_ctrl::async_cb_type()); +} + +void b100_impl::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 != B100_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." +        ) % B100_FW_COMPAT_NUM % fw_compat_num)); +    } +} + +void b100_impl::check_fpga_compat(void){ +    const boost::uint16_t fpga_compat_num = _fpga_ctrl->peek16(B100_REG_MISC_COMPAT); +    if (fpga_compat_num != B100_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." +        ) % B100_FPGA_COMPAT_NUM % fpga_compat_num)); +    } +} + +double b100_impl::update_rx_codec_gain(const double gain){ +    //set gain on both I and Q, readback on one +    //TODO in the future, gains should have individual control +    _codec_ctrl->set_rx_pga_gain(gain, 'A'); +    _codec_ctrl->set_rx_pga_gain(gain, 'B'); +    return _codec_ctrl->get_rx_pga_gain('A'); +} + +void b100_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom){ +    mb_eeprom.commit(*_fx2_ctrl, mboard_eeprom_t::MAP_B000); +} + +void b100_impl::set_db_eeprom(const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ +    if (type == "rx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_RX_A); +    if (type == "tx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_A); +    if (type == "gdb") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_A ^ 5); +} + +void b100_impl::update_clock_source(const std::string &source){ +    if      (source == "auto")     _clock_ctrl->use_auto_ref(); +    else if (source == "internal") _clock_ctrl->use_internal_ref(); +    else if (source == "external") _clock_ctrl->use_external_ref(); +    else throw uhd::runtime_error("unhandled clock configuration reference source: " + source); +} + +////////////////// some GPIF preparation related stuff ///////////////// +void b100_impl::reset_gpif(const boost::uint16_t ep) { +    _fx2_ctrl->usrp_control_write(VRQ_RESET_GPIF, ep, ep, 0, 0); +} + +void b100_impl::enable_gpif(const bool en) { +    _fx2_ctrl->usrp_control_write(VRQ_ENABLE_GPIF, en ? 1 : 0, 0, 0, 0); +} + +void b100_impl::clear_fpga_fifo(void) { +    _fx2_ctrl->usrp_control_write(VRQ_CLEAR_FPGA_FIFO, 0, 0, 0, 0); +} diff --git a/host/lib/usrp/b100/b100_impl.hpp b/host/lib/usrp/b100/b100_impl.hpp new file mode 100644 index 000000000..26f363479 --- /dev/null +++ b/host/lib/usrp/b100/b100_impl.hpp @@ -0,0 +1,137 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_IMPL_HPP +#define INCLUDED_B100_IMPL_HPP + +#include "fx2_ctrl.hpp" +#include "b100_ctrl.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "spi_core_100.hpp" +#include "i2c_core_100.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "rx_dsp_core_200.hpp" +#include "tx_dsp_core_200.hpp" +#include "time64_core_200.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.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/mboard_eeprom.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> + +static const double          B100_LINK_RATE_BPS = 256e6/8; //pratical link rate (< 480 Mbps) +static const std::string     B100_FW_FILE_NAME = "usrp_b100_fw.ihx"; +static const std::string     B100_FPGA_FILE_NAME = "usrp_b100_fpga.bin"; +static const boost::uint16_t B100_FW_COMPAT_NUM = 0x02; +static const boost::uint16_t B100_FPGA_COMPAT_NUM = 0x05; +static const boost::uint32_t B100_RX_SID_BASE = 2; +static const boost::uint32_t B100_TX_ASYNC_SID = 1; +static const double          B100_DEFAULT_TICK_RATE = 64e6; + +//! Make a b100 dboard interface +uhd::usrp::dboard_iface::sptr make_b100_dboard_iface( +    wb_iface::sptr wb_iface, +    uhd::i2c_iface::sptr i2c_iface, +    uhd::spi_iface::sptr spi_iface, +    b100_clock_ctrl::sptr clock, +    b100_codec_ctrl::sptr codec +); + +//! Implementation guts +class b100_impl : public uhd::device { +public: +    //structors +    b100_impl(const uhd::device_addr_t &); +    ~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: +    uhd::property_tree::sptr _tree; + +    //controllers +    spi_core_100::sptr _fpga_spi_ctrl; +    i2c_core_100::sptr _fpga_i2c_ctrl; +    rx_frontend_core_200::sptr _rx_fe; +    tx_frontend_core_200::sptr _tx_fe; +    std::vector<rx_dsp_core_200::sptr> _rx_dsps; +    tx_dsp_core_200::sptr _tx_dsp; +    time64_core_200::sptr _time64; +    b100_clock_ctrl::sptr _clock_ctrl; +    b100_codec_ctrl::sptr _codec_ctrl; +    b100_ctrl::sptr _fpga_ctrl; +    uhd::usrp::fx2_ctrl::sptr _fx2_ctrl; + +    //transports +    uhd::transport::zero_copy_if::sptr _data_transport, _ctrl_transport; + +    //dboard stuff +    uhd::usrp::dboard_manager::sptr _dboard_manager; +    uhd::usrp::dboard_iface::sptr _dboard_iface; + +    //handle io stuff +    uhd::otw_type_t _rx_otw_type, _tx_otw_type; +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void io_init(void); + +    //device properties interface +    void get(const wax::obj &, wax::obj &val){ +        val = _tree; //entry point into property tree +    } +    uhd::property_tree::sptr get_tree(void) const{ +        return _tree; +    } + +    void check_fw_compat(void); +    void check_fpga_compat(void); +    double update_rx_codec_gain(const double); //sets A and B at once +    void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); +    void set_db_eeprom(const std::string &, const uhd::usrp::dboard_eeprom_t &); +    void update_tick_rate(const double rate); +    void update_rx_samp_rate(const double rate); +    void update_tx_samp_rate(const double rate); +    void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void update_clock_source(const std::string &); +    void reset_gpif(const boost::uint16_t); +    void enable_gpif(const bool); +    void clear_fpga_fifo(void); +    void handle_async_message(uhd::transport::managed_recv_buffer::sptr); +}; + +#endif /* INCLUDED_b100_IMPL_HPP */ diff --git a/host/lib/usrp/b100/b100_regs.hpp b/host/lib/usrp/b100/b100_regs.hpp new file mode 100644 index 000000000..5e24f9937 --- /dev/null +++ b/host/lib/usrp/b100/b100_regs.hpp @@ -0,0 +1,147 @@ + + +//////////////////////////////////////////////////////////////// +// +//         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 INCLUDED_B100_REGS_HPP +#define INCLUDED_B100_REGS_HPP + +///////////////////////////////////////////////////// +// Slave pointers + +#define B100_REG_SLAVE(n) ((n)<<7) + +///////////////////////////////////////////////////// +// 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) + +//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) + +//////////////////////////////////////////////// +// Slave 3 -- I2C Core + +#define B100_REG_I2C_BASE B100_REG_SLAVE(3) + +//////////////////////////////////////////////// +// 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 +// 64 total regs in address space +#define B100_SR_RX_CTRL0 0       // 9 regs (+0 to +8) +#define B100_SR_RX_DSP0 10       // 4 regs (+0 to +3) +#define B100_SR_RX_CTRL1 16      // 9 regs (+0 to +8) +#define B100_SR_RX_DSP1 26       // 4 regs (+0 to +3) +#define B100_SR_TX_CTRL 32       // 4 regs (+0 to +3) +#define B100_SR_TX_DSP 38        // 3 regs (+0 to +2) + +#define B100_SR_TIME64 42        // 6 regs (+0 to +5) +#define B100_SR_RX_FRONT 48      // 5 regs (+0 to +4) +#define B100_SR_TX_FRONT 54      // 5 regs (+0 to +4) + +#define B100_SR_REG_TEST32 60    // 1 reg +#define B100_SR_CLEAR_RX_FIFO 61 // 1 reg +#define B100_SR_CLEAR_TX_FIFO 62 // 1 reg +#define B100_SR_GLOBAL_RESET 63  // 1 reg + +#define B100_REG_SR_ADDR(n) (B100_REG_SLAVE(8) + (4*(n))) + +#define B100_REG_SR_MISC_TEST32        B100_REG_SR_ADDR(B100_SR_REG_TEST32) + +///////////////////////////////////////////////// +// Magic reset regs +//////////////////////////////////////////////// +#define B100_REG_CLEAR_RX           B100_REG_SR_ADDR(B100_SR_CLEAR_RX_FIFO) +#define B100_REG_CLEAR_TX           B100_REG_SR_ADDR(B100_SR_CLEAR_RX_FIFO) +#define B100_REG_GLOBAL_RESET       B100_REG_SR_ADDR(B100_SR_GLOBAL_RESET) + +#endif + diff --git a/host/lib/usrp/b100/clock_ctrl.cpp b/host/lib/usrp/b100/clock_ctrl.cpp new file mode 100644 index 000000000..c93ff64c7 --- /dev/null +++ b/host/lib/usrp/b100/clock_ctrl.cpp @@ -0,0 +1,530 @@ +// +// 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 "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(i2c_iface::sptr iface, double master_clock_rate){ +        _iface = iface; +        _chan_rate = 0.0; +        _out_rate = 0.0; + +        //perform soft-reset +        _ad9522_regs.soft_reset = 1; +        this->send_reg(0x000); +        this->latch_regs(); +        _ad9522_regs.soft_reset = 0; + +        //init the clock gen registers +        _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); +    } + +    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: +    i2c_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->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->write_i2c(0x5C, buf); + +        buf = _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(i2c_iface::sptr iface, double master_clock_rate){ +    return sptr(new b100_clock_ctrl_impl(iface, master_clock_rate)); +} diff --git a/host/lib/usrp/b100/clock_ctrl.hpp b/host/lib/usrp/b100/clock_ctrl.hpp new file mode 100644 index 000000000..5ef231281 --- /dev/null +++ b/host/lib/usrp/b100/clock_ctrl.hpp @@ -0,0 +1,118 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CLOCK_CTRL_HPP +#define INCLUDED_B100_CLOCK_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp-e clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class b100_clock_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<b100_clock_ctrl> sptr; + +    /*! +     * Make a new clock control object. +     * \param iface the controller iface object +     * \param master_clock_rate the master FPGA/sample clock rate +     * \return the clock control object +     */ +    static sptr make(uhd::i2c_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/b100/codec_ctrl.cpp b/host/lib/usrp/b100/codec_ctrl.cpp new file mode 100644 index 000000000..4f9036039 --- /dev/null +++ b/host/lib/usrp/b100/codec_ctrl.cpp @@ -0,0 +1,283 @@ +// +// 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 "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(spi_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: +    spi_iface::sptr _iface; +    ad9862_regs_t _ad9862_regs; +    void send_reg(boost::uint8_t addr); +    void recv_reg(boost::uint8_t addr); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +b100_codec_ctrl_impl::b100_codec_ctrl_impl(spi_iface::sptr iface){ +    _iface = iface; + +    //soft reset +    _ad9862_regs.soft_reset = 1; +    this->send_reg(0); + +    //initialize the codec register settings +    _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; +    _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; +    _ad9862_regs.soft_reset = 0; + +    //setup rx side of codec +    _ad9862_regs.byp_buffer_a = 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(spi_iface::sptr iface){ +    return sptr(new b100_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/b100/codec_ctrl.hpp b/host/lib/usrp/b100/codec_ctrl.hpp new file mode 100644 index 000000000..1f7bdef09 --- /dev/null +++ b/host/lib/usrp/b100/codec_ctrl.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CODEC_CTRL_HPP +#define INCLUDED_B100_CODEC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp-e codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class 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(uhd::spi_iface::sptr iface); + +    //! aux adc identifier constants +    enum aux_adc_t{ +        AUX_ADC_A2 = 0xA2, +        AUX_ADC_A1 = 0xA1, +        AUX_ADC_B2 = 0xB2, +        AUX_ADC_B1 = 0xB1 +    }; + +    /*! +     * Read an auxiliary adc: +     * The internals remember which aux adc was read last. +     * Therefore, the aux adc switch is only changed as needed. +     * \param which which of the 4 adcs +     * \return a value in volts +     */ +    virtual double read_aux_adc(aux_adc_t which) = 0; + +    //! aux dac identifier constants +    enum aux_dac_t{ +        AUX_DAC_A = 0xA, +        AUX_DAC_B = 0xB, +        AUX_DAC_C = 0xC, +        AUX_DAC_D = 0xD //really the sigma delta output +    }; + +    /*! +     * Write an auxiliary dac. +     * \param which which of the 4 dacs +     * \param volts the level in in volts +     */ +    virtual void write_aux_dac(aux_dac_t which, double volts) = 0; + +    //! Set the TX PGA gain +    virtual void set_tx_pga_gain(double gain) = 0; + +    //! Get the TX PGA gain +    virtual double get_tx_pga_gain(void) = 0; + +    //! Set the RX PGA gain ('A' or 'B') +    virtual void set_rx_pga_gain(double gain, char which) = 0; + +    //! Get the RX PGA gain ('A' or 'B') +    virtual double get_rx_pga_gain(char which) = 0; +}; + +#endif /* INCLUDED_B100_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/b100/ctrl_packet.hpp b/host/lib/usrp/b100/ctrl_packet.hpp new file mode 100644 index 000000000..bab1f0de1 --- /dev/null +++ b/host/lib/usrp/b100/ctrl_packet.hpp @@ -0,0 +1,75 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_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/b100/dboard_iface.cpp b/host/lib/usrp/b100/dboard_iface.cpp new file mode 100644 index 000000000..33c4b355d --- /dev/null +++ b/host/lib/usrp/b100/dboard_iface.cpp @@ -0,0 +1,307 @@ +// +// 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 "wb_iface.hpp" +#include <uhd/types/serial.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( +        wb_iface::sptr wb_iface, +        i2c_iface::sptr i2c_iface, +        spi_iface::sptr spi_iface, +        b100_clock_ctrl::sptr clock, +        b100_codec_ctrl::sptr codec +    ){ +        _wb_iface = wb_iface; +        _i2c_iface = i2c_iface; +        _spi_iface = spi_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()); + +        _wb_iface->poke16(B100_REG_GPIO_RX_DBG, 0); +        _wb_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: +    wb_iface::sptr _wb_iface; +    i2c_iface::sptr _i2c_iface; +    spi_iface::sptr _spi_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( +    wb_iface::sptr wb_iface, +    i2c_iface::sptr i2c_iface, +    spi_iface::sptr spi_iface, +    b100_clock_ctrl::sptr clock, +    b100_codec_ctrl::sptr codec +){ +    return dboard_iface::sptr(new b100_dboard_iface(wb_iface, i2c_iface, spi_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: _wb_iface->poke16(B100_REG_GPIO_RX_SEL, value); return; +    case UNIT_TX: _wb_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: _wb_iface->poke16(B100_REG_GPIO_RX_DDR, value); return; +    case UNIT_TX: _wb_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: _wb_iface->poke16(B100_REG_GPIO_RX_IO, value); return; +    case UNIT_TX: _wb_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 _wb_iface->peek16(B100_REG_GPIO_RX_IO); +    case UNIT_TX: return _wb_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) +        ) +    ; +    _wb_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: +        _wb_iface->poke16(B100_REG_GPIO_RX_DBG, 0xffff); +        _wb_iface->poke16(B100_REG_GPIO_RX_SEL, dbg_sels); +        return; + +    case UNIT_TX: +        _wb_iface->poke16(B100_REG_GPIO_TX_DBG, 0xffff); +        _wb_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 +){ +    _spi_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 _spi_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 _i2c_iface->write_i2c(addr, bytes); +} + +byte_vector_t b100_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ +    return _i2c_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/b100/io_impl.cpp b/host/lib/usrp/b100/io_impl.cpp new file mode 100644 index 000000000..34535217a --- /dev/null +++ b/host/lib/usrp/b100/io_impl.cpp @@ -0,0 +1,249 @@ +// +// 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 "recv_packet_demuxer.hpp" +#include "validate_subdev_spec.hpp" +#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/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/*********************************************************************** + * IO Implementation Details + **********************************************************************/ +struct b100_impl::io_impl{ +    io_impl(void): +        async_msg_fifo(100/*messages deep*/) +    { /* NOP */ } + +    zero_copy_if::sptr data_transport; +    bounded_buffer<async_metadata_t> async_msg_fifo; +    recv_packet_demuxer::sptr demuxer; +    sph::recv_packet_handler recv_handler; +    sph::send_packet_handler send_handler; +}; + +/*********************************************************************** + * Initialize internals within this file + **********************************************************************/ +void b100_impl::io_init(void){ + +    //setup rx otw type +    _rx_otw_type.width = 16; +    _rx_otw_type.shift = 0; +    _rx_otw_type.byteorder = uhd::otw_type_t::BO_LITTLE_ENDIAN; + +    //setup tx otw type +    _tx_otw_type.width = 16; +    _tx_otw_type.shift = 0; +    _tx_otw_type.byteorder = uhd::otw_type_t::BO_LITTLE_ENDIAN; + +    //TODO best place to put this? +    this->reset_gpif(6); + +    //set the expected packet size in USB frames +    _fpga_ctrl->poke32(B100_REG_MISC_RX_LEN, 4); + +    //create new io impl +    _io_impl = UHD_PIMPL_MAKE(io_impl, ()); +    _io_impl->demuxer = recv_packet_demuxer::make(_data_transport, _rx_dsps.size(), B100_RX_SID_BASE); + +    //now its safe to register the async callback +    _fpga_ctrl->set_async_cb(boost::bind(&b100_impl::handle_async_message, this, _1)); + +    //init some handler stuff +    _io_impl->recv_handler.set_vrt_unpacker(&vrt::if_hdr_unpack_le); +    _io_impl->recv_handler.set_converter(_rx_otw_type); +    _io_impl->send_handler.set_vrt_packer(&vrt::if_hdr_pack_le); +    _io_impl->send_handler.set_converter(_tx_otw_type); +    _io_impl->send_handler.set_max_samples_per_packet(get_max_send_samps_per_packet()); +} + +void b100_impl::handle_async_message(managed_recv_buffer::sptr rbuf){ +    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 *>(); +    try{ +        vrt::if_hdr_unpack_le(vrt_hdr, if_packet_info); +    } +    catch(const std::exception &e){ +        UHD_MSG(error) << "Error (handle_async_message): " << e.what() << std::endl; +    } + +    if (if_packet_info.sid == B100_TX_ASYNC_SID and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ +        //fill in the async metadata +        async_metadata_t metadata; +        metadata.channel = 0; +        metadata.has_time_spec = if_packet_info.has_tsi and if_packet_info.has_tsf; +        metadata.time_spec = time_spec_t( +            time_t(if_packet_info.tsi), size_t(if_packet_info.tsf), _clock_ctrl->get_fpga_clock_rate() +        ); +        metadata.event_code = async_metadata_t::event_code_t(sph::get_context_code(vrt_hdr, if_packet_info)); +        _io_impl->async_msg_fifo.push_with_pop_on_full(metadata); +        if (metadata.event_code & +            ( async_metadata_t::EVENT_CODE_UNDERFLOW +            | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET) +        ) UHD_MSG(fastpath) << "U"; +        else if (metadata.event_code & +            ( async_metadata_t::EVENT_CODE_SEQ_ERROR +            | async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST) +        ) UHD_MSG(fastpath) << "S"; +    } +    else UHD_MSG(error) << "Unknown async packet" << std::endl; +} + +void b100_impl::update_tick_rate(const double rate){ +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.set_tick_rate(rate); +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.set_tick_rate(rate); +} + +void b100_impl::update_rx_samp_rate(const double rate){ +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.set_samp_rate(rate); +    const double adj = _rx_dsps.front()->get_scaling_adjustment(); +    _io_impl->recv_handler.set_scale_factor(adj/32767.); +} + +void b100_impl::update_tx_samp_rate(const double rate){ +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.set_samp_rate(rate); +} + +void b100_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    fs_path root = "/mboards/0/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "rx"); + +    //setup mux for this spec +    bool fe_swapped = false; +    for (size_t i = 0; i < spec.size(); i++){ +        const std::string conn = _tree->access<std::string>(root / spec[i].db_name / "rx_frontends" / spec[i].sd_name / "connection").get(); +        if (i == 0 and (conn == "QI" or conn == "Q")) fe_swapped = true; +        _rx_dsps[i]->set_mux(conn, fe_swapped); +    } +    _rx_fe->set_mux(fe_swapped); + +    //resize for the new occupancy +    _io_impl->recv_handler.resize(spec.size()); + +    //bind new callbacks for the handler +    for (size_t i = 0; i < _io_impl->recv_handler.size(); i++){ +        _rx_dsps[i]->set_nsamps_per_packet(get_max_recv_samps_per_packet()); //seems to be a good place to set this +        _io_impl->recv_handler.set_xport_chan_get_buff(i, boost::bind( +            &recv_packet_demuxer::get_recv_buff, _io_impl->demuxer, i, _1 +        )); +        _io_impl->recv_handler.set_overflow_handler(i, boost::bind(&rx_dsp_core_200::handle_overflow, _rx_dsps[i])); +    } +} + +void b100_impl::update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    fs_path root = "/mboards/0/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "tx"); + +    //set the mux for this spec +    const std::string conn = _tree->access<std::string>(root / spec[0].db_name / "tx_frontends" / spec[0].sd_name / "connection").get(); +    _tx_fe->set_mux(conn); + +    //resize for the new occupancy +    _io_impl->send_handler.resize(spec.size()); + +    //bind new callbacks for the handler +    for (size_t i = 0; i < _io_impl->send_handler.size(); i++){ +        _io_impl->send_handler.set_xport_chan_get_buff(i, boost::bind( +            &zero_copy_if::get_send_buff, _data_transport, _1 +        )); +    } +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool b100_impl::recv_async_msg( +    async_metadata_t &async_metadata, double timeout +){ +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return _io_impl->async_msg_fifo.pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Send Data + **********************************************************************/ +size_t 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 / _tx_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 +    ); +} + +/*********************************************************************** + * Receive Data + **********************************************************************/ +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/_rx_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 +    ); +} diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt new file mode 100644 index 000000000..ec8b60fec --- /dev/null +++ b/host/lib/usrp/common/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +IF(ENABLE_USB) +    INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/../firmware/fx2/common) + +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/fx2_ctrl.cpp +    ) +ENDIF(ENABLE_USB) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp +) diff --git a/host/lib/usrp/common/fx2_ctrl.cpp b/host/lib/usrp/common/fx2_ctrl.cpp new file mode 100644 index 000000000..650fba784 --- /dev/null +++ b/host/lib/usrp/common/fx2_ctrl.cpp @@ -0,0 +1,460 @@ +// +// 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 "fx2_ctrl.hpp" +#include "usrp_commands.h" +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/usb_control.hpp> +#include <boost/functional/hash.hpp> +#include <boost/thread/thread.hpp> +#include <fstream> +#include <sstream> +#include <string> +#include <vector> +#include <cstring> + +using namespace uhd; +using namespace uhd::usrp; + +#define FX2_FIRMWARE_LOAD 0xa0 + +static const bool load_img_msg = true; + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +/*! + * Create a file hash + * The hash will be used to identify the loaded firmware and fpga image + * \param filename file used to generate hash value + * \return hash value in a size_t type + */ +static size_t generate_hash(const char *filename) +{ +    std::ifstream file(filename); +    if (not file){ +        throw uhd::io_error(std::string("cannot open input file ") + filename); +    } + +    size_t hash = 0; + +    char ch; +    while (file.get(ch)) { +        boost::hash_combine(hash, ch); +    } + +    if (not file.eof()){ +        throw uhd::io_error(std::string("file error ") + filename); +    } + +    file.close(); +    return hash; +} + + +/*! + * Verify checksum of a Intel HEX record + * \param record a line from an Intel HEX file + * \return true if record is valid, false otherwise + */ +static bool checksum(std::string *record) +{ + +    size_t len = record->length(); +    unsigned int i; +    unsigned char sum = 0; +    unsigned int val; + +    for (i = 1; i < len; i += 2) { +        std::istringstream(record->substr(i, 2)) >> std::hex >> val; +        sum += val; +    } + +    if (sum == 0) +       return true; +    else +       return false; +} + + +/*! + * Parse Intel HEX record + * + * \param record a line from an Intel HEX file + * \param len output length of record + * \param addr output address + * \param type output type + * \param data output data + * \return true if record is sucessfully read, false on error + */ +bool parse_record(std::string *record, unsigned int &len, +                  unsigned int &addr, unsigned int &type, +                  unsigned char* data) +{ +    unsigned int i; +    std::string _data; +    unsigned int val; + +    if (record->substr(0, 1) != ":") +        return false; + +    std::istringstream(record->substr(1, 2)) >> std::hex >> len; +    std::istringstream(record->substr(3, 4)) >> std::hex >> addr; +    std::istringstream(record->substr(7, 2)) >> std::hex >> type; + +    for (i = 0; i < len; i++) { +        std::istringstream(record->substr(9 + 2 * i, 2)) >> std::hex >> val; +        data[i] = (unsigned char) val; +    } + +    return true; +} + + +/*! + * USRP control implementation for device discovery and configuration + */ +class fx2_ctrl_impl : public fx2_ctrl { +public: +    fx2_ctrl_impl(uhd::transport::usb_control::sptr ctrl_transport) +    { +        _ctrl_transport = ctrl_transport; +    } + +    void usrp_load_firmware(std::string filestring, bool force) +    { +        const char *filename = filestring.c_str(); + +        size_t hash = generate_hash(filename); + +        size_t loaded_hash; usrp_get_firmware_hash(loaded_hash); + +        if (not force and (hash == loaded_hash)) return; + +        //FIXME: verify types +        unsigned int len; +        unsigned int addr; +        unsigned int type; +        unsigned char data[512]; + +        std::ifstream file; +        file.open(filename, std::ifstream::in); + +        if (!file.good()) { +            throw uhd::io_error("usrp_load_firmware: cannot open firmware input file"); +        } + +        unsigned char reset_y = 1; +        unsigned char reset_n = 0; + +        //hit the reset line +        if (load_img_msg) UHD_MSG(status) << "Loading firmware image: " << filestring << "..." << std::flush; +        usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, &reset_y, 1); + +        while (!file.eof()) { +           std::string record; +           file >> record; + +            //check for valid record +            if (not checksum(&record) or not parse_record(&record, len, addr, type, data)) { +                throw uhd::io_error("usrp_load_firmware: bad record checksum"); +            } + +            //type 0x00 is data +            if (type == 0x00) { +                int ret = usrp_control_write(FX2_FIRMWARE_LOAD, addr, 0, data, len); +                if (ret < 0) throw uhd::io_error("usrp_load_firmware: usrp_control_write failed"); +            } +            //type 0x01 is end +            else if (type == 0x01) { +                usrp_set_firmware_hash(hash); //set hash before reset +                usrp_control_write(FX2_FIRMWARE_LOAD, 0xe600, 0, &reset_n, 1); +                file.close(); + +                //wait for things to settle +                boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); +                if (load_img_msg) UHD_MSG(status) << " done" << std::endl; +                return; +            } +            //type anything else is unhandled +            else { +                throw uhd::io_error("usrp_load_firmware: unsupported record"); +            } +        } + +        //file did not end +        throw uhd::io_error("usrp_load_firmware: bad record"); +    } + +    void usrp_init(void){ +        //disable +        usrp_rx_enable(false); +        usrp_tx_enable(false); + +        //toggle resets +        usrp_rx_reset(true); +        usrp_tx_reset(true); +        usrp_rx_reset(false); +        usrp_tx_reset(false); +    } + +    void usrp_load_fpga(std::string filestring) +    { +        const char *filename = filestring.c_str(); + +        size_t hash = generate_hash(filename); + +        size_t loaded_hash; usrp_get_fpga_hash(loaded_hash); + +        if (hash == loaded_hash) return; +        const int ep0_size = 64; +        unsigned char buf[ep0_size]; + +        if (load_img_msg) UHD_MSG(status) << "Loading FPGA image: " << filestring << "..." << std::flush; +        std::ifstream file; +        file.open(filename, std::ios::in | std::ios::binary); +        if (not file.good()) { +            throw uhd::io_error("usrp_load_fpga: cannot open fpga input file"); +        } + +        usrp_fpga_reset(true); //holding the fpga in reset while loading + +        if (usrp_control_write_cmd(VRQ_FPGA_LOAD, 0, FL_BEGIN) < 0) { +            throw uhd::io_error("usrp_load_fpga: fpga load error"); +        } + +        while (not file.eof()) { +            file.read((char *)buf, sizeof(buf)); +            const std::streamsize n = file.gcount(); +            if(n == 0) continue; +            int ret = usrp_control_write(VRQ_FPGA_LOAD, 0, FL_XFER, buf, boost::uint16_t(n)); +            if (ret < 0 or std::streamsize(ret) != n) { +                throw uhd::io_error("usrp_load_fpga: fpga load error"); +            } +        } + +        if (usrp_control_write_cmd(VRQ_FPGA_LOAD, 0, FL_END) < 0) { +            throw uhd::io_error("usrp_load_fpga: fpga load error"); +        } + +        usrp_set_fpga_hash(hash); + +        usrp_fpga_reset(false); //done loading, take fpga out of reset + +        file.close(); +        if (load_img_msg) UHD_MSG(status) << " done" << std::endl; +    } + +    void usrp_load_eeprom(std::string filestring) +    { +        if (load_img_msg) UHD_MSG(status) << "Loading EEPROM image: " << filestring << "..." << std::flush; +        const char *filename = filestring.c_str(); +        const boost::uint16_t i2c_addr = 0x50; + +        unsigned int addr; +        unsigned char data[256]; +        unsigned char sendbuf[17]; + +        std::ifstream file; +        file.open(filename, std::ifstream::in); + +        if (not file.good()) { +            throw uhd::io_error("usrp_load_eeprom: cannot open EEPROM input file"); +        } + +        file.read((char *)data, 256); +        std::streamsize len = file.gcount(); + +        if(len == 256) { +            throw uhd::io_error("usrp_load_eeprom: image size too large"); +        } + +        const int pagesize = 16; +        addr = 0; +        while(len > 0) { +            sendbuf[0] = addr; +            memcpy(sendbuf+1, &data[addr], len > pagesize ? pagesize : size_t(len)); +            int ret = usrp_i2c_write(i2c_addr, sendbuf, (len > pagesize ? pagesize : size_t(len))+1); +            if (ret < 0) { +                throw uhd::io_error("usrp_load_eeprom: usrp_i2c_write failed"); +            } +            addr += pagesize; +            len -= pagesize; +            boost::this_thread::sleep(boost::posix_time::milliseconds(100)); +        } +        file.close(); +        if (load_img_msg) UHD_MSG(status) << " done" << std::endl; +    } + + +    void usrp_set_led(int led_num, bool on) +    { +        UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_SET_LED, on, led_num) >= 0); +    } + + +    void usrp_get_firmware_hash(size_t &hash) +    { +        UHD_ASSERT_THROW(usrp_control_read(0xa0, USRP_HASH_SLOT_0_ADDR, 0, +                                 (unsigned char*) &hash, sizeof(size_t)) >= 0); +    } + + +    void usrp_set_firmware_hash(size_t hash) +    { +        UHD_ASSERT_THROW(usrp_control_write(0xa0, USRP_HASH_SLOT_0_ADDR, 0, +                                  (unsigned char*) &hash, sizeof(size_t)) >= 0); + +    } + + +    void usrp_get_fpga_hash(size_t &hash) +    { +        UHD_ASSERT_THROW(usrp_control_read(0xa0, USRP_HASH_SLOT_1_ADDR, 0, +                                 (unsigned char*) &hash, sizeof(size_t)) >= 0); +    } + + +    void usrp_set_fpga_hash(size_t hash) +    { +        UHD_ASSERT_THROW(usrp_control_write(0xa0, USRP_HASH_SLOT_1_ADDR, 0, +                                  (unsigned char*) &hash, sizeof(size_t)) >= 0); +    } + +    void usrp_tx_enable(bool on) +    { +        UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_TX_ENABLE, on, 0) >= 0); +    } + + +    void usrp_rx_enable(bool on) +    { +        UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_RX_ENABLE, on, 0) >= 0); +    } + + +    void usrp_tx_reset(bool on) +    { +        UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_TX_RESET, on, 0) >= 0); +    } + + +    void usrp_rx_reset(bool on) +    { +        UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_RX_RESET, on, 0) >= 0); +    } + +    void usrp_fpga_reset(bool on) +    { +        UHD_ASSERT_THROW(usrp_control_write_cmd(VRQ_FPGA_SET_RESET, on, 0) >= 0); +    } + +    int usrp_control_write(boost::uint8_t request, +                           boost::uint16_t value, +                           boost::uint16_t index, +                           unsigned char *buff, +                           boost::uint16_t length) +    { +        return _ctrl_transport->submit(VRT_VENDOR_OUT,     // bmReqeustType +                                       request,            // bRequest +                                       value,              // wValue +                                       index,              // wIndex +                                       buff,               // data +                                       length);            // wLength +    } + + +    int usrp_control_read(boost::uint8_t request, +                          boost::uint16_t value, +                          boost::uint16_t index, +                          unsigned char *buff, +                          boost::uint16_t length) +    { +        return _ctrl_transport->submit(VRT_VENDOR_IN,      // bmReqeustType +                                       request,            // bRequest +                                       value,              // wValue +                                       index,              // wIndex +                                       buff,               // data +                                       length);            // wLength +    } + + +    int usrp_control_write_cmd(boost::uint8_t request, boost::uint16_t value, boost::uint16_t index) +    { +        return usrp_control_write(request, value, index, 0, 0); +    } + +    int usrp_i2c_write(boost::uint16_t i2c_addr, unsigned char *buf, boost::uint16_t len) +    { +        return usrp_control_write(VRQ_I2C_WRITE, i2c_addr, 0, buf, len); +    } + +    int usrp_i2c_read(boost::uint16_t i2c_addr, unsigned char *buf, boost::uint16_t len) +    { +        return usrp_control_read(VRQ_I2C_READ, i2c_addr, 0, buf, len); +    } + +    static const bool iface_debug = false; +    static const size_t max_i2c_data_bytes = 64; + +    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 = this->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 = this->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: +    uhd::transport::usb_control::sptr _ctrl_transport; +}; + +/*********************************************************************** + * Public make function for fx2_ctrl interface + **********************************************************************/ +fx2_ctrl::sptr fx2_ctrl::make(uhd::transport::usb_control::sptr ctrl_transport){ +    return sptr(new fx2_ctrl_impl(ctrl_transport)); +} + diff --git a/host/lib/usrp/common/fx2_ctrl.hpp b/host/lib/usrp/common/fx2_ctrl.hpp new file mode 100644 index 000000000..691d64275 --- /dev/null +++ b/host/lib/usrp/common/fx2_ctrl.hpp @@ -0,0 +1,123 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_FX2_CTRL_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_FX2_CTRL_HPP + +#include <uhd/transport/usb_control.hpp> +#include <uhd/types/serial.hpp> //i2c iface +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +namespace uhd{ namespace usrp{ + +class fx2_ctrl : boost::noncopyable, public uhd::i2c_iface{ +public: +    typedef boost::shared_ptr<fx2_ctrl> sptr; + +    /*! +     * Make a usrp control object from a control transport +     * \param ctrl_transport a USB control transport +     * \return a new usrp control object +     */ +    static sptr make(uhd::transport::usb_control::sptr ctrl_transport); + +    //! Call init after the fpga is loaded +    virtual void usrp_init(void) = 0; + +    /*! +     * Load firmware in Intel HEX Format onto device  +     * \param filename name of firmware file +     * \param force reload firmware if already loaded +     */ +    virtual void usrp_load_firmware(std::string filename, +                                   bool force = false) = 0; + +    /*! +     * Load fpga file onto usrp  +     * \param filename name of fpga image  +     */ +    virtual void usrp_load_fpga(std::string filename) = 0; + +    /*! +     * Load USB descriptor file in Intel HEX format into EEPROM +     * \param filename name of EEPROM image +     */ +    virtual void usrp_load_eeprom(std::string filestring) = 0; +     +    /*! +     * Submit an IN transfer  +     * \param request device specific request  +     * \param value device specific field +     * \param index device specific field +     * \param buff buffer to place data +     * \return number of bytes read or error  +     */ +    virtual int usrp_control_read(boost::uint8_t request, +                                  boost::uint16_t value, +                                  boost::uint16_t index, +                                  unsigned char *buff, +                                  boost::uint16_t length) = 0; + +    /*! +     * Submit an OUT transfer  +     * \param request device specific request  +     * \param value device specific field +     * \param index device specific field +     * \param buff buffer of data to be sent  +     * \return number of bytes written or error  +     */ +    virtual int usrp_control_write(boost::uint8_t request, +                                   boost::uint16_t value, +                                   boost::uint16_t index, +                                   unsigned char *buff, +                                   boost::uint16_t length) = 0; + +    /*! +     * Perform an I2C write +     * \param i2c_addr I2C device address +     * \param buf data to be written  +     * \param len length of data in bytes +     * \return number of bytes written or error  +     */ + +    virtual int usrp_i2c_write(boost::uint16_t i2c_addr, +                               unsigned char *buf,  +                               boost::uint16_t len) = 0; + +    /*! +     * Perform an I2C read +     * \param i2c_addr I2C device address +     * \param buf data to be read  +     * \param len length of data in bytes +     * \return number of bytes read or error  +     */ + +    virtual int usrp_i2c_read(boost::uint16_t i2c_addr, +                               unsigned char *buf,  +                               boost::uint16_t len) = 0; + +    //! enable/disable the rx path +    virtual void usrp_rx_enable(bool on) = 0; + +    //! enable/disable the tx path +    virtual void usrp_tx_enable(bool on) = 0; +}; + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_FX2_CTRL_HPP */ diff --git a/host/lib/usrp/common/recv_packet_demuxer.cpp b/host/lib/usrp/common/recv_packet_demuxer.cpp new file mode 100644 index 000000000..f2cfe3bb0 --- /dev/null +++ b/host/lib/usrp/common/recv_packet_demuxer.cpp @@ -0,0 +1,87 @@ +// +// 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 "recv_packet_demuxer.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/thread/mutex.hpp> +#include <queue> +#include <deque> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static UHD_INLINE boost::uint32_t extract_sid(managed_recv_buffer::sptr &buff){ +    //ASSUME that the data is in little endian format +    return uhd::wtohx(buff->cast<const boost::uint32_t *>()[1]); +} + +class recv_packet_demuxer_impl : public uhd::usrp::recv_packet_demuxer{ +public: +    recv_packet_demuxer_impl( +        transport::zero_copy_if::sptr transport, +        const size_t size, +        const boost::uint32_t sid_base +    ): +        _transport(transport), _sid_base(sid_base), _queues(size) +    { +        /* NOP */ +    } + +    managed_recv_buffer::sptr get_recv_buff(const size_t index, const double timeout){ +        boost::mutex::scoped_lock lock(_mutex); +        managed_recv_buffer::sptr buff; + +        //there is already an entry in the queue, so pop that +        if (not _queues[index].wrapper.empty()){ +            std::swap(buff, _queues[index].wrapper.front()); +            _queues[index].wrapper.pop(); +            return buff; +        } + +        while (true){ +            //otherwise call into the transport +            buff = _transport->get_recv_buff(timeout); +            if (buff.get() == NULL) return buff; //timeout + +            //check the stream id to know which channel +            const size_t rx_index = extract_sid(buff) - _sid_base; +            if (rx_index == index) return buff; //got expected message + +            //otherwise queue and try again +            if (rx_index < _queues.size()) _queues[rx_index].wrapper.push(buff); +            else UHD_MSG(error) << "Got a data packet with unknown SID " << extract_sid(buff) << std::endl; +        } +    } + +private: +    transport::zero_copy_if::sptr _transport; +    const boost::uint32_t _sid_base; +    boost::mutex _mutex; +    struct channel_guts_type{ +        channel_guts_type(void): wrapper(container){} +        std::deque<managed_recv_buffer::sptr> container; +        std::queue<managed_recv_buffer::sptr> wrapper; +    }; +    std::vector<channel_guts_type> _queues; +}; + +recv_packet_demuxer::sptr recv_packet_demuxer::make(transport::zero_copy_if::sptr transport, const size_t size, const boost::uint32_t sid_base){ +    return sptr(new recv_packet_demuxer_impl(transport, size, sid_base)); +} diff --git a/host/lib/usrp/common/recv_packet_demuxer.hpp b/host/lib/usrp/common/recv_packet_demuxer.hpp new file mode 100644 index 000000000..fde756d27 --- /dev/null +++ b/host/lib/usrp/common/recv_packet_demuxer.hpp @@ -0,0 +1,41 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_RECV_PACKET_DEMUXER_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_RECV_PACKET_DEMUXER_HPP + +#include <uhd/config.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/cstdint.hpp> + +namespace uhd{ namespace usrp{ + +    class recv_packet_demuxer{ +    public: +        typedef boost::shared_ptr<recv_packet_demuxer> sptr; + +        //! Make a new demuxer from a transport and parameters +        static sptr make(transport::zero_copy_if::sptr transport, const size_t size, const boost::uint32_t sid_base); + +        //! Get a buffer at the given index from the transport +        virtual transport::managed_recv_buffer::sptr get_recv_buff(const size_t index, const double timeout) = 0; +    }; + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_RECV_PACKET_DEMUXER_HPP */ diff --git a/host/lib/usrp/common/validate_subdev_spec.cpp b/host/lib/usrp/common/validate_subdev_spec.cpp new file mode 100644 index 000000000..fab40b204 --- /dev/null +++ b/host/lib/usrp/common/validate_subdev_spec.cpp @@ -0,0 +1,73 @@ +// +// 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 "validate_subdev_spec.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +namespace uhd{ namespace usrp{ + +    static std::ostream& operator<< (std::ostream &out, const subdev_spec_pair_t &pair){ +        out << pair.db_name << ":" << pair.sd_name; +        return out; +    } + +}} + +void uhd::usrp::validate_subdev_spec( +    property_tree::sptr tree, +    const subdev_spec_t &spec, +    const std::string &type, +    const std::string &mb +){ +    const size_t num_dsps = tree->list(str(boost::format("/mboards/%s/%s_dsps") % mb % type)).size(); + +    //sanity checking on the length +    if (spec.size() == 0) throw uhd::value_error(str(boost::format( +        "Empty %s subdevice specification is not supported.\n" +    ) % type)); +    if (spec.size() > num_dsps) throw uhd::value_error(str(boost::format( +        "The subdevice specification \"%s\" is too long.\n" +        "The user specified %u channels, but there are only %u %s dsps on mboard %s.\n" +    ) % spec.to_string() % spec.size() % num_dsps % type % mb)); + +    //make a list of all possible specs +    subdev_spec_t all_specs; +    BOOST_FOREACH(const std::string &db, tree->list(str(boost::format("/mboards/%s/dboards") % mb))){ +        BOOST_FOREACH(const std::string &sd, tree->list(str(boost::format("/mboards/%s/dboards/%s/%s_frontends") % mb % db % type))){ +            all_specs.push_back(subdev_spec_pair_t(db, sd)); +        } +    } + +    //validate that the spec is possible +    BOOST_FOREACH(const subdev_spec_pair_t &pair, spec){ +        uhd::assert_has(all_specs, pair, str(boost::format("%s subdevice specification on mboard %s") % type % mb)); +    } + +    //enable selected frontends, disable others +    BOOST_FOREACH(const subdev_spec_pair_t &pair, all_specs){ +        const bool enb = uhd::has(spec, pair); +        tree->access<bool>(str(boost::format( +            "/mboards/%s/dboards/%s/%s_frontends/%s/enabled" +        ) % mb % pair.db_name % type % pair.sd_name)).set(enb); +    } +} diff --git a/host/lib/usrp/common/validate_subdev_spec.hpp b/host/lib/usrp/common/validate_subdev_spec.hpp new file mode 100644 index 000000000..7d9e2c309 --- /dev/null +++ b/host/lib/usrp/common/validate_subdev_spec.hpp @@ -0,0 +1,38 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_VALIDATE_SUBDEV_SPEC_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_VALIDATE_SUBDEV_SPEC_HPP + +#include <uhd/config.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/property_tree.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + +    //! Validate a subdev spec against a property tree +    void validate_subdev_spec( +        property_tree::sptr tree, +        const subdev_spec_t &spec, +        const std::string &type, //rx or tx +        const std::string &mb = "0" +    ); + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_VALIDATE_SUBDEV_SPEC_HPP */ diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt new file mode 100644 index 000000000..2aa8f6b99 --- /dev/null +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/gpio_core_200.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/i2c_core_100.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/spi_core_100.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/time64_core_200.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_200.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_200.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_200.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/tx_frontend_core_200.cpp +) diff --git a/host/lib/usrp/cores/gpio_core_200.cpp b/host/lib/usrp/cores/gpio_core_200.cpp new file mode 100644 index 000000000..8639b1851 --- /dev/null +++ b/host/lib/usrp/cores/gpio_core_200.cpp @@ -0,0 +1,100 @@ +// +// 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 "gpio_core_200.hpp" +#include <uhd/types/dict.hpp> + +#define REG_GPIO_IDLE          _base + 0 +#define REG_GPIO_RX_ONLY       _base + 4 +#define REG_GPIO_TX_ONLY       _base + 8 +#define REG_GPIO_BOTH          _base + 12 +#define REG_GPIO_DDR           _base + 16 +#define REG_GPIO_READ          _base + 0 //any address will readback + +using namespace uhd; +using namespace usrp; + +class gpio_core_200_impl : public gpio_core_200{ +public: +    gpio_core_200_impl(wb_iface::sptr iface, const size_t base): +        _iface(iface), _base(base) { /* NOP */ } + +    void set_pin_ctrl(const unit_t unit, const boost::uint16_t value){ +        _pin_ctrl[unit] = value; //shadow +        this->update(); //full update +    } + +    void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value){ +        _atr_regs[unit][atr] = value;  //shadow +        this->update(); //full update +    } + +    void set_gpio_ddr(const unit_t unit, const boost::uint16_t value){ +        _gpio_ddr[unit] = value; //shadow +        _iface->poke32(REG_GPIO_DDR, //update the 32 bit register +            (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | +            (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)) +        ); +    } + +    void set_gpio_out(const unit_t unit, const boost::uint16_t value){ +        _gpio_out[unit] = value; //shadow +        this->update(); //full update +    } + +    boost::uint16_t read_gpio(const unit_t unit){ +        return boost::uint16_t(_iface->peek32(REG_GPIO_READ) >> unit2shit(unit)); +    } + +private: +    wb_iface::sptr _iface; +    const size_t _base; + +    uhd::dict<unit_t, boost::uint16_t> _pin_ctrl, _gpio_out, _gpio_ddr; +    uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > _atr_regs; + +    unsigned unit2shit(const unit_t unit){ +        return (unit == dboard_iface::UNIT_RX)? 0 : 16; +    } + +    void update(void){ +        this->update(dboard_iface::ATR_REG_IDLE, REG_GPIO_IDLE); +        this->update(dboard_iface::ATR_REG_TX_ONLY, REG_GPIO_TX_ONLY); +        this->update(dboard_iface::ATR_REG_RX_ONLY, REG_GPIO_RX_ONLY); +        this->update(dboard_iface::ATR_REG_FULL_DUPLEX, REG_GPIO_BOTH); +    } + +    void update(const atr_reg_t atr, const size_t addr){ +        const boost::uint32_t atr_val = +            (boost::uint32_t(_atr_regs[dboard_iface::UNIT_RX][atr]) << unit2shit(dboard_iface::UNIT_RX)) | +            (boost::uint32_t(_atr_regs[dboard_iface::UNIT_TX][atr]) << unit2shit(dboard_iface::UNIT_TX)); + +        const boost::uint32_t gpio_val = +            (boost::uint32_t(_gpio_out[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | +            (boost::uint32_t(_gpio_out[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)); + +        const boost::uint32_t ctrl = +            (boost::uint32_t(_pin_ctrl[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | +            (boost::uint32_t(_pin_ctrl[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)); +        _iface->poke32(addr, (ctrl & atr_val) | ((~ctrl) & gpio_val)); +    } + +}; + +gpio_core_200::sptr gpio_core_200::make(wb_iface::sptr iface, const size_t base){ +    return sptr(new gpio_core_200_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/gpio_core_200.hpp b/host/lib/usrp/cores/gpio_core_200.hpp new file mode 100644 index 000000000..7ff2af649 --- /dev/null +++ b/host/lib/usrp/cores/gpio_core_200.hpp @@ -0,0 +1,52 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_GPIO_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_GPIO_CORE_200_HPP + +#include <uhd/config.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <boost/cstdint.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class gpio_core_200 : boost::noncopyable{ +public: +    typedef boost::shared_ptr<gpio_core_200> sptr; + +    typedef uhd::usrp::dboard_iface::unit_t unit_t; +    typedef uhd::usrp::dboard_iface::atr_reg_t atr_reg_t; + +    //! makes a new GPIO core from iface and slave base +    static sptr make(wb_iface::sptr iface, const size_t base); + +    //! 1 = ATR +    virtual void set_pin_ctrl(const unit_t unit, const boost::uint16_t value) = 0; + +    virtual void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value) = 0; + +    //! 1 = OUTPUT +    virtual void set_gpio_ddr(const unit_t unit, const boost::uint16_t value) = 0; + +    virtual void set_gpio_out(const unit_t unit, const boost::uint16_t value) = 0; + +    virtual boost::uint16_t read_gpio(const unit_t unit) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_GPIO_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/i2c_core_100.cpp b/host/lib/usrp/cores/i2c_core_100.cpp new file mode 100644 index 000000000..ceeb3f518 --- /dev/null +++ b/host/lib/usrp/cores/i2c_core_100.cpp @@ -0,0 +1,140 @@ +// +// 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 "i2c_core_100.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/thread/thread.hpp> //sleep + +#define REG_I2C_PRESCALER_LO _base + 0 +#define REG_I2C_PRESCALER_HI _base + 2 +#define REG_I2C_CTRL         _base + 4 +#define REG_I2C_DATA         _base + 6 +#define REG_I2C_CMD_STATUS   _base + 8 + +// +// 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 + +using namespace uhd; + +class i2c_core_100_impl : public i2c_core_100{ +public: +    i2c_core_100_impl(wb_iface::sptr iface, const size_t base): +        _iface(iface), _base(base) +    { +        //init I2C FPGA interface. +        _iface->poke16(REG_I2C_CTRL, 0x0000); +        //set prescalers to operate at 400kHz: WB_CLK is 64MHz... +        static const boost::uint32_t i2c_datarate = 400000; +        static const boost::uint32_t wishbone_clk = 64000000; //FIXME should go somewhere else +        boost::uint16_t prescaler = wishbone_clk / (i2c_datarate*5) - 1; +        _iface->poke16(REG_I2C_PRESCALER_LO, prescaler & 0xFF); +        _iface->poke16(REG_I2C_PRESCALER_HI, (prescaler >> 8) & 0xFF); +        _iface->poke16(REG_I2C_CTRL, I2C_CTRL_EN); //enable I2C core +    } + +    void write_i2c( +        boost::uint8_t addr, +        const byte_vector_t &bytes +    ){ +        _iface->poke16(REG_I2C_DATA, (addr << 1) | 0); //addr and read bit (0) +        _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | I2C_CMD_START | (bytes.size() == 0 ? I2C_CMD_STOP : 0)); + +        //wait for previous transfer to complete +        if (not wait_chk_ack()) { +            _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_STOP); +            return; +        } + +        for (size_t i = 0; i < bytes.size(); i++) { +            _iface->poke16(REG_I2C_DATA, bytes[i]); +            _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | ((i == (bytes.size() - 1)) ? I2C_CMD_STOP : 0)); +            if(!wait_chk_ack()) { +                _iface->poke16(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 (_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_BUSY){ +            /* NOP */ +        } + +        _iface->poke16(REG_I2C_DATA, (addr << 1) | 1); //addr and read bit (1) +        _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_WR | I2C_CMD_START); +        //wait for previous transfer to complete +        if (not wait_chk_ack()) { +            _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_STOP); +        } +        for (size_t i = 0; i < num_bytes; i++) { +            _iface->poke16(REG_I2C_CMD_STATUS, I2C_CMD_RD | ((num_bytes == i+1) ? (I2C_CMD_STOP | I2C_CMD_NACK) : 0)); +            i2c_wait(); +            bytes.push_back(boost::uint8_t(_iface->peek16(REG_I2C_DATA))); +        } +        return bytes; +    } + +private: +    void i2c_wait(void) { +        for (size_t i = 0; i < 100; i++){ +            if ((_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_TIP) == 0) return; +            boost::this_thread::sleep(boost::posix_time::milliseconds(1)); +        } +        UHD_MSG(error) << "i2c_core_100: i2c_wait timeout" << std::endl; +    } + +    bool wait_chk_ack(void){ +        i2c_wait(); +        return (_iface->peek16(REG_I2C_CMD_STATUS) & I2C_ST_RXACK) == 0; +    } + +    wb_iface::sptr _iface; +    const size_t _base; +}; + +i2c_core_100::sptr i2c_core_100::make(wb_iface::sptr iface, const size_t base){ +    return sptr(new i2c_core_100_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/i2c_core_100.hpp b/host/lib/usrp/cores/i2c_core_100.hpp new file mode 100644 index 000000000..f7a5ae4f7 --- /dev/null +++ b/host/lib/usrp/cores/i2c_core_100.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_I2C_CORE_100_HPP +#define INCLUDED_LIBUHD_USRP_I2C_CORE_100_HPP + +#include <uhd/config.hpp> +#include <uhd/types/serial.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class i2c_core_100 : boost::noncopyable, public uhd::i2c_iface{ +public: +    typedef boost::shared_ptr<i2c_core_100> sptr; + +    //! makes a new i2c core from iface and slave base +    static sptr make(wb_iface::sptr iface, const size_t base); +}; + +#endif /* INCLUDED_LIBUHD_USRP_I2C_CORE_100_HPP */ diff --git a/host/lib/usrp/cores/rx_dsp_core_200.cpp b/host/lib/usrp/cores/rx_dsp_core_200.cpp new file mode 100644 index 000000000..b377b16f0 --- /dev/null +++ b/host/lib/usrp/cores/rx_dsp_core_200.cpp @@ -0,0 +1,203 @@ +// +// 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 "rx_dsp_core_200.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> +#include <algorithm> +#include <cmath> + +#define REG_DSP_RX_FREQ       _dsp_base + 0 +//skip one right here +#define REG_DSP_RX_DECIM      _dsp_base + 8 +#define REG_DSP_RX_MUX        _dsp_base + 12 + +#define FLAG_DSP_RX_MUX_SWAP_IQ   (1 << 0) +#define FLAG_DSP_RX_MUX_REAL_MODE (1 << 1) + +#define REG_RX_CTRL_STREAM_CMD     _ctrl_base + 0 +#define REG_RX_CTRL_TIME_SECS      _ctrl_base + 4 +#define REG_RX_CTRL_TIME_TICKS     _ctrl_base + 8 +#define REG_RX_CTRL_CLEAR          _ctrl_base + 12 +#define REG_RX_CTRL_VRT_HDR        _ctrl_base + 16 +#define REG_RX_CTRL_VRT_SID        _ctrl_base + 20 +#define REG_RX_CTRL_VRT_TLR        _ctrl_base + 24 +#define REG_RX_CTRL_NSAMPS_PP      _ctrl_base + 28 +#define REG_RX_CTRL_NCHANNELS      _ctrl_base + 32 + +template <class T> T ceil_log2(T num){ +    return std::ceil(std::log(num)/std::log(T(2))); +} + +using namespace uhd; + +class rx_dsp_core_200_impl : public rx_dsp_core_200{ +public: +    rx_dsp_core_200_impl( +        wb_iface::sptr iface, +        const size_t dsp_base, const size_t ctrl_base, +        const boost::uint32_t sid, const bool lingering_packet +    ): +        _iface(iface), _dsp_base(dsp_base), _ctrl_base(ctrl_base) +    { +        //This is a hack/fix for the lingering packet problem. +        //The caller should also flush the recv transports +        if (lingering_packet){ +            stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); +            stream_cmd.num_samps = 1; +            issue_stream_command(stream_cmd); +        } + +        _iface->poke32(REG_RX_CTRL_CLEAR, 1); //reset +        _iface->poke32(REG_RX_CTRL_NCHANNELS, 1); +        _iface->poke32(REG_RX_CTRL_VRT_HDR, 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(REG_RX_CTRL_VRT_SID, sid); +        _iface->poke32(REG_RX_CTRL_VRT_TLR, 0); +    } + +    void set_nsamps_per_packet(const size_t nsamps){ +        _iface->poke32(REG_RX_CTRL_NSAMPS_PP, nsamps); +    } + +    void issue_stream_command(const stream_cmd_t &stream_cmd){ +        UHD_ASSERT_THROW(stream_cmd.num_samps <= 0x1fffffff); +        _continuous_streaming = stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS; + +        //setup the mode to instruction flags +        typedef boost::tuple<bool, bool, bool, bool> inst_t; +        static const uhd::dict<stream_cmd_t::stream_mode_t, inst_t> mode_to_inst = boost::assign::map_list_of +                                                                //reload, chain, samps, stop +            (stream_cmd_t::STREAM_MODE_START_CONTINUOUS,   inst_t(true,  true,  false, false)) +            (stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS,    inst_t(false, false, false, true)) +            (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE, inst_t(false, false, true,  false)) +            (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE, inst_t(false, true,  true,  false)) +        ; + +        //setup the instruction flag values +        bool inst_reload, inst_chain, inst_samps, inst_stop; +        boost::tie(inst_reload, inst_chain, inst_samps, inst_stop) = mode_to_inst[stream_cmd.stream_mode]; + +        //calculate the word from flags and length +        boost::uint32_t cmd_word = 0; +        cmd_word |= boost::uint32_t((stream_cmd.stream_now)? 1 : 0) << 31; +        cmd_word |= boost::uint32_t((inst_chain)?            1 : 0) << 30; +        cmd_word |= boost::uint32_t((inst_reload)?           1 : 0) << 29; +        cmd_word |= boost::uint32_t((inst_stop)?             1 : 0) << 28; +        cmd_word |= (inst_samps)? stream_cmd.num_samps : ((inst_stop)? 0 : 1); + +        //issue the stream command +        _iface->poke32(REG_RX_CTRL_STREAM_CMD, cmd_word); +        _iface->poke32(REG_RX_CTRL_TIME_SECS, boost::uint32_t(stream_cmd.time_spec.get_full_secs())); +        _iface->poke32(REG_RX_CTRL_TIME_TICKS, stream_cmd.time_spec.get_tick_count(_tick_rate)); //latches the command +    } + +    void set_mux(const std::string &mode, const bool fe_swapped){ +        static const uhd::dict<std::string, boost::uint32_t> mode_to_mux = boost::assign::map_list_of +            ("IQ", 0) +            ("QI", FLAG_DSP_RX_MUX_SWAP_IQ) +            ("I", FLAG_DSP_RX_MUX_REAL_MODE) +            ("Q", FLAG_DSP_RX_MUX_SWAP_IQ | FLAG_DSP_RX_MUX_REAL_MODE) +        ; +        _iface->poke32(REG_DSP_RX_MUX, mode_to_mux[mode] ^ (fe_swapped? FLAG_DSP_RX_MUX_SWAP_IQ : 0)); +    } + +    void set_tick_rate(const double rate){ +        _tick_rate = rate; +    } + +    void set_link_rate(const double rate){ +        _link_rate = rate/sizeof(boost::uint32_t); //in samps/s +    } + +    double set_host_rate(const double rate){ +        const size_t decim_rate = uhd::clip<size_t>( +            boost::math::iround(_tick_rate/rate), size_t(std::ceil(_tick_rate/_link_rate)), 512 +        ); +        size_t decim = decim_rate; + +        //determine which half-band filters are activated +        int hb0 = 0, hb1 = 0; +        if (decim % 2 == 0){ +            hb0 = 1; +            decim /= 2; +        } +        if (decim % 2 == 0){ +            hb1 = 1; +            decim /= 2; +        } + +        _iface->poke32(REG_DSP_RX_DECIM, (hb1 << 9) | (hb0 << 8) | (decim & 0xff)); + +        // Calculate CIC decimation (i.e., without halfband decimators) +        // Calculate closest multiplier constant to reverse gain absent scale multipliers +        const double rate_pow = std::pow(double(decim & 0xff), 4); +        _scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(1.65*rate_pow); + +        return _tick_rate/decim_rate; +    } + +    double get_scaling_adjustment(void){ +        return _scaling_adjustment; +    } + +    double set_freq(const double freq_){ +        //correct for outside of rate (wrap around) +        double freq = std::fmod(freq_, _tick_rate); +        if (std::abs(freq) > _tick_rate/2.0) +            freq -= boost::math::sign(freq)*_tick_rate; + +        //calculate the freq register word (signed) +        UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); +        static const double scale_factor = std::pow(2.0, 32); +        const boost::int32_t freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); + +        //update the actual frequency +        const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + +        _iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word)); + +        return actual_freq; +    } + +    uhd::meta_range_t get_freq_range(void){ +        return uhd::meta_range_t(-_tick_rate/2, +_tick_rate/2, _tick_rate/std::pow(2.0, 32)); +    } + +    void handle_overflow(void){ +        if (_continuous_streaming) issue_stream_command(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); +    } + +private: +    wb_iface::sptr _iface; +    const size_t _dsp_base, _ctrl_base; +    double _tick_rate, _link_rate; +    bool _continuous_streaming; +    double _scaling_adjustment; +}; + +rx_dsp_core_200::sptr rx_dsp_core_200::make(wb_iface::sptr iface, const size_t dsp_base, const size_t ctrl_base, const boost::uint32_t sid, const bool lingering_packet){ +    return sptr(new rx_dsp_core_200_impl(iface, dsp_base, ctrl_base, sid, lingering_packet)); +} diff --git a/host/lib/usrp/cores/rx_dsp_core_200.hpp b/host/lib/usrp/cores/rx_dsp_core_200.hpp new file mode 100644 index 000000000..391cc8441 --- /dev/null +++ b/host/lib/usrp/cores/rx_dsp_core_200.hpp @@ -0,0 +1,61 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_RX_DSP_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_RX_DSP_CORE_200_HPP + +#include <uhd/config.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/stream_cmd.hpp> +#include "wb_iface.hpp" +#include <string> + +class rx_dsp_core_200 : boost::noncopyable{ +public: +    typedef boost::shared_ptr<rx_dsp_core_200> sptr; + +    static sptr make( +        wb_iface::sptr iface, +        const size_t dsp_base, const size_t ctrl_base, +        const boost::uint32_t sid, const bool lingering_packet = false +    ); + +    virtual void set_nsamps_per_packet(const size_t nsamps) = 0; + +    virtual void issue_stream_command(const uhd::stream_cmd_t &stream_cmd) = 0; + +    virtual void set_mux(const std::string &mode, const bool fe_swapped = false) = 0; + +    virtual void set_tick_rate(const double rate) = 0; + +    virtual void set_link_rate(const double rate) = 0; + +    virtual double set_host_rate(const double rate) = 0; + +    virtual double get_scaling_adjustment(void) = 0; + +    virtual uhd::meta_range_t get_freq_range(void) = 0; + +    virtual double set_freq(const double freq) = 0; + +    virtual void handle_overflow(void) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_RX_DSP_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/rx_frontend_core_200.cpp b/host/lib/usrp/cores/rx_frontend_core_200.cpp new file mode 100644 index 000000000..0e8220b49 --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_200.cpp @@ -0,0 +1,61 @@ +// +// 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 "rx_frontend_core_200.hpp" +#include <boost/math/special_functions/round.hpp> + +#define REG_RX_FE_SWAP_IQ             _base + 0 //lower bit +#define REG_RX_FE_MAG_CORRECTION      _base + 4 //18 bits +#define REG_RX_FE_PHASE_CORRECTION    _base + 8 //18 bits +#define REG_RX_FE_OFFSET_I            _base + 12 //18 bits +#define REG_RX_FE_OFFSET_Q            _base + 16 //18 bits + +static boost::uint32_t fs_to_bits(const double num, const size_t bits){ +    return boost::int32_t(boost::math::round(num * (1 << (bits-1)))); +} + + +class rx_frontend_core_200_impl : public rx_frontend_core_200{ +public: +    rx_frontend_core_200_impl(wb_iface::sptr iface, const size_t base): +        _iface(iface), _base(base) +    { +        //NOP +    } + +    void set_mux(const bool swap){ +        _iface->poke32(REG_RX_FE_SWAP_IQ, swap? 1 : 0); +    } + +    void set_offset(const std::complex<double> &off){ +        _iface->poke32(REG_RX_FE_OFFSET_I, fs_to_bits(off.real(), 24)); +        _iface->poke32(REG_RX_FE_OFFSET_Q, fs_to_bits(off.imag(), 24)); +    } + +    void set_correction(const std::complex<double> &cor){ +        _iface->poke32(REG_RX_FE_MAG_CORRECTION, fs_to_bits(std::abs(cor), 18)); +        _iface->poke32(REG_RX_FE_PHASE_CORRECTION, fs_to_bits(std::atan2(cor.real(), cor.imag()), 18)); +    } + +private: +    wb_iface::sptr _iface; +    const size_t _base; +}; + +rx_frontend_core_200::sptr rx_frontend_core_200::make(wb_iface::sptr iface, const size_t base){ +    return sptr(new rx_frontend_core_200_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/rx_frontend_core_200.hpp b/host/lib/usrp/cores/rx_frontend_core_200.hpp new file mode 100644 index 000000000..a950e2bb7 --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_200.hpp @@ -0,0 +1,42 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_200_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" +#include <complex> +#include <string> + +class rx_frontend_core_200 : boost::noncopyable{ +public: +    typedef boost::shared_ptr<rx_frontend_core_200> sptr; + +    static sptr make(wb_iface::sptr iface, const size_t base); + +    virtual void set_mux(const bool swap) = 0; + +    virtual void set_offset(const std::complex<double> &off) = 0; + +    virtual void set_correction(const std::complex<double> &cor) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/spi_core_100.cpp b/host/lib/usrp/cores/spi_core_100.cpp new file mode 100644 index 000000000..d11a499a9 --- /dev/null +++ b/host/lib/usrp/cores/spi_core_100.cpp @@ -0,0 +1,88 @@ +// +// 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 "spi_core_100.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/thread/thread.hpp> //sleep + +#define REG_SPI_TXRX0 _base + 0 +#define REG_SPI_TXRX1 _base + 4 +#define REG_SPI_TXRX2 _base + 8 +#define REG_SPI_TXRX3 _base + 12 +#define REG_SPI_CTRL  _base + 16 +#define REG_SPI_DIV   _base + 20 +#define REG_SPI_SS    _base + 24 + +//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 + +using namespace uhd; + +class spi_core_100_impl : public spi_core_100{ +public: +    spi_core_100_impl(wb_iface::sptr iface, const size_t base): +        _iface(iface), _base(base) { /* NOP */} + +    boost::uint32_t transact_spi( +        int which_slave, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits, +        bool readback +    ){ +        UHD_ASSERT_THROW(num_bits <= 32 and (num_bits % 8) == 0); + +        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; + +        spi_wait(); +        _iface->poke16(REG_SPI_DIV, 0x0001); // = fpga_clk / 4 +        _iface->poke32(REG_SPI_SS, which_slave & 0xFFFF); +        _iface->poke32(REG_SPI_TXRX0, data); +        _iface->poke16(REG_SPI_CTRL, ctrl); +        _iface->poke16(REG_SPI_CTRL, ctrl | SPI_CTRL_GO_BSY); + +        if (not readback) return 0; +        spi_wait(); +        return _iface->peek32(REG_SPI_TXRX0); +    } + +private: +    void spi_wait(void) { +        for (size_t i = 0; i < 100; i++){ +            if ((_iface->peek16(REG_SPI_CTRL) & SPI_CTRL_GO_BSY) == 0) return; +            boost::this_thread::sleep(boost::posix_time::milliseconds(1)); +        } +        UHD_MSG(error) << "spi_core_100: spi_wait timeout" << std::endl; +    } + +    wb_iface::sptr _iface; +    const size_t _base; +}; + +spi_core_100::sptr spi_core_100::make(wb_iface::sptr iface, const size_t base){ +    return sptr(new spi_core_100_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/spi_core_100.hpp b/host/lib/usrp/cores/spi_core_100.hpp new file mode 100644 index 000000000..87d328aaa --- /dev/null +++ b/host/lib/usrp/cores/spi_core_100.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_SPI_CORE_100_HPP +#define INCLUDED_LIBUHD_USRP_SPI_CORE_100_HPP + +#include <uhd/config.hpp> +#include <uhd/types/serial.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class spi_core_100 : boost::noncopyable, public uhd::spi_iface{ +public: +    typedef boost::shared_ptr<spi_core_100> sptr; + +    //! makes a new spi core from iface and slave base +    static sptr make(wb_iface::sptr iface, const size_t base); +}; + +#endif /* INCLUDED_LIBUHD_USRP_SPI_CORE_100_HPP */ diff --git a/host/lib/usrp/cores/time64_core_200.cpp b/host/lib/usrp/cores/time64_core_200.cpp new file mode 100644 index 000000000..23d1bdea2 --- /dev/null +++ b/host/lib/usrp/cores/time64_core_200.cpp @@ -0,0 +1,132 @@ +// +// 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 "time64_core_200.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/math/special_functions/round.hpp> + +#define REG_TIME64_SECS        _base + 0 +#define REG_TIME64_TICKS       _base + 4 +#define REG_TIME64_FLAGS       _base + 8 +#define REG_TIME64_IMM         _base + 12 +#define REG_TIME64_TPS         _base + 16 +#define REG_TIME64_MIMO_SYNC   _base + 20 //lower byte is delay cycles + +//pps flags (see above) +#define FLAG_TIME64_PPS_NEGEDGE (0 << 0) +#define FLAG_TIME64_PPS_POSEDGE (1 << 0) +#define FLAG_TIME64_PPS_SMA     (0 << 1) +#define FLAG_TIME64_PPS_MIMO    (1 << 1) //apparently not used + +#define FLAG_TIME64_LATCH_NOW 1 +#define FLAG_TIME64_LATCH_NEXT_PPS 0 + +#define FLAG_TIME64_MIMO_SYNC (1 << 8) + +using namespace uhd; + +class time64_core_200_impl : public time64_core_200{ +public: +    time64_core_200_impl( +        wb_iface::sptr iface, const size_t base, +        const readback_bases_type &readback_bases, +        const size_t mimo_delay_cycles +    ): +        _iface(iface), _base(base), +        _readback_bases(readback_bases), +        _mimo_delay_cycles(mimo_delay_cycles) +    { +        _sources.push_back("none"); +        _sources.push_back("external"); +        _sources.push_back("_external_"); +        if (_mimo_delay_cycles != 0) _sources.push_back("mimo"); +    } + +    void set_tick_rate(const double rate){ +        _tick_rate = rate; +        _iface->poke32(REG_TIME64_TPS, boost::math::iround(rate)); +    } + +    uhd::time_spec_t get_time_now(void){ +        for (size_t i = 0; i < 3; i++){ //special algorithm because we cant read 64 bits synchronously +            const boost::uint32_t secs = _iface->peek32(_readback_bases.rb_secs_now); +            const boost::uint32_t ticks = _iface->peek32(_readback_bases.rb_ticks_now); +            if (secs != _iface->peek32(_readback_bases.rb_secs_now)) continue; +            return time_spec_t(secs, ticks, _tick_rate); +        } +        throw uhd::runtime_error("time64_core_200: get time now timeout"); +    } + +    uhd::time_spec_t get_time_last_pps(void){ +        for (size_t i = 0; i < 3; i++){ //special algorithm because we cant read 64 bits synchronously +            const boost::uint32_t secs = _iface->peek32(_readback_bases.rb_secs_pps); +            const boost::uint32_t ticks = _iface->peek32(_readback_bases.rb_ticks_pps); +            if (secs != _iface->peek32(_readback_bases.rb_secs_pps)) continue; +            return time_spec_t(secs, ticks, _tick_rate); +        } +        throw uhd::runtime_error("time64_core_200: get time last pps timeout"); +    } + +    void set_time_now(const uhd::time_spec_t &time){ +        _iface->poke32(REG_TIME64_TICKS, time.get_tick_count(_tick_rate)); +        _iface->poke32(REG_TIME64_IMM, FLAG_TIME64_LATCH_NOW); +        _iface->poke32(REG_TIME64_SECS, boost::uint32_t(time.get_full_secs())); //latches all 3 +    } + +    void set_time_next_pps(const uhd::time_spec_t &time){ +        _iface->poke32(REG_TIME64_TICKS, time.get_tick_count(_tick_rate)); +        _iface->poke32(REG_TIME64_IMM, FLAG_TIME64_LATCH_NEXT_PPS); +        _iface->poke32(REG_TIME64_SECS, boost::uint32_t(time.get_full_secs())); //latches all 3 +    } + +    void set_time_source(const std::string &source){ +        assert_has(_sources, source, "time source"); + +        //setup pps flags +        if (source == "external"){ +            _iface->poke32(REG_TIME64_FLAGS, FLAG_TIME64_PPS_SMA | FLAG_TIME64_PPS_POSEDGE); +        } +        else if (source == "_external_"){ +            _iface->poke32(REG_TIME64_FLAGS, FLAG_TIME64_PPS_SMA | FLAG_TIME64_PPS_NEGEDGE); +        } + +        //setup mimo flags +        if (source == "mimo"){ +            _iface->poke32(REG_TIME64_MIMO_SYNC, FLAG_TIME64_MIMO_SYNC | (_mimo_delay_cycles & 0xff)); +        } +        else{ +            _iface->poke32(REG_TIME64_MIMO_SYNC, 0); +        } +    } + +    std::vector<std::string> get_time_sources(void){ +        return _sources; +    } + +private: +    wb_iface::sptr _iface; +    const size_t _base; +    const readback_bases_type _readback_bases; +    double _tick_rate; +    const size_t _mimo_delay_cycles; +    std::vector<std::string> _sources; +}; + +time64_core_200::sptr time64_core_200::make(wb_iface::sptr iface, const size_t base, const readback_bases_type &readback_bases, const size_t mimo_delay_cycles){ +    return sptr(new time64_core_200_impl(iface, base, readback_bases, mimo_delay_cycles)); +} diff --git a/host/lib/usrp/cores/time64_core_200.hpp b/host/lib/usrp/cores/time64_core_200.hpp new file mode 100644 index 000000000..ebd51a02f --- /dev/null +++ b/host/lib/usrp/cores/time64_core_200.hpp @@ -0,0 +1,61 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TIME64_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_TIME64_CORE_200_HPP + +#include <uhd/config.hpp> +#include <uhd/types/time_spec.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" +#include <string> +#include <vector> + +class time64_core_200 : boost::noncopyable{ +public: +    typedef boost::shared_ptr<time64_core_200> sptr; + +    struct readback_bases_type{ +        size_t rb_secs_now, rb_ticks_now; +        size_t rb_secs_pps, rb_ticks_pps; +    }; + +    //! makes a new time64 core from iface and slave base +    static sptr make( +        wb_iface::sptr iface, const size_t base, +        const readback_bases_type &readback_bases, +        const size_t mimo_delay_cycles = 0 // 0 means no-mimo +    ); + +    virtual void set_tick_rate(const double rate) = 0; + +    virtual uhd::time_spec_t get_time_now(void) = 0; + +    virtual uhd::time_spec_t get_time_last_pps(void) = 0; + +    virtual void set_time_now(const uhd::time_spec_t &time) = 0; + +    virtual void set_time_next_pps(const uhd::time_spec_t &time) = 0; + +    virtual void set_time_source(const std::string &source) = 0; + +    virtual std::vector<std::string> get_time_sources(void) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TIME64_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/tx_dsp_core_200.cpp b/host/lib/usrp/cores/tx_dsp_core_200.cpp new file mode 100644 index 000000000..222ba589a --- /dev/null +++ b/host/lib/usrp/cores/tx_dsp_core_200.cpp @@ -0,0 +1,140 @@ +// +// 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 "tx_dsp_core_200.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> +#include <algorithm> +#include <cmath> + +#define REG_DSP_TX_FREQ          _dsp_base + 0 +#define REG_DSP_TX_SCALE_IQ      _dsp_base + 4 +#define REG_DSP_TX_INTERP        _dsp_base + 8 + +#define REG_TX_CTRL_NUM_CHAN        _ctrl_base + 0 +#define REG_TX_CTRL_CLEAR_STATE     _ctrl_base + 4 +#define REG_TX_CTRL_REPORT_SID      _ctrl_base + 8 +#define REG_TX_CTRL_POLICY          _ctrl_base + 12 +#define REG_TX_CTRL_CYCLES_PER_UP   _ctrl_base + 16 +#define REG_TX_CTRL_PACKETS_PER_UP  _ctrl_base + 20 + +#define FLAG_TX_CTRL_POLICY_WAIT          (0x1 << 0) +#define FLAG_TX_CTRL_POLICY_NEXT_PACKET   (0x1 << 1) +#define FLAG_TX_CTRL_POLICY_NEXT_BURST    (0x1 << 2) + +//enable flag for registers: cycles and packets per update packet +#define FLAG_TX_CTRL_UP_ENB              (1ul << 31) + +template <class T> T ceil_log2(T num){ +    return std::ceil(std::log(num)/std::log(T(2))); +} + +using namespace uhd; + +class tx_dsp_core_200_impl : public tx_dsp_core_200{ +public: +    tx_dsp_core_200_impl( +        wb_iface::sptr iface, +        const size_t dsp_base, const size_t ctrl_base, +        const boost::uint32_t sid +    ): +        _iface(iface), _dsp_base(dsp_base), _ctrl_base(ctrl_base) +    { +        //init the tx control registers +        _iface->poke32(REG_TX_CTRL_CLEAR_STATE, 1); //reset +        _iface->poke32(REG_TX_CTRL_NUM_CHAN, 0);    //1 channel +        _iface->poke32(REG_TX_CTRL_REPORT_SID, sid); +        _iface->poke32(REG_TX_CTRL_POLICY, FLAG_TX_CTRL_POLICY_NEXT_PACKET); +    } + +    void set_tick_rate(const double rate){ +        _tick_rate = rate; +    } + +    void set_link_rate(const double rate){ +        _link_rate = rate/sizeof(boost::uint32_t); //in samps/s +    } + +    double set_host_rate(const double rate){ +        const size_t interp_rate = uhd::clip<size_t>( +            boost::math::iround(_tick_rate/rate), size_t(std::ceil(_tick_rate/_link_rate)), 512 +        ); +        size_t interp = interp_rate; + +        //determine which half-band filters are activated +        int hb0 = 0, hb1 = 0; +        if (interp % 2 == 0){ +            hb0 = 1; +            interp /= 2; +        } +        if (interp % 2 == 0){ +            hb1 = 1; +            interp /= 2; +        } + +        _iface->poke32(REG_DSP_TX_INTERP, (hb1 << 9) | (hb0 << 8) | (interp & 0xff)); + +        // Calculate CIC interpolation (i.e., without halfband interpolators) +        // Calculate closest multiplier constant to reverse gain absent scale multipliers +        double rate_cubed = std::pow(double(interp & 0xff), 3); +        const boost::int16_t scale = boost::math::iround((4096*std::pow(2, ceil_log2(rate_cubed)))/(1.65*rate_cubed)); +        _iface->poke32(REG_DSP_TX_SCALE_IQ, (boost::uint32_t(scale) << 16) | (boost::uint32_t(scale) << 0)); + +        return _tick_rate/interp_rate; +    } + +    double set_freq(const double freq_){ +        //correct for outside of rate (wrap around) +        double freq = std::fmod(freq_, _tick_rate); +        if (std::abs(freq) > _tick_rate/2.0) +            freq -= boost::math::sign(freq)*_tick_rate; + +        //calculate the freq register word (signed) +        UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); +        static const double scale_factor = std::pow(2.0, 32); +        const boost::int32_t freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); + +        //update the actual frequency +        const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + +        _iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word)); + +        return actual_freq; +    } + +    uhd::meta_range_t get_freq_range(void){ +        return uhd::meta_range_t(-_tick_rate/2, +_tick_rate/2, _tick_rate/std::pow(2.0, 32)); +    } + +    void set_updates(const size_t cycles_per_up, const size_t packets_per_up){ +        _iface->poke32(REG_TX_CTRL_CYCLES_PER_UP,  (cycles_per_up  == 0)? 0 : (FLAG_TX_CTRL_UP_ENB | cycles_per_up)); +        _iface->poke32(REG_TX_CTRL_PACKETS_PER_UP, (packets_per_up == 0)? 0 : (FLAG_TX_CTRL_UP_ENB | packets_per_up)); +    } + +private: +    wb_iface::sptr _iface; +    const size_t _dsp_base, _ctrl_base; +    double _tick_rate, _link_rate; +}; + +tx_dsp_core_200::sptr tx_dsp_core_200::make(wb_iface::sptr iface, const size_t dsp_base, const size_t ctrl_base, const boost::uint32_t sid){ +    return sptr(new tx_dsp_core_200_impl(iface, dsp_base, ctrl_base, sid)); +} diff --git a/host/lib/usrp/cores/tx_dsp_core_200.hpp b/host/lib/usrp/cores/tx_dsp_core_200.hpp new file mode 100644 index 000000000..65f822558 --- /dev/null +++ b/host/lib/usrp/cores/tx_dsp_core_200.hpp @@ -0,0 +1,51 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TX_DSP_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_TX_DSP_CORE_200_HPP + +#include <uhd/config.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" + +class tx_dsp_core_200 : boost::noncopyable{ +public: +    typedef boost::shared_ptr<tx_dsp_core_200> sptr; + +    static sptr make( +        wb_iface::sptr iface, +        const size_t dsp_base, const size_t ctrl_base, +        const boost::uint32_t sid +    ); + +    virtual void set_tick_rate(const double rate) = 0; + +    virtual void set_link_rate(const double rate) = 0; + +    virtual double set_host_rate(const double rate) = 0; + +    virtual uhd::meta_range_t get_freq_range(void) = 0; + +    virtual double set_freq(const double freq) = 0; + +    virtual void set_updates(const size_t cycles_per_up, const size_t packets_per_up) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TX_DSP_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/tx_frontend_core_200.cpp b/host/lib/usrp/cores/tx_frontend_core_200.cpp new file mode 100644 index 000000000..a7568a81e --- /dev/null +++ b/host/lib/usrp/cores/tx_frontend_core_200.cpp @@ -0,0 +1,70 @@ +// +// 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 "tx_frontend_core_200.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/math/special_functions/round.hpp> + +#define REG_TX_FE_DC_OFFSET_I         _base + 0 //24 bits +#define REG_TX_FE_DC_OFFSET_Q         _base + 4 //24 bits +#define REG_TX_FE_MAG_CORRECTION      _base + 8 //18 bits +#define REG_TX_FE_PHASE_CORRECTION    _base + 12 //18 bits +#define REG_TX_FE_MUX                 _base + 16 //8 bits (std output = 0x10, reversed = 0x01) + +static boost::uint32_t fs_to_bits(const double num, const size_t bits){ +    return boost::int32_t(boost::math::round(num * (1 << (bits-1)))); +} + + +class tx_frontend_core_200_impl : public tx_frontend_core_200{ +public: +    tx_frontend_core_200_impl(wb_iface::sptr iface, const size_t base): +        _iface(iface), _base(base) +    { +        //NOP +    } + +    void set_mux(const std::string &mode){ +        static const uhd::dict<std::string, boost::uint32_t> mode_to_mux = boost::assign::map_list_of +            ("IQ", (0x1 << 4) | (0x0 << 0)) //DAC0Q=DUC0Q, DAC0I=DUC0I +            ("QI", (0x0 << 4) | (0x1 << 0)) //DAC0Q=DUC0I, DAC0I=DUC0Q +            ("I",  (0xf << 4) | (0x0 << 0)) //DAC0Q=ZERO,  DAC0I=DUC0I +            ("Q",  (0x0 << 4) | (0xf << 0)) //DAC0Q=DUC0I, DAC0I=ZERO +        ; +        _iface->poke32(REG_TX_FE_MUX, mode_to_mux[mode]); +    } + +    void set_dc_offset(const std::complex<double> &off){ +        _iface->poke32(REG_TX_FE_DC_OFFSET_I, fs_to_bits(off.real(), 24)); +        _iface->poke32(REG_TX_FE_DC_OFFSET_Q, fs_to_bits(off.imag(), 24)); +    } + +    void set_correction(const std::complex<double> &cor){ +        _iface->poke32(REG_TX_FE_MAG_CORRECTION, fs_to_bits(std::abs(cor), 18)); +        _iface->poke32(REG_TX_FE_PHASE_CORRECTION, fs_to_bits(std::atan2(cor.real(), cor.imag()), 18)); +    } + +private: +    wb_iface::sptr _iface; +    const size_t _base; +}; + +tx_frontend_core_200::sptr tx_frontend_core_200::make(wb_iface::sptr iface, const size_t base){ +    return sptr(new tx_frontend_core_200_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/tx_frontend_core_200.hpp b/host/lib/usrp/cores/tx_frontend_core_200.hpp new file mode 100644 index 000000000..9e4a7bc79 --- /dev/null +++ b/host/lib/usrp/cores/tx_frontend_core_200.hpp @@ -0,0 +1,42 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_RX_FRONTEND_CORE_200_HPP +#define INCLUDED_LIBUHD_USRP_RX_FRONTEND_CORE_200_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include "wb_iface.hpp" +#include <complex> +#include <string> + +class tx_frontend_core_200 : boost::noncopyable{ +public: +    typedef boost::shared_ptr<tx_frontend_core_200> sptr; + +    static sptr make(wb_iface::sptr iface, const size_t base); + +    virtual void set_mux(const std::string &mode) = 0; + +    virtual void set_dc_offset(const std::complex<double> &off) = 0; + +    virtual void set_correction(const std::complex<double> &cor) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_RX_FRONTEND_CORE_200_HPP */ diff --git a/host/lib/usrp/cores/wb_iface.hpp b/host/lib/usrp/cores/wb_iface.hpp new file mode 100644 index 000000000..982594b21 --- /dev/null +++ b/host/lib/usrp/cores/wb_iface.hpp @@ -0,0 +1,60 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_WB_IFACE_HPP +#define INCLUDED_LIBUHD_USRP_WB_IFACE_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <boost/shared_ptr.hpp> + +class wb_iface{ +public: +    typedef boost::shared_ptr<wb_iface> sptr; +    typedef boost::uint32_t wb_addr_type; + +    /*! +     * Write a register (32 bits) +     * \param addr the address +     * \param data the 32bit data +     */ +    virtual void poke32(wb_addr_type addr, boost::uint32_t data) = 0; + +    /*! +     * Read a register (32 bits) +     * \param addr the address +     * \return the 32bit data +     */ +    virtual boost::uint32_t peek32(wb_addr_type addr) = 0; + +    /*! +     * Write a register (16 bits) +     * \param addr the address +     * \param data the 16bit data +     */ +    virtual void poke16(wb_addr_type addr, boost::uint16_t data) = 0; + +    /*! +     * Read a register (16 bits) +     * \param addr the address +     * \return the 16bit data +     */ +    virtual boost::uint16_t peek16(wb_addr_type addr) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_WB_IFACE_HPP */ diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt new file mode 100644 index 000000000..c7b46e7c4 --- /dev/null +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# 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 +######################################################################## + +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/db_basic_and_lf.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_rfx.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_xcvr2450.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_sbx.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_common.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_simple.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_unknown.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.cpp +) + diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp new file mode 100644 index 000000000..ff93f1d89 --- /dev/null +++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp @@ -0,0 +1,326 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const uhd::dict<std::string, double> subdev_bandwidth_scalar = map_list_of +    ("A", 1.0) +    ("B", 1.0) +    ("AB", 2.0) +    ("BA", 2.0) +; + +/*********************************************************************** + * The basic and lf boards: + *   They share a common class because only the frequency bounds differ. + **********************************************************************/ +class basic_rx : public rx_dboard_base{ +public: +    basic_rx(ctor_args_t args, double max_freq); +    ~basic_rx(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _max_freq; +}; + +class basic_tx : public tx_dboard_base{ +public: +    basic_tx(ctor_args_t args, double max_freq); +    ~basic_tx(void); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _max_freq; +}; + +static const uhd::dict<std::string, subdev_conn_t> sd_name_to_conn = map_list_of +    ("AB", SUBDEV_CONN_COMPLEX_IQ) +    ("BA", SUBDEV_CONN_COMPLEX_QI) +    ("A",  SUBDEV_CONN_REAL_I) +    ("B",  SUBDEV_CONN_REAL_Q) +; + +/*********************************************************************** + * Register the basic and LF dboards + **********************************************************************/ +static dboard_base::sptr make_basic_rx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new basic_rx(args, 250e6)); +} + +static dboard_base::sptr make_basic_tx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new basic_tx(args, 250e6)); +} + +static dboard_base::sptr make_lf_rx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new basic_rx(args, 32e6)); +} + +static dboard_base::sptr make_lf_tx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new basic_tx(args, 32e6)); +} + +UHD_STATIC_BLOCK(reg_basic_and_lf_dboards){ +    dboard_manager::register_dboard(0x0000, &make_basic_tx, "Basic TX", sd_name_to_conn.keys()); +    dboard_manager::register_dboard(0x0001, &make_basic_rx, "Basic RX", sd_name_to_conn.keys()); +    dboard_manager::register_dboard(0x000e, &make_lf_tx,    "LF TX",    sd_name_to_conn.keys()); +    dboard_manager::register_dboard(0x000f, &make_lf_rx,    "LF RX",    sd_name_to_conn.keys()); +} + +/*********************************************************************** + * Basic and LF RX dboard + **********************************************************************/ +basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){ +    _max_freq = max_freq; +     +    //set GPIOs to output 0x0000 to decrease noise pickup +    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){ +    /* NOP */ +} + +void basic_rx::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = std::string(str(boost::format("%s - %s") +            % get_rx_id().to_pp_string() +            % get_subdev_name() +        )); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        val = double(0); +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        val = gain_range_t(0, 0, 0); +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_FREQ: +        val = double(0); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(-_max_freq, +_max_freq); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = sd_name_to_conn[get_subdev_name()]; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = subdev_bandwidth_scalar[get_subdev_name()]*_max_freq; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void basic_rx::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        UHD_ASSERT_THROW(val.as<double>() == double(0)); +        return; + +    case SUBDEV_PROP_ANTENNA: +        if (val.as<std::string>().empty()) return; +        throw uhd::value_error("no selectable antennas on this board"); + +    case SUBDEV_PROP_FREQ: +        return; // it wont do you much good, but you can set it + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << boost::format( +            "%s: No tunable bandwidth, fixed filtered to %0.2fMHz" +        ) % get_rx_id().to_pp_string() % _max_freq; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * Basic and LF TX dboard + **********************************************************************/ +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){ +    /* NOP */ +} + +void basic_tx::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = std::string(str(boost::format("%s - %s") +            % get_tx_id().to_pp_string() +            % get_subdev_name() +        )); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        val = double(0); +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        val = gain_range_t(0, 0, 0); +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_FREQ: +        val = double(0); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(-_max_freq, +_max_freq); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = sd_name_to_conn[get_subdev_name()]; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = subdev_bandwidth_scalar[get_subdev_name()]*_max_freq; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void basic_tx::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        UHD_ASSERT_THROW(val.as<double>() == double(0)); +        return; + +    case SUBDEV_PROP_ANTENNA: +        if (val.as<std::string>().empty()) return; +        throw uhd::value_error("no selectable antennas on this board"); + +    case SUBDEV_PROP_FREQ: +        return; // it wont do you much good, but you can set it + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << boost::format( +            "%s: No tunable bandwidth, fixed filtered to %0.2fMHz" +        ) % get_tx_id().to_pp_string() % _max_freq; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp new file mode 100644 index 000000000..c65c52590 --- /dev/null +++ b/host/lib/usrp/dboard/db_dbsrx.cpp @@ -0,0 +1,600 @@ +// +// 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/>. +// + +// No RX IO Pins Used + +// RX IO Functions + +#include "max2118_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The DBSRX constants + **********************************************************************/ +static const freq_range_t dbsrx_freq_range(0.8e9, 2.4e9); + +static const freq_range_t dbsrx_pfd_freq_range(0.15e6, 2.01e6); + +static const prop_names_t dbsrx_antennas = list_of("J3"); + +static const uhd::dict<std::string, gain_range_t> dbsrx_gain_ranges = map_list_of +    ("GC1", gain_range_t(0, 56, 0.5)) +    ("GC2", gain_range_t(0, 24, 1)) +; + +/*********************************************************************** + * The DBSRX dboard class + **********************************************************************/ +class dbsrx : public rx_dboard_base{ +public: +    dbsrx(ctor_args_t args); +    ~dbsrx(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _lo_freq; +    double _bandwidth; +    uhd::dict<std::string, double> _gains; +    max2118_write_regs_t _max2118_write_regs; +    max2118_read_regs_t _max2118_read_regs; +    boost::uint8_t _max2118_addr(void){ +        return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x65 : 0x67; +    }; + +    void set_lo_freq(double target_freq); +    void set_gain(double gain, const std::string &name); +    void set_bandwidth(double bandwidth); + +    void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +        start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x5)); +        stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x5)); + +        for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t) - 1){ +            int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) - 1 ? sizeof(boost::uint32_t) - 1 : stop_reg - start_addr + 1; + +            //create buffer for register data (+1 for start address) +            byte_vector_t regs_vector(num_bytes + 1); + +            //first byte is the address of first register +            regs_vector[0] = start_addr; + +            //get the register data +            for(int i=0; i<num_bytes; i++){ +                regs_vector[1+i] = _max2118_write_regs.get_reg(start_addr+i); +                UHD_LOGV(often) << boost::format( +                    "DBSRX: send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" +                ) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl; +            } + +            //send the data +            this->get_iface()->write_i2c( +                _max2118_addr(), regs_vector +            ); +        } +    } + +    void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +        static const boost::uint8_t status_addr = 0x0; +        start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x1)); +        stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x1)); + +        for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t)){ +            int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) ? sizeof(boost::uint32_t) : stop_reg - start_addr + 1; + +            //create buffer for register data +            byte_vector_t regs_vector(num_bytes); + +            //read from i2c +            regs_vector = this->get_iface()->read_i2c( +                _max2118_addr(), num_bytes +            ); + +            for(boost::uint8_t i=0; i < num_bytes; i++){ +                if (i + start_addr >= status_addr){ +                    _max2118_read_regs.set_reg(i + start_addr, regs_vector[i]); +                } +                UHD_LOGV(often) << boost::format( +                    "DBSRX: read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" +                ) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl; +            } +        } +    } + +    /*! +     * Is the LO locked? +     * \return true for locked +     */ +    bool get_locked(void){ +        read_reg(0x0, 0x0); + +        //mask and return lock detect +        bool locked = 5 >= _max2118_read_regs.adc and _max2118_read_regs.adc >= 2; + +        UHD_LOGV(often) << boost::format( +            "DBSRX: locked %d" +        ) % locked << std::endl; + +        return locked; +    } + +}; + +/*********************************************************************** + * Register the DBSRX dboard + **********************************************************************/ +static dboard_base::sptr make_dbsrx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new dbsrx(args)); +} + +UHD_STATIC_BLOCK(reg_dbsrx_dboard){ +    //register the factory function for the rx dbid (others version) +    dboard_manager::register_dboard(0x000D, &make_dbsrx, "DBSRX"); +    //register the factory function for the rx dbid (USRP1 version) +    dboard_manager::register_dboard(0x0002, &make_dbsrx, "DBSRX"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){ +    //warn user about incorrect DBID on USRP1, requires R193 populated +    if (this->get_iface()->get_special_props().soft_clock_divider and this->get_rx_id() == 0x000D) +        UHD_MSG(warning) << boost::format( +                "DBSRX: incorrect dbid\n" +                "Expected dbid 0x0002 and R193\n" +                "found dbid == %d\n" +                "Please see the daughterboard app notes"  +                ) % this->get_rx_id().to_pp_string(); + +    //warn user about incorrect DBID on non-USRP1, requires R194 populated +    if (not this->get_iface()->get_special_props().soft_clock_divider and this->get_rx_id() == 0x0002) +        UHD_MSG(warning) << boost::format( +                "DBSRX: incorrect dbid\n" +                "Expected dbid 0x000D and R194\n" +                "found dbid == %d\n" +                "Please see the daughterboard app notes"  +                ) % this->get_rx_id().to_pp_string(); + +    //enable only the clocks we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr +    if (this->get_iface()->get_special_props().soft_clock_divider){ +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x1); // GPIO0 is clock +    } +    else{ +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs +    } + +    //send initial register settings +    this->send_reg(0x0, 0x5); + +    //set defaults for LO, gains, and filter bandwidth +    _bandwidth = 33e6; +    set_lo_freq(dbsrx_freq_range.start()); + +    BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){ +        set_gain(dbsrx_gain_ranges[name].start(), name); +    } + +    set_bandwidth(33e6); // default bandwidth from datasheet +} + +dbsrx::~dbsrx(void){ +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +void dbsrx::set_lo_freq(double target_freq){ +    target_freq = dbsrx_freq_range.clip(target_freq); + +    double actual_freq=0.0, pfd_freq=0.0, ref_clock=0.0; +    int R=0, N=0, r=0, m=0; +    bool update_filter_settings = false; +    //choose refclock +    std::vector<double> clock_rates = this->get_iface()->get_clock_rates(dboard_iface::UNIT_RX); +    const double max_clock_rate = uhd::sorted(clock_rates).back(); +    BOOST_FOREACH(ref_clock, uhd::reversed(uhd::sorted(clock_rates))){ +        if (ref_clock > 27.0e6) continue; +        if (size_t(max_clock_rate/ref_clock)%2 == 1) continue; //reject asymmetric clocks (odd divisors) + +        //choose m_divider such that filter tuning constraint is met +        m = 31; +        while ((ref_clock/m < 1e6 or ref_clock/m > 2.5e6) and m > 0){ m--; } + +        UHD_LOGV(often) << boost::format( +            "DBSRX: trying ref_clock %f and m_divider %d" +        ) % (ref_clock) % m << std::endl; + +        if (m >= 32) continue; + +        //choose R +        for(r = 0; r <= 6; r += 1) { +            //compute divider from setting +            R = 1 << (r+1); +            UHD_LOGV(often) << boost::format("DBSRX R:%d\n") % R << std::endl; + +            //compute PFD compare frequency = ref_clock/R +            pfd_freq = ref_clock / R; + +            //constrain the PFD frequency to specified range +            if ((pfd_freq < dbsrx_pfd_freq_range.start()) or (pfd_freq > dbsrx_pfd_freq_range.stop())) continue; + +            //compute N +            N = int(std::floor(target_freq/pfd_freq)); + +            //constrain N to specified range +            if ((N < 256) or (N > 32768)) continue; + +            goto done_loop; +        } +    }  + +    done_loop: + +    //Assert because we failed to find a suitable combination of ref_clock, R and N  +    UHD_ASSERT_THROW(ref_clock <= 27.0e6 and ref_clock >= 0.0); +    UHD_ASSERT_THROW(ref_clock/m >= 1e6 and ref_clock/m <= 2.5e6); +    UHD_ASSERT_THROW((pfd_freq >= dbsrx_pfd_freq_range.start()) and (pfd_freq <= dbsrx_pfd_freq_range.stop())); +    UHD_ASSERT_THROW((N >= 256) and (N <= 32768)); + +    UHD_LOGV(often) << boost::format( +        "DBSRX: choose ref_clock (current: %f, new: %f) and m_divider %d" +    ) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % ref_clock % m << std::endl; + +    //if ref_clock or m divider changed, we need to update the filter settings +    if (ref_clock != this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) or m != _max2118_write_regs.m_divider) update_filter_settings = true; + +    //compute resulting output frequency +    actual_freq = pfd_freq * N; + +    //apply ref_clock, R, and N settings +    this->get_iface()->set_clock_rate(dboard_iface::UNIT_RX, ref_clock); +    ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); +    _max2118_write_regs.m_divider = m; +    _max2118_write_regs.r_divider = (max2118_write_regs_t::r_divider_t) r; +    _max2118_write_regs.set_n_divider(N); +    _max2118_write_regs.ade_vco_ade_read = max2118_write_regs_t::ADE_VCO_ADE_READ_ENABLED; +     +    //compute prescaler variables +    int scaler = actual_freq > 1125e6 ? 2 : 4; +    _max2118_write_regs.div2 = scaler == 4 ? max2118_write_regs_t::DIV2_DIV4 : max2118_write_regs_t::DIV2_DIV2; + +    UHD_LOGV(often) << boost::format( +        "DBSRX: scaler %d, actual_freq %f MHz, register bit: %d" +    ) % scaler % (actual_freq/1e6) % int(_max2118_write_regs.div2) << std::endl; + +    //compute vco frequency and select vco +    double vco_freq = actual_freq * scaler; +    if (vco_freq < 2433e6) +        _max2118_write_regs.osc_band = 0; +    else if (vco_freq < 2711e6) +        _max2118_write_regs.osc_band = 1; +    else if (vco_freq < 3025e6) +        _max2118_write_regs.osc_band = 2; +    else if (vco_freq < 3341e6) +        _max2118_write_regs.osc_band = 3; +    else if (vco_freq < 3727e6) +        _max2118_write_regs.osc_band = 4; +    else if (vco_freq < 4143e6) +        _max2118_write_regs.osc_band = 5; +    else if (vco_freq < 4493e6) +        _max2118_write_regs.osc_band = 6; +    else +        _max2118_write_regs.osc_band = 7; + +    //send settings over i2c +    send_reg(0x0, 0x4); + +    //check vtune for lock condition +    read_reg(0x0, 0x0); + +    UHD_LOGV(often) << boost::format( +        "DBSRX: initial guess for vco %d, vtune adc %d" +    ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + +    //if we are out of lock for chosen vco, change vco +    while ((_max2118_read_regs.adc == 0) or (_max2118_read_regs.adc == 7)){ + +        //vtune is too low, try lower frequency vco +        if (_max2118_read_regs.adc == 0){ +            if (_max2118_write_regs.osc_band == 0){ +                UHD_MSG(warning) << boost::format( +                        "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n"  +                        ) % int(_max2118_write_regs.osc_band); +                UHD_ASSERT_THROW(_max2118_read_regs.adc != 0); //just to cause a throw +            } +            if (_max2118_write_regs.osc_band <= 0) break; +            _max2118_write_regs.osc_band -= 1; +        } + +        //vtune is too high, try higher frequency vco +        if (_max2118_read_regs.adc == 7){ +            if (_max2118_write_regs.osc_band == 7){ +                UHD_MSG(warning) << boost::format( +                        "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n"  +                        ) % int(_max2118_write_regs.osc_band); +                UHD_ASSERT_THROW(_max2118_read_regs.adc != 7); //just to cause a throw +            } +            if (_max2118_write_regs.osc_band >= 7) break; +            _max2118_write_regs.osc_band += 1; +        } + +        UHD_LOGV(often) << boost::format( +            "DBSRX: trying vco %d, vtune adc %d" +        ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + +        //update vco selection and check vtune +        send_reg(0x2, 0x2); +        read_reg(0x0, 0x0); + +        //allow for setup time before checking condition again +        boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +    } +       +    UHD_LOGV(often) << boost::format( +        "DBSRX: final vco %d, vtune adc %d" +    ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; + +    //select charge pump bias current +    if (_max2118_read_regs.adc <= 2) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_100UA; +    else if (_max2118_read_regs.adc >= 5) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_400UA; +    else _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_200UA; +     +    //update charge pump bias current setting +    send_reg(0x2, 0x2); + +    //compute actual tuned frequency +    _lo_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) / std::pow(2.0,(1 + _max2118_write_regs.r_divider)) * _max2118_write_regs.get_n_divider(); + +    //debug output of calculated variables +    UHD_LOGV(often) +        << boost::format("DBSRX tune:\n") +        << boost::format("    VCO=%d, CP=%d, PFD Freq=%fMHz\n") % int(_max2118_write_regs.osc_band) % _max2118_write_regs.cp_current % (pfd_freq/1e6) +        << boost::format("    R=%d, N=%f, scaler=%d, div2=%d\n") % R % N % scaler % int(_max2118_write_regs.div2) +        << boost::format("    Ref    Freq=%fMHz\n") % (ref_clock/1e6) +        << boost::format("    Target Freq=%fMHz\n") % (target_freq/1e6) +        << boost::format("    Actual Freq=%fMHz\n") % (_lo_freq/1e6) +        << boost::format("    VCO    Freq=%fMHz\n") % (vco_freq/1e6) +        << std::endl; + +    if (update_filter_settings) set_bandwidth(_bandwidth); +    get_locked(); +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Convert a requested gain for the GC2 vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 5 bit the register value + */ +static int gain_to_gc2_vga_reg(double &gain){ +    int reg = 0; +    gain = dbsrx_gain_ranges["GC2"].clip(gain); + +    // Half dB steps from 0-5dB, 1dB steps from 5-24dB +    if (gain < 5) { +        reg = boost::math::iround(31.0 - gain/0.5); +        gain = double(boost::math::iround(gain) * 0.5); +    } else { +        reg = boost::math::iround(22.0 - (gain - 4.0)); +        gain = double(boost::math::iround(gain)); +    } + +    UHD_LOGV(often) << boost::format( +        "DBSRX GC2 Gain: %f dB, reg: %d" +    ) % gain % reg << std::endl; + +    return reg; +} + +/*! + * Convert a requested gain for the GC1 rf vga into the dac_volts value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ +static double gain_to_gc1_rfvga_dac(double &gain){ +    //clip the input +    gain = dbsrx_gain_ranges["GC1"].clip(gain); + +    //voltage level constants +    static const double max_volts = 1.2, min_volts = 2.7; +    static const double slope = (max_volts-min_volts)/dbsrx_gain_ranges["GC1"].stop(); + +    //calculate the voltage for the aux dac +    double dac_volts = gain*slope + min_volts; + +    UHD_LOGV(often) << boost::format( +        "DBSRX GC1 Gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + +void dbsrx::set_gain(double gain, const std::string &name){ +    assert_has(dbsrx_gain_ranges.keys(), name, "dbsrx gain name"); +    if (name == "GC2"){ +        _max2118_write_regs.gc2 = gain_to_gc2_vga_reg(gain); +        send_reg(0x5, 0x5); +    } +    else if(name == "GC1"){ +        //write the new voltage to the aux dac +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, gain_to_gc1_rfvga_dac(gain)); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _gains[name] = gain; +} + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +void dbsrx::set_bandwidth(double bandwidth){ +    //clip the input +    bandwidth = uhd::clip<double>(bandwidth, 4e6, 33e6); + +    double ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); +     +    //NOTE: _max2118_write_regs.m_divider set in set_lo_freq + +    //compute f_dac setting +    _max2118_write_regs.f_dac = uhd::clip<int>(int((((bandwidth*_max2118_write_regs.m_divider)/ref_clock) - 4)/0.145),0,127); + +    //determine actual bandwidth +    _bandwidth = double((ref_clock/(_max2118_write_regs.m_divider))*(4+0.145*_max2118_write_regs.f_dac)); + +    UHD_LOGV(often) << boost::format( +        "DBSRX Filter Bandwidth: %f MHz, m: %d, f_dac: %d\n" +    ) % (_bandwidth/1e6) % int(_max2118_write_regs.m_divider) % int(_max2118_write_regs.f_dac) << std::endl; + +    this->send_reg(0x3, 0x4); +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void dbsrx::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_gains.keys(), key.name, "dbsrx gain name"); +        val = _gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(dbsrx_gain_ranges.keys(), key.name, "dbsrx gain name"); +        val = dbsrx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(dbsrx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = dbsrx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = dbsrx_antennas.at(0); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = dbsrx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", this->get_locked(), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*_bandwidth; //_bandwidth is low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void dbsrx::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_ANTENNA: +        assert_has(dbsrx_antennas, val.as<std::string>(), "DBSRX antenna name"); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_bandwidth(val.as<double>()/2.0); //complex double-sided, we want low-pass +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_dbsrx2.cpp b/host/lib/usrp/dboard/db_dbsrx2.cpp new file mode 100644 index 000000000..f19236907 --- /dev/null +++ b/host/lib/usrp/dboard/db_dbsrx2.cpp @@ -0,0 +1,443 @@ +// +// 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/>. +// + +// No RX IO Pins Used + +#include "max2112_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The DBSRX2 constants + **********************************************************************/ +static const freq_range_t dbsrx2_freq_range(0.8e9, 2.4e9); + +static const int dbsrx2_ref_divider = 4; // Hitachi HMC426 divider (U7) + +static const prop_names_t dbsrx2_antennas = list_of("J3"); + +static const uhd::dict<std::string, gain_range_t> dbsrx2_gain_ranges = map_list_of +    ("GC1", gain_range_t(0, 73, 0.05)) +    ("BBG", gain_range_t(0, 15, 1)) +; + +/*********************************************************************** + * The DBSRX2 dboard class + **********************************************************************/ +class dbsrx2 : public rx_dboard_base{ +public: +    dbsrx2(ctor_args_t args); +    ~dbsrx2(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _lo_freq; +    double _bandwidth; +    uhd::dict<std::string, double> _gains; +    max2112_write_regs_t _max2112_write_regs; +    max2112_read_regs_t _max2112_read_regs; +    boost::uint8_t _max2112_addr(){ //0x60 or 0x61 depending on which side +        return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x60 : 0x61; +    } + +    void set_lo_freq(double target_freq); +    void set_gain(double gain, const std::string &name); +    void set_bandwidth(double bandwidth); + +    void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +        start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0xB)); +        stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0xB)); + +        for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t) - 1){ +            int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) - 1 ? sizeof(boost::uint32_t) - 1 : stop_reg - start_addr + 1; + +            //create buffer for register data (+1 for start address) +            byte_vector_t regs_vector(num_bytes + 1); + +            //first byte is the address of first register +            regs_vector[0] = start_addr; + +            //get the register data +            for(int i=0; i<num_bytes; i++){ +                regs_vector[1+i] = _max2112_write_regs.get_reg(start_addr+i); +                UHD_LOGV(often) << boost::format( +                    "DBSRX2: send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" +                ) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl; +            } + +            //send the data +            this->get_iface()->write_i2c( +                _max2112_addr(), regs_vector +            ); +        } +    } + +    void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +        static const boost::uint8_t status_addr = 0xC; +        start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0xD)); +        stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0xD)); + +        for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t)){ +            int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) ? sizeof(boost::uint32_t) : stop_reg - start_addr + 1; + +            //create address to start reading register data +            byte_vector_t address_vector(1); +            address_vector[0] = start_addr; + +            //send the address +            this->get_iface()->write_i2c( +                _max2112_addr(), address_vector +            ); + +            //create buffer for register data +            byte_vector_t regs_vector(num_bytes); + +            //read from i2c +            regs_vector = this->get_iface()->read_i2c( +                _max2112_addr(), num_bytes +            ); + +            for(boost::uint8_t i=0; i < num_bytes; i++){ +                if (i + start_addr >= status_addr){ +                    _max2112_read_regs.set_reg(i + start_addr, regs_vector[i]); +                    /* +                    UHD_LOGV(always) << boost::format( +                        "DBSRX2: set reg 0x%02x, value 0x%04x" +                    ) % int(i + start_addr) % int(_max2112_read_regs.get_reg(i + start_addr)) << std::endl; +                    */ +                } +                UHD_LOGV(often) << boost::format( +                    "DBSRX2: read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" +                ) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl; +            } +        } +    } + +    /*! +     * Is the LO locked? +     * \return true for locked +     */ +    bool get_locked(void){ +        read_reg(0xC, 0xD); + +        //mask and return lock detect +        bool locked = (_max2112_read_regs.ld & _max2112_read_regs.vasa & _max2112_read_regs.vase) != 0; + +        UHD_LOGV(often) << boost::format( +            "DBSRX2 locked: %d" +        ) % locked << std::endl; + +        return locked; +    } + +}; + +/*********************************************************************** + * Register the DBSRX2 dboard + **********************************************************************/ +// FIXME 0x67 is the default i2c address on USRP2 +//       need to handle which side for USRP1 with different address +static dboard_base::sptr make_dbsrx2(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new dbsrx2(args)); +} + +UHD_STATIC_BLOCK(reg_dbsrx2_dboard){ +    //register the factory function for the rx dbid +    dboard_manager::register_dboard(0x0012, &make_dbsrx2, "DBSRX2"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ +    //enable only the clocks we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs + +    //send initial register settings +    send_reg(0x0, 0xB); +    //for (boost::uint8_t addr=0; addr<=12; addr++) this->send_reg(addr, addr); + +    //set defaults for LO, gains +    set_lo_freq(dbsrx2_freq_range.start()); +    BOOST_FOREACH(const std::string &name, dbsrx2_gain_ranges.keys()){ +        set_gain(dbsrx2_gain_ranges[name].start(), name); +    } + +    set_bandwidth(40e6); // default bandwidth from datasheet +    get_locked(); + +    _max2112_write_regs.bbg = boost::math::iround(dbsrx2_gain_ranges["BBG"].start()); +    send_reg(0x9, 0x9); +} + +dbsrx2::~dbsrx2(void){ +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +void dbsrx2::set_lo_freq(double target_freq){ +    //target_freq = uhd::clip(target_freq, dbsrx2_freq_range.min, dbsrx2_freq_range.max); + +    //variables used in the calculation below +    int scaler = target_freq > 1125e6 ? 2 : 4; +    double ref_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); +    int R, intdiv, fracdiv, ext_div; +    double N; + +    //compute tuning variables +    ext_div = dbsrx2_ref_divider; // 12MHz < ref_freq/ext_divider < 30MHz + +    R = 1; //Divide by 1 is the only tested value + +    N = (target_freq*R*ext_div)/(ref_freq); //actual spec range is (19, 251) +    intdiv = int(std::floor(N)); //  if (intdiv < 19  or intdiv > 251) continue; +    fracdiv = std::floor((N - intdiv)*double(1 << 20)); + +    //calculate the actual freq from the values above +    N = double(intdiv) + double(fracdiv)/double(1 << 20); +    _lo_freq = (N*ref_freq)/(R*ext_div); + +    //load new counters into registers +    _max2112_write_regs.set_n_divider(intdiv); +    _max2112_write_regs.set_f_divider(fracdiv); +    _max2112_write_regs.r_divider = R; +    _max2112_write_regs.d24 = scaler == 4 ? max2112_write_regs_t::D24_DIV4 : max2112_write_regs_t::D24_DIV2; + +    //debug output of calculated variables +    UHD_LOGV(often) +        << boost::format("DBSRX2 tune:\n") +        << boost::format("    R=%d, N=%f, scaler=%d, ext_div=%d\n") % R % N % scaler % ext_div +        << boost::format("    int=%d, frac=%d, d24=%d\n") % intdiv % fracdiv % int(_max2112_write_regs.d24) +        << boost::format("    Ref    Freq=%fMHz\n") % (ref_freq/1e6) +        << boost::format("    Target Freq=%fMHz\n") % (target_freq/1e6) +        << boost::format("    Actual Freq=%fMHz\n") % (_lo_freq/1e6) +        << std::endl; + +    //send the registers +    send_reg(0x0, 0x7); + +    //FIXME: probably unnecessary to call get_locked here +    //get_locked(); + +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Convert a requested gain for the BBG vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 4 bit the register value + */ +static int gain_to_bbg_vga_reg(double &gain){ +    int reg = boost::math::iround(dbsrx2_gain_ranges["BBG"].clip(gain)); + +    gain = double(reg); + +    UHD_LOGV(often) +        << boost::format("DBSRX2 BBG Gain:\n") +        << boost::format("    %f dB, bbg: %d") % gain % reg  +        << std::endl; + +    return reg; +} + +/*! + * Convert a requested gain for the GC1 rf vga into the dac_volts value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ +static double gain_to_gc1_rfvga_dac(double &gain){ +    //clip the input +    gain = dbsrx2_gain_ranges["GC1"].clip(gain); + +    //voltage level constants +    static const double max_volts = 0.5, min_volts = 2.7; +    static const double slope = (max_volts-min_volts)/dbsrx2_gain_ranges["GC1"].stop(); + +    //calculate the voltage for the aux dac +    double dac_volts = gain*slope + min_volts; + +    UHD_LOGV(often) +        << boost::format("DBSRX2 GC1 Gain:\n") +        << boost::format("    %f dB, dac_volts: %f V") % gain % dac_volts  +        << std::endl; + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + +void dbsrx2::set_gain(double gain, const std::string &name){ +    assert_has(dbsrx2_gain_ranges.keys(), name, "dbsrx2 gain name"); +    if (name == "BBG"){ +        _max2112_write_regs.bbg = gain_to_bbg_vga_reg(gain); +        send_reg(0x9, 0x9); +    } +    else if(name == "GC1"){ +        //write the new voltage to the aux dac +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, gain_to_gc1_rfvga_dac(gain)); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _gains[name] = gain; +} + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +void dbsrx2::set_bandwidth(double bandwidth){ +    //clip the input +    bandwidth = uhd::clip<double>(bandwidth, 4e6, 40e6); + +    _max2112_write_regs.lp = int((bandwidth/1e6 - 4)/0.29 + 12); +    _bandwidth = double(4 + (_max2112_write_regs.lp - 12) * 0.29)*1e6; + +    UHD_LOGV(often) +        << boost::format("DBSRX2 Bandwidth:\n") +        << boost::format("    %f MHz, lp: %f V") % (_bandwidth/1e6) % int(_max2112_write_regs.lp) +        << std::endl; + +    this->send_reg(0x8, 0x8); +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void dbsrx2::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_gains.keys(), key.name, "dbsrx2 gain name"); +        val = _gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(dbsrx2_gain_ranges.keys(), key.name, "dbsrx2 gain name"); +        val = dbsrx2_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(dbsrx2_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = dbsrx2_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string("J3"); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = dbsrx2_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_QI; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", this->get_locked(), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = _bandwidth; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void dbsrx2::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_bandwidth(val.as<double>()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp new file mode 100644 index 000000000..14129ef72 --- /dev/null +++ b/host/lib/usrp/dboard/db_rfx.cpp @@ -0,0 +1,621 @@ +// +// 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/>. +// + +// IO Pin functions +#define POWER_IO     (1 << 7)   // Low enables power supply +#define ANTSW_IO     (1 << 6)   // On TX DB, 0 = TX, 1 = RX, on RX DB 0 = main ant, 1 = RX2 +#define MIXER_IO     (1 << 5)   // Enable appropriate mixer +#define LOCKDET_MASK (1 << 2)   // Input pin + +// Mixer constants +#define MIXER_ENB    MIXER_IO +#define MIXER_DIS    0 + +// Antenna constants +#define ANT_TX       0          //the tx line is transmitting +#define ANT_RX       ANTSW_IO   //the tx line is receiving +#define ANT_TXRX     0          //the rx line is on txrx +#define ANT_RX2      ANTSW_IO   //the rx line in on rx2 +#define ANT_XX       0          //dont care how the antenna is set + +#include "adf4360_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The RFX Series constants + **********************************************************************/ +static const prop_names_t rfx_tx_antennas = list_of("TX/RX"); + +static const prop_names_t rfx_rx_antennas = list_of("TX/RX")("RX2"); + +static const uhd::dict<std::string, gain_range_t> rfx_tx_gain_ranges; //empty + +static const uhd::dict<std::string, gain_range_t> rfx_rx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 70, 0.022)) +; + +static const uhd::dict<std::string, gain_range_t> rfx400_rx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 45, 0.022)) +; + +/*********************************************************************** + * The RFX series of dboards + **********************************************************************/ +class rfx_xcvr : public xcvr_dboard_base{ +public: +    rfx_xcvr( +        ctor_args_t args, +        const freq_range_t &freq_range, +        bool rx_div2, bool tx_div2 +    ); +    ~rfx_xcvr(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    const freq_range_t _freq_range; +    const uhd::dict<std::string, gain_range_t> _rx_gain_ranges; +    const uhd::dict<dboard_iface::unit_t, bool> _div2; +    double       _rx_lo_freq, _tx_lo_freq; +    std::string  _rx_ant; +    uhd::dict<std::string, double> _rx_gains; +    boost::uint16_t _power_up; + +    void set_rx_lo_freq(double freq); +    void set_tx_lo_freq(double freq); +    void set_rx_ant(const std::string &ant); +    void set_tx_ant(const std::string &ant); +    void set_rx_gain(double gain, const std::string &name); +    void set_tx_gain(double gain, const std::string &name); + +    /*! +     * Set the LO frequency for the particular dboard unit. +     * \param unit which unit rx or tx +     * \param target_freq the desired frequency in Hz +     * \return the actual frequency in Hz +     */ +    double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + +    /*! +     * Get the lock detect status of the LO. +     * \param unit which unit rx or tx +     * \return true for locked +     */ +    bool get_locked(dboard_iface::unit_t unit){ +        return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; +    } + +    /*! +     * Read the RSSI from the aux adc +     * \return the rssi in dB +     */ +    double get_rssi(void){ +        //RSSI from VAGC vs RF Power, Fig 34, pg 13 +        double max_power = -3.0; + +        //constants for the rssi calculation +        static const double min_v = 0.35, max_v = 1.0; +        static const double rssi_dyn_range = 60; +        //calculate the rssi from the voltage +        double voltage = this->get_iface()->read_aux_adc(dboard_iface::UNIT_RX, dboard_iface::AUX_ADC_B); +        return max_power - rssi_dyn_range*(voltage - min_v)/(max_v - min_v); +    } +}; + +/*********************************************************************** + * Register the RFX dboards (min freq, max freq, rx div2, tx div2) + **********************************************************************/ +static dboard_base::sptr make_rfx_flex400(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(400e6, 500e6), true, true)); +} + +static dboard_base::sptr make_rfx_flex900(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(750e6, 1050e6), true, true)); +} + +static dboard_base::sptr make_rfx_flex1800(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(1500e6, 2100e6), false, false)); +} + +static dboard_base::sptr make_rfx_flex1200(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(1150e6, 1450e6), true, true)); +} + +static dboard_base::sptr make_rfx_flex2200(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(2000e6, 2400e6), false, false)); +} + +static dboard_base::sptr make_rfx_flex2400(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new rfx_xcvr(args, freq_range_t(2300e6, 2900e6), false, false)); +} + +UHD_STATIC_BLOCK(reg_rfx_dboards){ +    dboard_manager::register_dboard(0x0024, 0x0028, &make_rfx_flex400,  "RFX400"); +    dboard_manager::register_dboard(0x0025, 0x0029, &make_rfx_flex900,  "RFX900"); +    dboard_manager::register_dboard(0x0034, 0x0035, &make_rfx_flex1800, "RFX1800"); +    dboard_manager::register_dboard(0x0026, 0x002a, &make_rfx_flex1200, "RFX1200"); +    dboard_manager::register_dboard(0x002c, 0x002d, &make_rfx_flex2200, "RFX2200"); +    dboard_manager::register_dboard(0x0027, 0x002b, &make_rfx_flex2400, "RFX2400"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +rfx_xcvr::rfx_xcvr( +    ctor_args_t args, +    const freq_range_t &freq_range, +    bool rx_div2, bool tx_div2 +): +    xcvr_dboard_base(args), +    _freq_range(freq_range), +    _rx_gain_ranges((get_rx_id() == 0x0024)? +        rfx400_rx_gain_ranges : rfx_rx_gain_ranges +    ), +    _div2(map_list_of +        (dboard_iface::UNIT_RX, rx_div2) +        (dboard_iface::UNIT_TX, tx_div2) +    ), +    _power_up((get_rx_id() == 0x0024 && get_tx_id() == 0x0028) ? POWER_IO : 0) +{ +    //enable the clocks that we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set the gpio directions and atr controls (identically) +    boost::uint16_t output_enables = POWER_IO | ANTSW_IO | MIXER_IO; +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, output_enables); +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, output_enables); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, output_enables); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, output_enables); + +    //setup the tx atr (this does not change with antenna) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        _power_up | ANT_XX | MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     _power_up | ANT_RX | MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     _power_up | ANT_TX | MIXER_ENB); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + +    //setup the rx atr (this does not change with antenna) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE,        _power_up | ANT_XX | MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY,     _power_up | ANT_XX | MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); + +    //set some default values +    set_rx_lo_freq((_freq_range.start() + _freq_range.stop())/2.0); +    set_tx_lo_freq((_freq_range.start() + _freq_range.stop())/2.0); +    set_rx_ant("RX2"); + +    BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){ +        set_rx_gain(_rx_gain_ranges[name].start(), name); +    } +} + +rfx_xcvr::~rfx_xcvr(void){ +    /* NOP */ +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void rfx_xcvr::set_rx_ant(const std::string &ant){ +    //validate input +    assert_has(rfx_rx_antennas, ant, "rfx rx antenna name"); + +    //set the rx atr regs that change with antenna setting +    this->get_iface()->set_atr_reg( +        dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, +        _power_up | MIXER_ENB | ((ant == "TX/RX")? ANT_TXRX : ANT_RX2) +    ); + +    //shadow the setting +    _rx_ant = ant; +} + +void rfx_xcvr::set_tx_ant(const std::string &ant){ +    assert_has(rfx_tx_antennas, ant, "rfx tx antenna name"); +    //only one antenna option, do nothing +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +static double rx_pga0_gain_to_dac_volts(double &gain, double range){ +    //voltage level constants (negative slope) +    static const double max_volts = .2, min_volts = 1.2; +    static const double slope = (max_volts-min_volts)/(range); + +    //calculate the voltage for the aux dac +    double dac_volts = uhd::clip<double>(gain*slope + min_volts, max_volts, min_volts); + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + +void rfx_xcvr::set_tx_gain(double, const std::string &name){ +    assert_has(rfx_tx_gain_ranges.keys(), name, "rfx tx gain name"); +    UHD_THROW_INVALID_CODE_PATH(); //no gains to set +} + +void rfx_xcvr::set_rx_gain(double gain, const std::string &name){ +    assert_has(_rx_gain_ranges.keys(), name, "rfx rx gain name"); +    if(name == "PGA0"){ +        double dac_volts = rx_pga0_gain_to_dac_volts(gain,  +                              (_rx_gain_ranges["PGA0"].stop() - _rx_gain_ranges["PGA0"].start())); +        _rx_gains[name] = gain; + +        //write the new voltage to the aux dac +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, dac_volts); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +void rfx_xcvr::set_rx_lo_freq(double freq){ +    _rx_lo_freq = set_lo_freq(dboard_iface::UNIT_RX, freq); +} + +void rfx_xcvr::set_tx_lo_freq(double freq){ +    _tx_lo_freq = set_lo_freq(dboard_iface::UNIT_TX, freq); +} + +double rfx_xcvr::set_lo_freq( +    dboard_iface::unit_t unit, +    double target_freq +){ +    UHD_LOGV(often) << boost::format( +        "RFX tune: target frequency %f Mhz" +    ) % (target_freq/1e6) << std::endl; + +    //clip the input +    target_freq = _freq_range.clip(target_freq); +    if (_div2[unit]) target_freq *= 2; + +    //rfx400 rx is a special case with div2 in mixer, so adf4360 must output fundamental +    bool is_rx_rfx400 = ((get_rx_id() == 0x0024) && unit != dboard_iface::UNIT_TX); + +    //map prescalers to the register enums +    static const uhd::dict<int, adf4360_regs_t::prescaler_value_t> prescaler_to_enum = map_list_of +        (8,  adf4360_regs_t::PRESCALER_VALUE_8_9) +        (16, adf4360_regs_t::PRESCALER_VALUE_16_17) +        (32, adf4360_regs_t::PRESCALER_VALUE_32_33) +    ; + +    //map band select clock dividers to enums +    static const uhd::dict<int, adf4360_regs_t::band_select_clock_div_t> bandsel_to_enum = map_list_of +        (1, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_1) +        (2, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_2) +        (4, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_4) +        (8, adf4360_regs_t::BAND_SELECT_CLOCK_DIV_8) +    ; + +    double actual_freq=0, ref_freq = this->get_iface()->get_clock_rate(unit); +    int R=0, BS=0, P=0, B=0, A=0; + +    /* +     * The goal here to to loop though possible R dividers, +     * band select clock dividers, and prescaler values. +     * Calculate the A and B counters for each set of values. +     * The loop exists when it meets all of the constraints. +     * The resulting loop values are loaded into the registers. +     * +     * fvco = [P*B + A] * fref/R +     * fvco*R/fref = P*B + A = N +     */ +    for(R = 2; R <= 32; R+=2){ +        BOOST_FOREACH(BS, bandsel_to_enum.keys()){ +            if (ref_freq/R/BS > 1e6) continue; //constraint on band select clock +            BOOST_FOREACH(P, prescaler_to_enum.keys()){ +                //calculate B and A from N +                double N = target_freq*R/ref_freq; +                B = int(std::floor(N/P)); +                A = boost::math::iround(N - P*B); +                if (B < A or B > 8191 or B < 3 or A > 31) continue; //constraints on A, B +                //calculate the actual frequency +                actual_freq = double(P*B + A)*ref_freq/R; +                if (actual_freq/P > 300e6) continue; //constraint on prescaler output +                //constraints met: exit loop +                goto done_loop; +            } +        } +    } done_loop: + +    UHD_LOGV(often) << boost::format( +        "RFX tune: R=%d, BS=%d, P=%d, B=%d, A=%d, DIV2=%d" +    ) % R % BS % P % B % A % int(_div2[unit] && (!is_rx_rfx400)) << std::endl; + +    //load the register values +    adf4360_regs_t regs; +    regs.core_power_level        = adf4360_regs_t::CORE_POWER_LEVEL_10MA; +    regs.counter_operation       = adf4360_regs_t::COUNTER_OPERATION_NORMAL; +    regs.muxout_control          = adf4360_regs_t::MUXOUT_CONTROL_DLD; +    regs.phase_detector_polarity = adf4360_regs_t::PHASE_DETECTOR_POLARITY_POS; +    regs.charge_pump_output      = adf4360_regs_t::CHARGE_PUMP_OUTPUT_NORMAL; +    regs.cp_gain_0               = adf4360_regs_t::CP_GAIN_0_SET1; +    regs.mute_till_ld            = adf4360_regs_t::MUTE_TILL_LD_ENB; +    regs.output_power_level      = adf4360_regs_t::OUTPUT_POWER_LEVEL_3_5MA; +    regs.current_setting1        = adf4360_regs_t::CURRENT_SETTING1_0_31MA; +    regs.current_setting2        = adf4360_regs_t::CURRENT_SETTING2_0_31MA; +    regs.power_down              = adf4360_regs_t::POWER_DOWN_NORMAL_OP; +    regs.prescaler_value         = prescaler_to_enum[P]; +    regs.a_counter               = A; +    regs.b_counter               = B; +    regs.cp_gain_1               = adf4360_regs_t::CP_GAIN_1_SET1; +    regs.divide_by_2_output      = (_div2[unit] && (!is_rx_rfx400)) ?  // Special case RFX400 RX Mixer divides by two +                                    adf4360_regs_t::DIVIDE_BY_2_OUTPUT_DIV2 : +                                    adf4360_regs_t::DIVIDE_BY_2_OUTPUT_FUND ; +    regs.divide_by_2_prescaler   = adf4360_regs_t::DIVIDE_BY_2_PRESCALER_FUND; +    regs.r_counter               = R; +    regs.ablpw                   = adf4360_regs_t::ABLPW_3_0NS; +    regs.lock_detect_precision   = adf4360_regs_t::LOCK_DETECT_PRECISION_5CYCLES; +    regs.test_mode_bit           = 0; +    regs.band_select_clock_div   = bandsel_to_enum[BS]; + +    //write the registers +    std::vector<adf4360_regs_t::addr_t> addrs = list_of //correct power-up sequence to write registers (R, C, N) +        (adf4360_regs_t::ADDR_RCOUNTER) +        (adf4360_regs_t::ADDR_CONTROL) +        (adf4360_regs_t::ADDR_NCOUNTER) +    ; +    BOOST_FOREACH(adf4360_regs_t::addr_t addr, addrs){ +        this->get_iface()->write_spi( +            unit, spi_config_t::EDGE_RISE, +            regs.get_reg(addr), 24 +        ); +    } + +    //return the actual frequency +    if (_div2[unit]) actual_freq /= 2; +    UHD_LOGV(often) << boost::format( +        "RFX tune: actual frequency %f Mhz" +    ) % (actual_freq/1e6) << std::endl; +    return actual_freq; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void rfx_xcvr::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_rx_gains.keys(), key.name, "rfx rx gain name"); +        val = _rx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(_rx_gain_ranges.keys(), key.name, "rfx rx gain name"); +        val = _rx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(_rx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _rx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = _freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _rx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = rfx_rx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_QI; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        if (key.name == "lo_locked") +            val = sensor_value_t("LO", this->get_locked(dboard_iface::UNIT_RX), "locked", "unlocked"); +        else if ((key.name == "rssi") and (get_rx_id() != 0x0024)) +            val = sensor_value_t("RSSI", this->get_rssi(), "dBm"); +        else +            UHD_THROW_INVALID_CODE_PATH(); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES:{ +            prop_names_t names = list_of("lo_locked"); +            if (get_rx_id() != 0x0024) names.push_back("rssi"); +            val = names; +        } +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void rfx_xcvr::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_rx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_rx_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_rx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "RFX: No tunable bandwidth, fixed filtered to 40MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void rfx_xcvr::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(rfx_tx_gain_ranges.keys(), key.name, "rfx tx gain name"); +        //no controllable tx gains, will not get here +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(rfx_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _tx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = _freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string("TX/RX"); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = rfx_tx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = true; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", this->get_locked(dboard_iface::UNIT_TX), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void rfx_xcvr::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_tx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_tx_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_tx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "RFX: No tunable bandwidth, fixed filtered to 40MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_sbx.cpp b/host/lib/usrp/dboard/db_sbx.cpp new file mode 100644 index 000000000..aa9556e77 --- /dev/null +++ b/host/lib/usrp/dboard/db_sbx.cpp @@ -0,0 +1,785 @@ +// +// 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/>. +// + +// Common IO Pins +#define LO_LPF_EN       (1 << 15) +#define ADF4350_CE      (1 << 3) +#define ADF4350_PDBRF   (1 << 2) +#define ADF4350_MUXOUT  (1 << 1)                // INPUT!!! +#define LOCKDET_MASK    (1 << 0)                // INPUT!!! + +// TX IO Pins +#define TRSW            (1 << 14)               // 0 = TX, 1 = RX  +#define TX_LED_TXRX     (1 << 7)                // LED for TX Antenna Selection TX/RX +#define TX_LED_LD       (1 << 6)                // LED for TX Lock Detect +#define DIS_POWER_TX    (1 << 5)                // on UNIT_TX, 0 powers up TX +#define TX_ENABLE       (1 << 4)                // on UNIT_TX, 0 disables TX Mixer + +// RX IO Pins +#define LNASW           (1 << 14)               // 0 = TX/RX, 1 = RX2 +#define RX_LED_RX1RX2   (1 << 7)                // LED for RX Antenna Selection RX1/RX2 +#define RX_LED_LD       (1 << 6)                // LED for RX Lock Detect +#define DIS_POWER_RX    (1 << 5)                // on UNIT_RX, 0 powers up RX +#define RX_DISABLE      (1 << 4)                // on UNIT_RX, 1 disables RX Mixer and Baseband + +// RX Attenuator Pins +#define RX_ATTN_SHIFT   8                       // lsb of RX Attenuator Control +#define RX_ATTN_MASK    (63 << RX_ATTN_SHIFT)   // valid bits of RX Attenuator Control + +// TX Attenuator Pins +#define TX_ATTN_SHIFT   8                       // lsb of RX Attenuator Control +#define TX_ATTN_MASK    (63 << TX_ATTN_SHIFT)   // valid bits of RX Attenuator Control + +// Mixer functions +#define TX_MIXER_ENB    (ADF4350_PDBRF) +#define TX_MIXER_DIS    0 + +#define RX_MIXER_ENB    (ADF4350_PDBRF) +#define RX_MIXER_DIS    0 + +// Pin functions +#define TX_LED_IO       (TX_LED_TXRX|TX_LED_LD)     // LED gpio lines, pull down for LED +#define TXIO_MASK       (LO_LPF_EN|TRSW|ADF4350_CE|ADF4350_PDBRF|TX_ATTN_MASK|DIS_POWER_TX|TX_ENABLE) + +#define RX_LED_IO       (RX_LED_RX1RX2|RX_LED_LD)   // LED gpio lines, pull down for LED +#define RXIO_MASK       (LO_LPF_EN|LNASW|ADF4350_CE|ADF4350_PDBRF|RX_ATTN_MASK|DIS_POWER_RX|RX_DISABLE) + +// Power functions +#define TX_POWER_UP     (ADF4350_CE|TX_ENABLE) +#define TX_POWER_DOWN   (DIS_POWER_TX) + +#define RX_POWER_UP     (ADF4350_CE) +#define RX_POWER_DOWN   (DIS_POWER_RX) + +// Antenna constants +#define ANT_TX          TRSW                    //the tx line is transmitting +#define ANT_RX          0                       //the tx line is receiving +#define ANT_TXRX        0                       //the rx line is on txrx +#define ANT_RX2         LNASW                   //the rx line in on rx2 +#define ANT_XX          LNASW                   //dont care how the antenna is set + +#include "adf4350_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/thread.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The SBX dboard constants + **********************************************************************/ +static const freq_range_t sbx_freq_range(68.75e6, 4.4e9); + +static const freq_range_t sbx_tx_lo_2dbm = list_of +    (range_t(0.35e9, 0.37e9)) +; + +static const freq_range_t sbx_enable_tx_lo_filter = list_of +    (range_t(0.4e9, 1.5e9)) +; + +static const freq_range_t sbx_enable_rx_lo_filter = list_of +    (range_t(0.4e9, 1.5e9)) +; + +static const prop_names_t sbx_tx_antennas = list_of("TX/RX"); + +static const prop_names_t sbx_rx_antennas = list_of("TX/RX")("RX2"); + +static const uhd::dict<std::string, gain_range_t> sbx_tx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 31.5, double(0.5))) +; + +static const uhd::dict<std::string, gain_range_t> sbx_rx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 31.5, double(0.5))) +; + +/*********************************************************************** + * The SBX dboard + **********************************************************************/ +class sbx_xcvr : public xcvr_dboard_base{ +public: +    sbx_xcvr(ctor_args_t args); +    ~sbx_xcvr(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    uhd::dict<std::string, double> _tx_gains, _rx_gains; +    double       _rx_lo_freq, _tx_lo_freq; +    std::string  _tx_ant, _rx_ant; + +    void set_rx_lo_freq(double freq); +    void set_tx_lo_freq(double freq); +    void set_rx_ant(const std::string &ant); +    void set_tx_ant(const std::string &ant); +    void set_rx_gain(double gain, const std::string &name); +    void set_tx_gain(double gain, const std::string &name); + +    void update_atr(void); + +    /*! +     * Set the LO frequency for the particular dboard unit. +     * \param unit which unit rx or tx +     * \param target_freq the desired frequency in Hz +     * \return the actual frequency in Hz +     */ +    double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + +    /*! +     * Get the lock detect status of the LO. +     * \param unit which unit rx or tx +     * \return true for locked +     */ +    bool get_locked(dboard_iface::unit_t unit){ +        return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; +    } + +    /*! +     * Flash the LEDs +     */ +    void flash_leds(void) { +        //Remove LED gpios from ATR control temporarily and set to outputs +        this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); +        this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|RX_LED_IO)); +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); + +        /* +        //flash All LEDs +        for (int i = 0; i < 3; i++) { +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_IO, RX_LED_IO); +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_IO, TX_LED_IO); + +            boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO); +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO); + +            boost::this_thread::sleep(boost::posix_time::milliseconds(100)); +        } +        */ + +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_TXRX|TX_LED_LD, TX_LED_IO); +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_RX1RX2|RX_LED_LD, RX_LED_IO); +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO); +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO); +        boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +        /* +        //flash All LEDs +        for (int i = 0; i < 3; i++) { +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO); +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO); + +            boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_IO, RX_LED_IO); +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_IO, TX_LED_IO); + +            boost::this_thread::sleep(boost::posix_time::milliseconds(100)); +        } +        */ +        //Put LED gpios back in ATR control and update atr +        this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); +        this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); +    } + +}; + +/*********************************************************************** + * Register the SBX dboard (min freq, max freq, rx div2, tx div2) + **********************************************************************/ +static dboard_base::sptr make_sbx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new sbx_xcvr(args)); +} + +UHD_STATIC_BLOCK(reg_sbx_dboards){ +    dboard_manager::register_dboard(0x0054, 0x0055, &make_sbx, "SBX"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ + +    //enable the clocks that we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); + +    //flash LEDs +    flash_leds(); + +    UHD_LOGV(often) << boost::format( +        "SBX GPIO Direction: RX: 0x%08x, TX: 0x%08x" +    ) % RXIO_MASK % TXIO_MASK << std::endl; + +    //set some default values +    set_rx_lo_freq((sbx_freq_range.start() + sbx_freq_range.stop())/2.0); +    set_tx_lo_freq((sbx_freq_range.start() + sbx_freq_range.stop())/2.0); +    set_rx_ant("RX2"); + +    BOOST_FOREACH(const std::string &name, sbx_tx_gain_ranges.keys()){ +        set_tx_gain(sbx_tx_gain_ranges[name].start(), name); +    } +    BOOST_FOREACH(const std::string &name, sbx_rx_gain_ranges.keys()){ +        set_rx_gain(sbx_rx_gain_ranges[name].start(), name); +    } +} + +sbx_xcvr::~sbx_xcvr(void){ +    /* NOP */ +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +static int rx_pga0_gain_to_iobits(double &gain){ +    //clip the input +    gain = sbx_rx_gain_ranges["PGA0"].clip(gain); + +    //convert to attenuation and update iobits for atr +    double attn = sbx_rx_gain_ranges["PGA0"].stop() - gain; + +    //calculate the RX attenuation +    int attn_code = int(floor(attn*2)); +    int iobits = ((~attn_code) << RX_ATTN_SHIFT) & RX_ATTN_MASK; + +     +    UHD_LOGV(often) << boost::format( +        "SBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" +    ) % attn % attn_code % (iobits & RX_ATTN_MASK) % RX_ATTN_MASK << std::endl; + +    //the actual gain setting +    gain = sbx_rx_gain_ranges["PGA0"].stop() - double(attn_code)/2; + +    return iobits; +} + +static int tx_pga0_gain_to_iobits(double &gain){ +    //clip the input +    gain = sbx_tx_gain_ranges["PGA0"].clip(gain); + +    //convert to attenuation and update iobits for atr +    double attn = sbx_tx_gain_ranges["PGA0"].stop() - gain; + +    //calculate the TX attenuation +    int attn_code = int(floor(attn*2)); +    int iobits = ((~attn_code) << TX_ATTN_SHIFT) & TX_ATTN_MASK; + +     +    UHD_LOGV(often) << boost::format( +        "SBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" +    ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl; + +    //the actual gain setting +    gain = sbx_tx_gain_ranges["PGA0"].stop() - double(attn_code)/2; + +    return iobits; +} + +void sbx_xcvr::set_tx_gain(double gain, const std::string &name){ +    assert_has(sbx_tx_gain_ranges.keys(), name, "sbx tx gain name"); +    if(name == "PGA0"){ +        tx_pga0_gain_to_iobits(gain); +        _tx_gains[name] = gain; + +        //write the new gain to atr regs +        update_atr(); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + +void sbx_xcvr::set_rx_gain(double gain, const std::string &name){ +    assert_has(sbx_rx_gain_ranges.keys(), name, "sbx rx gain name"); +    if(name == "PGA0"){ +        rx_pga0_gain_to_iobits(gain); +        _rx_gains[name] = gain; + +        //write the new gain to atr regs +        update_atr(); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void sbx_xcvr::update_atr(void){ +    //calculate atr pins +    int rx_pga0_iobits = rx_pga0_gain_to_iobits(_rx_gains["PGA0"]); +    int tx_pga0_iobits = tx_pga0_gain_to_iobits(_tx_gains["PGA0"]); +    int rx_lo_lpf_en = (_rx_lo_freq == sbx_enable_rx_lo_filter.clip(_rx_lo_freq)) ? LO_LPF_EN : 0; +    int tx_lo_lpf_en = (_tx_lo_freq == sbx_enable_tx_lo_filter.clip(_tx_lo_freq)) ? LO_LPF_EN : 0; +    int rx_ld_led = get_locked(dboard_iface::UNIT_RX) ? 0 : RX_LED_LD; +    int tx_ld_led = get_locked(dboard_iface::UNIT_TX) ? 0 : TX_LED_LD; +    int rx_ant_led = _rx_ant == "TX/RX" ? RX_LED_RX1RX2 : 0; +    int tx_ant_led = _rx_ant == "TX/RX" ? 0 : TX_LED_TXRX; + +    //setup the tx atr (this does not change with antenna) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, +        tx_pga0_iobits | tx_lo_lpf_en | tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_XX | TX_MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, +        tx_pga0_iobits | tx_lo_lpf_en | tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_TX | TX_MIXER_ENB); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, +        tx_pga0_iobits | tx_lo_lpf_en | tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_TX | TX_MIXER_ENB); + +    //setup the rx atr (this does not change with antenna) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, +        rx_pga0_iobits | rx_lo_lpf_en | rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_XX | RX_MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, +        rx_pga0_iobits | rx_lo_lpf_en | rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_RX2 | RX_MIXER_DIS); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, +        rx_pga0_iobits | rx_lo_lpf_en | rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_RX2 | RX_MIXER_ENB); + +    //set the atr regs that change with antenna setting +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, +        tx_pga0_iobits | tx_lo_lpf_en | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_DIS | +            ((_rx_ant == "TX/RX")? ANT_RX : ANT_TX)); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, +        rx_pga0_iobits | rx_lo_lpf_en | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB |  +            ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2)); + +    UHD_LOGV(often) << boost::format( +        "SBX RXONLY ATR REG: 0x%08x" +    ) % (rx_pga0_iobits | RX_POWER_UP | RX_MIXER_ENB | ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2)) << std::endl; +} + +void sbx_xcvr::set_rx_ant(const std::string &ant){ +    //validate input +    assert_has(sbx_rx_antennas, ant, "sbx rx antenna name"); + +    //shadow the setting +    _rx_ant = ant; + +    //write the new antenna setting to atr regs +    update_atr(); +} + +void sbx_xcvr::set_tx_ant(const std::string &ant){ +    assert_has(sbx_tx_antennas, ant, "sbx tx antenna name"); +    //only one antenna option, do nothing +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +void sbx_xcvr::set_rx_lo_freq(double freq){ +    _rx_lo_freq = set_lo_freq(dboard_iface::UNIT_RX, freq); +} + +void sbx_xcvr::set_tx_lo_freq(double freq){ +    _tx_lo_freq = set_lo_freq(dboard_iface::UNIT_TX, freq); +} + +double sbx_xcvr::set_lo_freq( +    dboard_iface::unit_t unit, +    double target_freq +){ +    UHD_LOGV(often) << boost::format( +        "SBX tune: target frequency %f Mhz" +    ) % (target_freq/1e6) << std::endl; + +    //clip the input +    target_freq = sbx_freq_range.clip(target_freq); + +    //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) +    static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of +        (0,23) //adf4350_regs_t::PRESCALER_4_5 +        (1,75) //adf4350_regs_t::PRESCALER_8_9 +    ; + +    //map rf divider select output dividers to enums +    static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of +        (1,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) +        (2,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) +        (4,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) +        (8,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) +        (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) +    ; + +    double actual_freq, pfd_freq; +    double ref_freq = this->get_iface()->get_clock_rate(unit); +    int R=0, BS=0, N=0, FRAC=0, MOD=0; +    int RFdiv = 1; +    adf4350_regs_t::reference_divide_by_2_t T     = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; +    adf4350_regs_t::reference_doubler_t     D     = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;     + +    //Reference doubler for 50% duty cycle +    // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 +    if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; + +    //increase RF divider until acceptable VCO frequency +    //start with target_freq*2 because mixer has divide by 2 +    double vco_freq = target_freq; +    while (vco_freq < 2.2e9) { +        vco_freq *= 2; +        RFdiv *= 2; +    } + +    //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) +    adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + +    /* +     * The goal here is to loop though possible R dividers, +     * band select clock dividers, N (int) dividers, and FRAC  +     * (frac) dividers. +     * +     * Calculate the N and F dividers for each set of values. +     * The loop exists when it meets all of the constraints. +     * The resulting loop values are loaded into the registers. +     * +     * from pg.21 +     * +     * f_pfd = f_ref*(1+D)/(R*(1+T)) +     * f_vco = (N + (FRAC/MOD))*f_pfd +     *    N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD +     * f_rf = f_vco/RFdiv) +     * f_actual = f_rf/2 +     */ +    for(R = 1; R <= 1023; R+=1){ +        //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) +        pfd_freq = ref_freq*(1+D)/(R*(1+T)); + +        //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) +        if (pfd_freq > 25e6) continue; + +        //ignore fractional part of tuning +        N = int(std::floor(vco_freq/pfd_freq)); + +        //keep N > minimum int divider requirement +        if (N < prescaler_to_min_int_div[prescaler]) continue; + +        for(BS=1; BS <= 255; BS+=1){ +            //keep the band select frequency at or below 100KHz +            //constraint on band select clock +            if (pfd_freq/BS > 100e3) continue; +            goto done_loop; +        } +    } done_loop: + +    //Fractional-N calculation +    MOD = 4095; //max fractional accuracy +    FRAC = int((vco_freq/pfd_freq - N)*MOD); + +    //Reference divide-by-2 for 50% duty cycle +    // if R even, move one divide by 2 to to regs.reference_divide_by_2 +    if(R % 2 == 0){ +        T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; +        R /= 2; +    } + +    //actual frequency calculation +    actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv); + +    UHD_LOGV(often) +        << boost::format("SBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl +        << boost::format("SBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%d" +            ) % R % BS % N % FRAC % MOD % T % D % RFdiv % get_locked(unit)<< std::endl +        << boost::format("SBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" +            ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + +    //load the register values +    adf4350_regs_t regs; + +    if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq)))  +        regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; +    else +        regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + +    regs.frac_12_bit = FRAC; +    regs.int_16_bit = N; +    regs.mod_12_bit = MOD; +    regs.prescaler = prescaler; +    regs.r_counter_10_bit = R; +    regs.reference_divide_by_2 = T; +    regs.reference_doubler = D; +    regs.band_select_clock_div = BS; +    UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); +    regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + +    //write the registers +    //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) +    int addr; + +    for(addr=5; addr>=0; addr--){ +        UHD_LOGV(often) << boost::format( +            "SBX SPI Reg (0x%02x): 0x%08x" +        ) % addr % regs.get_reg(addr) << std::endl; +        this->get_iface()->write_spi( +            unit, spi_config_t::EDGE_RISE, +            regs.get_reg(addr), 32 +        ); +    } + +    //return the actual frequency +    UHD_LOGV(often) << boost::format( +        "SBX tune: actual frequency %f Mhz" +    ) % (actual_freq/1e6) << std::endl; +    return actual_freq; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void sbx_xcvr::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_rx_gains.keys(), key.name, "sbx rx gain name"); +        val = _rx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(sbx_rx_gain_ranges.keys(), key.name, "sbx rx gain name"); +        val = sbx_rx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(sbx_rx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _rx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = sbx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _rx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = sbx_rx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", this->get_locked(dboard_iface::UNIT_RX), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void sbx_xcvr::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_rx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_rx_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_rx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "SBX: No tunable bandwidth, fixed filtered to 40MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void sbx_xcvr::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_tx_gains.keys(), key.name, "sbx tx gain name"); +        val = _tx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(sbx_tx_gain_ranges.keys(), key.name, "sbx tx gain name"); +        val = sbx_tx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(sbx_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _tx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = sbx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string("TX/RX"); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = sbx_tx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_QI; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", this->get_locked(dboard_iface::UNIT_TX), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void sbx_xcvr::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_tx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_tx_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_tx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "SBX: No tunable bandwidth, fixed filtered to 40MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp new file mode 100644 index 000000000..3b8b276e0 --- /dev/null +++ b/host/lib/usrp/dboard/db_tvrx.cpp @@ -0,0 +1,493 @@ +// +// 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/>. +// + +// No RX IO Pins Used + +// RX IO Functions + +//ADC/DAC functions: +//DAC 1: RF AGC +//DAC 2: IF AGC + +//min freq: 50e6 +//max freq: 860e6 +//gain range: [0:1dB:115dB] + +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/array.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> +#include <cmath> +#include <cfloat> +#include <limits> +#include <tuner_4937di5_regs.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The tvrx constants + **********************************************************************/ +static const freq_range_t tvrx_freq_range(50e6, 860e6); + +static const prop_names_t tvrx_antennas = list_of("RX"); + +static const uhd::dict<std::string, freq_range_t> tvrx_freq_ranges = map_list_of +    ("VHFLO", freq_range_t(50e6, 158e6)) +    ("VHFHI", freq_range_t(158e6, 454e6)) +    ("UHF"  , freq_range_t(454e6, 860e6)) +; + +static const boost::array<double, 17> vhflo_gains_db = +    {{-6.00000, -6.00000, -6.00000, -4.00000, 0.00000, +     5.00000, 10.00000, 17.40000, 26.30000, 36.00000, +     43.00000, 48.00000, 49.50000, 50.10000, 50.30000, +     50.30000, 50.30000}}; + +static const boost::array<double, 17> vhfhi_gains_db = +    {{-13.3000,  -13.3000,  -13.3000,   -1.0000,    7.7000, +    11.0000,   14.7000,   19.3000,   26.1000,   36.0000, +    42.7000,   46.0000,   47.0000,   47.8000,   48.2000, +    48.2000,   48.2000}}; + +static const boost::array<double, 17> uhf_gains_db = +    {{-8.0000,   -8.0000,   -7.0000,    4.0000,   10.2000, +     14.5000,   17.5000,   20.0000,   24.5000,   30.8000, +     37.0000,   39.8000,   40.7000,   41.6000,   42.6000, +     43.2000,   43.8000}}; + +static const boost::array<double, 17> tvrx_if_gains_db = +    {{-1.50000,   -1.50000,   -1.50000,   -1.00000,    0.20000, +     2.10000,    4.30000,    6.40000,    9.00000,   12.00000, +     14.80000,   18.20000,   26.10000,   32.50000,  32.50000, +     32.50000,   32.50000}}; + +//gain linearization data +//this is from the datasheet and is dB vs. volts (below) +//i tried to curve fit this, but it's really just so nonlinear that you'd +//need dang near as many coefficients as to just map it like this and interp. +//these numbers are culled from the 4937DI5 datasheet and are probably totally inaccurate +//but if it's better than the old linear fit i'm happy +static const uhd::dict<std::string, boost::array<double, 17> > tvrx_rf_gains_db = map_list_of +    ("VHFLO", vhflo_gains_db) +    ("VHFHI", vhfhi_gains_db) +    ("UHF"  , uhf_gains_db) +; + +//sample voltages for the above points +static const boost::array<double, 17> tvrx_gains_volts = +    {{0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6, 2.8, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0}}; + +static uhd::dict<std::string, gain_range_t> get_tvrx_gain_ranges(void) { +    double rfmax = 0.0, rfmin = FLT_MAX; +    BOOST_FOREACH(const std::string range, tvrx_rf_gains_db.keys()) { +        double my_max = tvrx_rf_gains_db[range].back(); //we're assuming it's monotonic +        double my_min = tvrx_rf_gains_db[range].front(); //if it's not this is wrong wrong wrong +        if(my_max > rfmax) rfmax = my_max; +        if(my_min < rfmin) rfmin = my_min; +    } + +    double ifmin = tvrx_if_gains_db.front(); +    double ifmax = tvrx_if_gains_db.back(); + +    return map_list_of +        ("RF", gain_range_t(rfmin, rfmax, (rfmax-rfmin)/4096.0)) +        ("IF", gain_range_t(ifmin, ifmax, (ifmax-ifmin)/4096.0)) +    ; +} + +static const double opamp_gain = 1.22; //onboard DAC opamp gain +static const double tvrx_if_freq = 43.75e6; //IF freq of TVRX module +static const boost::uint16_t reference_divider = 640; //clock reference divider to use +static const double reference_freq = 4.0e6; + +/*********************************************************************** + * The tvrx dboard class + **********************************************************************/ +class tvrx : public rx_dboard_base{ +public: +    tvrx(ctor_args_t args); +    ~tvrx(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    uhd::dict<std::string, double> _gains; +    double _lo_freq; +    tuner_4937di5_regs_t _tuner_4937di5_regs; +    boost::uint8_t _tuner_4937di5_addr(void){ +        return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x61 : 0x60; //ok really? we could rename that call +    }; + +    void set_gain(double gain, const std::string &name); +    void set_freq(double freq); + +    void update_regs(void){ +        byte_vector_t regs_vector(4); + +        //get the register data +        for(int i=0; i<4; i++){ +            regs_vector[i] = _tuner_4937di5_regs.get_reg(i); +            UHD_LOGV(often) << boost::format( +                "tvrx: send reg 0x%02x, value 0x%04x" +            ) % int(i) % int(regs_vector[i]) << std::endl; +        } + +        //send the data +        this->get_iface()->write_i2c( +            _tuner_4937di5_addr(), regs_vector +        ); +    } + +}; + +/*********************************************************************** + * Register the tvrx dboard + **********************************************************************/ +static dboard_base::sptr make_tvrx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new tvrx(args)); +} + +UHD_STATIC_BLOCK(reg_tvrx_dboard){ +    //register the factory function for the rx dbid +    dboard_manager::register_dboard(0x0040, &make_tvrx, "TVRX"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){ +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr +    if (this->get_iface()->get_special_props().soft_clock_divider){ +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x1); // GPIO0 is clock +    } +    else{ +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs +    } + +    //send initial register settings if necessary + +    //set default freq +    _lo_freq = tvrx_freq_range.start() + tvrx_if_freq; //init _lo_freq to a sane default +    set_freq(tvrx_freq_range.start()); + +    //set default gains +    BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){ +        set_gain(get_tvrx_gain_ranges()[name].start(), name); +    } +} + +tvrx::~tvrx(void){ +} + +/*! Return a string corresponding to the relevant band + * \param freq the frequency of interest + * \return a string corresponding to the band + */ + +static std::string get_band(double freq) { +    BOOST_FOREACH(const std::string &band, tvrx_freq_ranges.keys()) { +        if(freq >= tvrx_freq_ranges[band].start() && freq <= tvrx_freq_ranges[band].stop()){ +            UHD_LOGV(often) << "Band: " << band << std::endl; +            return band; +        } +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Execute a linear interpolation to find the voltage corresponding to a desired gain + * \param gain the desired gain in dB + * \param db_vector the vector of dB readings + * \param volts_vector the corresponding vector of voltages db_vector was sampled at + * \return a voltage to feed the TVRX analog gain + */ + +static double gain_interp(double gain, boost::array<double, 17> db_vector, boost::array<double, 17> volts_vector) { +    double volts; +    gain = uhd::clip<double>(gain, db_vector.front(), db_vector.back()); //let's not get carried away here + +    boost::uint8_t gain_step = 0; +    //find which bin we're in +    for(size_t i = 0; i < db_vector.size()-1; i++) { +        if(gain >= db_vector[i] && gain <= db_vector[i+1]) gain_step = i; +    } + +    //find the current slope for linear interpolation +    double slope = (volts_vector[gain_step + 1] - volts_vector[gain_step]) +                / (db_vector[gain_step + 1] - db_vector[gain_step]); + +    //the problem here is that for gains approaching the maximum, the voltage slope becomes infinite +    //i.e., a small change in gain requires an infinite change in voltage +    //to cope, we limit the slope + +    if(slope == std::numeric_limits<double>::infinity()) +        return volts_vector[gain_step]; + +    //use the volts per dB slope to find the final interpolated voltage +    volts = volts_vector[gain_step] + (slope * (gain - db_vector[gain_step])); + +    UHD_LOGV(often) << "Gain interp: gain: " << gain << ", gain_step: " << int(gain_step) << ", slope: " << slope << ", volts: " << volts << std::endl; + +    return volts; +} + +/*! + * Convert a requested gain for the RF gain into a DAC voltage. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ + +static double rf_gain_to_voltage(double gain, double lo_freq){ +    //clip the input +    gain = get_tvrx_gain_ranges()["RF"].clip(gain); + +    //first we need to find out what band we're in, because gains are different across different bands +    std::string band = get_band(lo_freq - tvrx_if_freq); + +    //this is the voltage at the TVRX gain input +    double gain_volts = gain_interp(gain, tvrx_rf_gains_db[band], tvrx_gains_volts); +    //this is the voltage at the USRP DAC output +    double dac_volts = gain_volts / opamp_gain; + +    dac_volts = uhd::clip<double>(dac_volts, 0.0, 3.3); + +    UHD_LOGV(often) << boost::format( +        "tvrx RF AGC gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    return dac_volts; +} + +/*! + * Convert a requested gain for the IF gain into a DAC voltage. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ + +static double if_gain_to_voltage(double gain){ +    //clip the input +    gain = get_tvrx_gain_ranges()["IF"].clip(gain); + +    double gain_volts = gain_interp(gain, tvrx_if_gains_db, tvrx_gains_volts); +    double dac_volts = gain_volts / opamp_gain; + +    dac_volts = uhd::clip<double>(dac_volts, 0.0, 3.3); + +    UHD_LOGV(often) << boost::format( +        "tvrx IF AGC gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    return dac_volts; +} + +void tvrx::set_gain(double gain, const std::string &name){ +    assert_has(get_tvrx_gain_ranges().keys(), name, "tvrx gain name"); +    if (name == "RF"){ +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_B, rf_gain_to_voltage(gain, _lo_freq)); +    } +    else if(name == "IF"){ +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, if_gain_to_voltage(gain)); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _gains[name] = gain; +} + +/*! + * Set the tuner to center the desired frequency at 43.75MHz + * \param freq the requested frequency + */ + +void tvrx::set_freq(double freq) { +    freq = tvrx_freq_range.clip(freq); +    std::string prev_band = get_band(_lo_freq - tvrx_if_freq); +    std::string new_band = get_band(freq); + +    double target_lo_freq = freq + tvrx_if_freq; //the desired LO freq for high-side mixing +    double f_ref = reference_freq / double(reference_divider); //your tuning step size + +    int divisor = int((target_lo_freq + (f_ref * 4.0)) / (f_ref * 8)); //the divisor we'll use +    double actual_lo_freq = (f_ref * 8 * divisor); //the LO freq we'll actually get + +    if((divisor & ~0x7fff)) UHD_THROW_INVALID_CODE_PATH(); + +    //now we update the registers +    _tuner_4937di5_regs.db1 = (divisor >> 8) & 0xff; +    _tuner_4937di5_regs.db2 = divisor & 0xff; + +    if(new_band == "VHFLO") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFLO; +    else if(new_band == "VHFHI") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFHI; +    else if(new_band == "UHF") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_UHF; +    else UHD_THROW_INVALID_CODE_PATH(); + +    _tuner_4937di5_regs.power = tuner_4937di5_regs_t::POWER_OFF; +    update_regs(); + +    //ok don't forget to reset RF gain here if the new band != the old band +    //we do this because the gains are different for different band settings +    //not FAR off, but we do this to be consistent +    if(prev_band != new_band) set_gain(_gains["RF"], "RF"); + +    UHD_LOGV(often) << boost::format("set_freq: target LO: %f f_ref: %f divisor: %i actual LO: %f") % target_lo_freq % f_ref % divisor % actual_lo_freq << std::endl; + +    _lo_freq = actual_lo_freq; //for rx props +} + +/*********************************************************************** + * Get the alias frequency of frequency freq when sampled at fs. + * \param freq the frequency of interest + * \param fs the sample rate + * \return the alias frequency + **********************************************************************/ + +static double get_alias(double freq, double fs) { +    double alias; +    freq = fmod(freq, fs); +    if(freq >= (fs/2)) { +        alias = freq - fs; +    } else { +        alias = freq; +    } +    return alias; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void tvrx::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); +    double codec_rate; + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_gains.keys(), key.name, "tvrx gain name"); +        val = _gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(get_tvrx_gain_ranges().keys(), key.name, "tvrx gain name"); +        val = get_tvrx_gain_ranges()[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(get_tvrx_gain_ranges().keys()); +        return; + +    case SUBDEV_PROP_FREQ: +    /* +     * so here we have to do some magic. because the TVRX uses a relatively high IF, +     * we have to watch the sample rate to see if the IF will be aliased +     * or if it will fall within Nyquist. +     */ +        codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); +        val = (_lo_freq - tvrx_if_freq) + get_alias(tvrx_if_freq, codec_rate); +        UHD_LOGV(often) +            << "Getting TVRX freq..." << std::endl +            << "\tCodec rate: " << codec_rate << std::endl +            << "\tLO freq: " << _lo_freq << std::endl +            << "\tIF freq: " << tvrx_if_freq << std::endl +            << "\tAlias freq: " << get_alias(tvrx_if_freq, codec_rate) << std::endl +            << "\tCalculated freq: " << val.as<double>() << std::endl; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = tvrx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = tvrx_antennas.front(); //there's only one +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = tvrx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_REAL_I; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 6.0e6; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void tvrx::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_GAIN: +        this->set_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_FREQ: +        this->set_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "TVRX: No tunable bandwidth, fixed filtered to 6MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_tvrx2.cpp b/host/lib/usrp/dboard/db_tvrx2.cpp new file mode 100644 index 000000000..23f203b8c --- /dev/null +++ b/host/lib/usrp/dboard/db_tvrx2.cpp @@ -0,0 +1,1911 @@ +// +// 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/>. +// + +// Common IO Pins +#define REFCLOCK_DIV_MASK    ((1 << 8)|(1 << 9)|(1 << 10))    // Three GPIO lines to CPLD for Clock Divisor Selection +#define REFCLOCK_DIV8        ((1 << 8)|(1 << 9)|(1 << 10))    // GPIO to set clock div8 mode +#define REFCLOCK_DIV7        ((0 << 8)|(1 << 9)|(1 << 10))    // GPIO to set clock div7 mode +#define REFCLOCK_DIV6        ((1 << 8)|(0 << 9)|(1 << 10))    // GPIO to set clock div6 mode +#define REFCLOCK_DIV5        ((0 << 8)|(0 << 9)|(1 << 10))    // GPIO to set clock div5 mode +#define REFCLOCK_DIV4        ((1 << 8)|(1 <<9))               // GPIO to set clock div4 mode +#define REFCLOCK_DIV3        (1 <<9)                          // GPIO to set clock div3 mode +#define REFCLOCK_DIV2        (1 <<8)                          // GPIO to set clock div2 mode +#define REFCLOCK_DIV1        ((0 << 8)|(0 << 9)|(0 << 10))    // GPIO to set clock div1 mode + +// RX1 IO Pins +#define RX1_MASTERSYNC  (1 << 3)                // MASTERSYNC Signal for Slave Tuner Coordination +#define RX1_FREEZE      (1 << 2)                // FREEZE Signal for Slave Tuner Coordination +#define RX1_IRQ         (1 << 1)                // IRQ Signals TDA18272HNM State Machine Completion +#define RX1_VSYNC       (1 << 0)                // VSYNC Pulse for AGC Holdoff + +// RX2 IO Pins +#define RX2_MASTERSYNC  (1 << 7)                // MASTERSYNC Signal for Slave Tuner Coordination +#define RX2_FREEZE      (1 << 6)                // FREEZE Signal for Slave Tuner Coordination +#define RX2_IRQ         (1 << 5)                // IRQ Signals TDA18272HNM State Machine Completion +#define RX2_VSYNC       (1 << 4)                // VSYNC Pulse for AGC Holdoff + +// Pin functions +#define RX1_OUTPUT_MASK (0) +#define RX1_INPUT_MASK  (RX1_VSYNC|RX1_MASTERSYNC|RX1_FREEZE|RX1_IRQ) + +#define RX2_OUTPUT_MASK (0) +#define RX2_INPUT_MASK  (RX2_VSYNC|RX2_MASTERSYNC|RX2_FREEZE|RX2_IRQ) + +#define OUTPUT_MASK     (RX1_OUTPUT_MASK|RX2_OUTPUT_MASK|REFCLOCK_DIV_MASK) +#define INPUT_MASK      (RX1_INPUT_MASK|RX2_INPUT_MASK) + + +#include "tda18272hnm_regs.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/array.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The TVRX2 types + **********************************************************************/ +struct tvrx2_tda18272_rfcal_result_t { +    boost::int8_t    delta_c; +    boost::int8_t    c_offset; +    tvrx2_tda18272_rfcal_result_t(void): delta_c(0), c_offset(0){} +}; + +struct tvrx2_tda18272_rfcal_coeffs_t  { +    boost::uint8_t   cal_number; +    boost::int32_t   RF_A1; +    boost::int32_t   RF_B1; +    tvrx2_tda18272_rfcal_coeffs_t(void): cal_number(0), RF_A1(0), RF_B1(0) {} +    tvrx2_tda18272_rfcal_coeffs_t(boost::uint32_t num): RF_A1(0), RF_B1(0) { cal_number = num; } +}; + +struct tvrx2_tda18272_cal_map_t { +    boost::array<boost::uint32_t, 4>  cal_freq; +    boost::array<boost::uint8_t, 4>   c_offset; +    tvrx2_tda18272_cal_map_t(boost::array<boost::uint32_t, 4> freqs, boost::array<boost::uint8_t, 4> offsets) +        { cal_freq = freqs; c_offset = offsets; } +}; + +struct tvrx2_tda18272_freq_map_t { +    boost::uint32_t  rf_max; +    boost::uint8_t   c_prog; +    boost::uint8_t   gain_taper; +    boost::uint8_t   rf_band; +    tvrx2_tda18272_freq_map_t( boost::uint32_t max, boost::uint8_t c, boost::uint8_t taper, boost::uint8_t band) +        { rf_max = max; c_prog = c; gain_taper = taper; rf_band = band; } +}; + +/*********************************************************************** + * The TVRX2 constants + **********************************************************************/ + +static const boost::array<freq_range_t, 4> tvrx2_tda18272_rf_bands = list_of +    ( freq_range_t(  44.056e6, 144.408e6) ) +    ( freq_range_t( 145.432e6, 361.496e6) ) +    ( freq_range_t( 365.592e6, 618.520e6) ) +    ( freq_range_t( 619.544e6, 865.304e6) ) +; + +#define TVRX2_TDA18272_FREQ_MAP_ENTRIES (565) + +static const uhd::dict<boost::uint32_t, tvrx2_tda18272_cal_map_t> tvrx2_tda18272_cal_map = map_list_of +    (  0, tvrx2_tda18272_cal_map_t( list_of( 44032000)( 48128000)( 52224000)( 56320000), list_of(15)( 0)(10)(17) ) ) +    (  1, tvrx2_tda18272_cal_map_t( list_of( 84992000)( 89088000)( 93184000)( 97280000), list_of( 1)( 0)(-2)( 3) ) ) +    (  2, tvrx2_tda18272_cal_map_t( list_of(106496000)(111616000)(115712000)(123904000), list_of( 0)(-1)( 1)( 2) ) ) +    (  3, tvrx2_tda18272_cal_map_t( list_of(161792000)(165888000)(169984000)(174080000), list_of( 3)( 0)( 1)( 2) ) ) +    (  4, tvrx2_tda18272_cal_map_t( list_of(224256000)(228352000)(232448000)(235520000), list_of( 3)( 0)( 1)( 2) ) ) +    (  5, tvrx2_tda18272_cal_map_t( list_of(301056000)(312320000)(322560000)(335872000), list_of( 0)(-1)( 1)( 2) ) ) +    (  6, tvrx2_tda18272_cal_map_t( list_of(389120000)(393216000)(397312000)(401408000), list_of(-2)( 0)(-1)( 1) ) ) +    (  7, tvrx2_tda18272_cal_map_t( list_of(455680000)(460800000)(465920000)(471040000), list_of( 0)(-2)(-3)( 1) ) ) +    (  8, tvrx2_tda18272_cal_map_t( list_of(555008000)(563200000)(570368000)(577536000), list_of(-1)( 0)(-3)(-2) ) ) +    (  9, tvrx2_tda18272_cal_map_t( list_of(647168000)(652288000)(658432000)(662528000), list_of(-6)(-3)( 0)(-5) ) ) +    ( 10, tvrx2_tda18272_cal_map_t( list_of(748544000)(755712000)(762880000)(770048000), list_of(-6)(-3)( 0)(-5) ) ) +    ( 11, tvrx2_tda18272_cal_map_t( list_of(792576000)(801792000)(809984000)(818176000), list_of(-5)(-2)( 0)(-4) ) ) +; + +static const std::vector<tvrx2_tda18272_freq_map_t> tvrx2_tda18272_freq_map = list_of +    ( tvrx2_tda18272_freq_map_t( 39936000, 0xFF, 0x17, 0) ) +    ( tvrx2_tda18272_freq_map_t( 40960000, 0xFD, 0x17, 0) ) +    ( tvrx2_tda18272_freq_map_t( 41984000, 0xF1, 0x15, 0) ) +    ( tvrx2_tda18272_freq_map_t( 43008000, 0xE5, 0x13, 0) ) +    ( tvrx2_tda18272_freq_map_t( 44032000, 0xDB, 0x13, 0) ) +    ( tvrx2_tda18272_freq_map_t( 45056000, 0xD1, 0x12, 0) ) +    ( tvrx2_tda18272_freq_map_t( 46080000, 0xC7, 0x10, 0) ) +    ( tvrx2_tda18272_freq_map_t( 47104000, 0xBE, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 48128000, 0xB5, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 49152000, 0xAD, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 50176000, 0xA6, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 51200000, 0x9F, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 52224000, 0x98, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 53248000, 0x91, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 54272000, 0x8B, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 55296000, 0x86, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 56320000, 0x80, 0x0F, 0) ) +    ( tvrx2_tda18272_freq_map_t( 57344000, 0x7B, 0x0E, 0) ) +    ( tvrx2_tda18272_freq_map_t( 58368000, 0x76, 0x0E, 0) ) +    ( tvrx2_tda18272_freq_map_t( 59392000, 0x72, 0x0D, 0) ) +    ( tvrx2_tda18272_freq_map_t( 60416000, 0x6D, 0x0D, 0) ) +    ( tvrx2_tda18272_freq_map_t( 61440000, 0x69, 0x0C, 0) ) +    ( tvrx2_tda18272_freq_map_t( 62464000, 0x65, 0x0C, 0) ) +    ( tvrx2_tda18272_freq_map_t( 63488000, 0x61, 0x0B, 0) ) +    ( tvrx2_tda18272_freq_map_t( 64512000, 0x5E, 0x0B, 0) ) +    ( tvrx2_tda18272_freq_map_t( 64512000, 0x5A, 0x0B, 0) ) +    ( tvrx2_tda18272_freq_map_t( 65536000, 0x57, 0x0A, 0) ) +    ( tvrx2_tda18272_freq_map_t( 66560000, 0x54, 0x0A, 0) ) +    ( tvrx2_tda18272_freq_map_t( 67584000, 0x51, 0x09, 0) ) +    ( tvrx2_tda18272_freq_map_t( 68608000, 0x4E, 0x09, 0) ) +    ( tvrx2_tda18272_freq_map_t( 69632000, 0x4B, 0x09, 0) ) +    ( tvrx2_tda18272_freq_map_t( 70656000, 0x49, 0x08, 0) ) +    ( tvrx2_tda18272_freq_map_t( 71680000, 0x46, 0x08, 0) ) +    ( tvrx2_tda18272_freq_map_t( 72704000, 0x44, 0x08, 0) ) +    ( tvrx2_tda18272_freq_map_t( 73728000, 0x41, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 74752000, 0x3F, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 75776000, 0x3D, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 76800000, 0x3B, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 77824000, 0x39, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 78848000, 0x37, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 79872000, 0x35, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 80896000, 0x33, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 81920000, 0x32, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 82944000, 0x30, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 83968000, 0x2F, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 84992000, 0x2D, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 86016000, 0x2C, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 87040000, 0x2A, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t( 88064000, 0x29, 0x06, 0) ) +    ( tvrx2_tda18272_freq_map_t( 89088000, 0x27, 0x06, 0) ) +    ( tvrx2_tda18272_freq_map_t( 90112000, 0x26, 0x06, 0) ) +    ( tvrx2_tda18272_freq_map_t( 91136000, 0x25, 0x06, 0) ) +    ( tvrx2_tda18272_freq_map_t( 92160000, 0x24, 0x06, 0) ) +    ( tvrx2_tda18272_freq_map_t( 93184000, 0x22, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t( 94208000, 0x21, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t( 95232000, 0x20, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t( 96256000, 0x1F, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t( 97280000, 0x1E, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t( 98304000, 0x1D, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t( 99328000, 0x1C, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(100352000, 0x1B, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(101376000, 0x1A, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(103424000, 0x19, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(104448000, 0x18, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(105472000, 0x17, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(106496000, 0x16, 0x03, 0) ) +    ( tvrx2_tda18272_freq_map_t(106496000, 0x15, 0x03, 0) ) +    ( tvrx2_tda18272_freq_map_t(108544000, 0x14, 0x03, 0) ) +    ( tvrx2_tda18272_freq_map_t(109568000, 0x13, 0x03, 0) ) +    ( tvrx2_tda18272_freq_map_t(111616000, 0x12, 0x03, 0) ) +    ( tvrx2_tda18272_freq_map_t(112640000, 0x11, 0x03, 0) ) +    ( tvrx2_tda18272_freq_map_t(113664000, 0x11, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t(114688000, 0x10, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t(115712000, 0x0F, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t(117760000, 0x0E, 0x07, 0) ) +    ( tvrx2_tda18272_freq_map_t(119808000, 0x0D, 0x06, 0) ) +    ( tvrx2_tda18272_freq_map_t(121856000, 0x0C, 0x06, 0) ) +    ( tvrx2_tda18272_freq_map_t(123904000, 0x0B, 0x06, 0) ) +    ( tvrx2_tda18272_freq_map_t(125952000, 0x0A, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t(128000000, 0x09, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t(130048000, 0x08, 0x05, 0) ) +    ( tvrx2_tda18272_freq_map_t(133120000, 0x07, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(135168000, 0x06, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(138240000, 0x05, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(141312000, 0x04, 0x04, 0) ) +    ( tvrx2_tda18272_freq_map_t(144384000, 0x03, 0x03, 0) ) +    ( tvrx2_tda18272_freq_map_t(145408000, 0xE0, 0x3F, 1) ) +    ( tvrx2_tda18272_freq_map_t(147456000, 0xDC, 0x37, 1) ) +    ( tvrx2_tda18272_freq_map_t(148480000, 0xD9, 0x32, 1) ) +    ( tvrx2_tda18272_freq_map_t(149504000, 0xD6, 0x2F, 1) ) +    ( tvrx2_tda18272_freq_map_t(149504000, 0xD2, 0x2F, 1) ) +    ( tvrx2_tda18272_freq_map_t(150528000, 0xCF, 0x2F, 1) ) +    ( tvrx2_tda18272_freq_map_t(151552000, 0xCC, 0x2B, 1) ) +    ( tvrx2_tda18272_freq_map_t(152576000, 0xC9, 0x27, 1) ) +    ( tvrx2_tda18272_freq_map_t(153600000, 0xC5, 0x27, 1) ) +    ( tvrx2_tda18272_freq_map_t(154624000, 0xC2, 0x25, 1) ) +    ( tvrx2_tda18272_freq_map_t(155648000, 0xBF, 0x23, 1) ) +    ( tvrx2_tda18272_freq_map_t(156672000, 0xBD, 0x20, 1) ) +    ( tvrx2_tda18272_freq_map_t(157696000, 0xBA, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(158720000, 0xB7, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(159744000, 0xB4, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(160768000, 0xB1, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(161792000, 0xAF, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(162816000, 0xAC, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(163840000, 0xAA, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(164864000, 0xA7, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(165888000, 0xA5, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(166912000, 0xA2, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(167936000, 0xA0, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(168960000, 0x9D, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(169984000, 0x9B, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(171008000, 0x99, 0x1F, 1) ) +    ( tvrx2_tda18272_freq_map_t(172032000, 0x97, 0x1E, 1) ) +    ( tvrx2_tda18272_freq_map_t(173056000, 0x95, 0x1D, 1) ) +    ( tvrx2_tda18272_freq_map_t(174080000, 0x92, 0x1C, 1) ) +    ( tvrx2_tda18272_freq_map_t(175104000, 0x90, 0x1B, 1) ) +    ( tvrx2_tda18272_freq_map_t(176128000, 0x8E, 0x1A, 1) ) +    ( tvrx2_tda18272_freq_map_t(177152000, 0x8C, 0x19, 1) ) +    ( tvrx2_tda18272_freq_map_t(178176000, 0x8A, 0x18, 1) ) +    ( tvrx2_tda18272_freq_map_t(179200000, 0x88, 0x17, 1) ) +    ( tvrx2_tda18272_freq_map_t(180224000, 0x86, 0x17, 1) ) +    ( tvrx2_tda18272_freq_map_t(181248000, 0x84, 0x17, 1) ) +    ( tvrx2_tda18272_freq_map_t(182272000, 0x82, 0x17, 1) ) +    ( tvrx2_tda18272_freq_map_t(183296000, 0x81, 0x17, 1) ) +    ( tvrx2_tda18272_freq_map_t(184320000, 0x7F, 0x17, 1) ) +    ( tvrx2_tda18272_freq_map_t(185344000, 0x7D, 0x16, 1) ) +    ( tvrx2_tda18272_freq_map_t(186368000, 0x7B, 0x15, 1) ) +    ( tvrx2_tda18272_freq_map_t(187392000, 0x7A, 0x14, 1) ) +    ( tvrx2_tda18272_freq_map_t(188416000, 0x78, 0x14, 1) ) +    ( tvrx2_tda18272_freq_map_t(189440000, 0x76, 0x13, 1) ) +    ( tvrx2_tda18272_freq_map_t(190464000, 0x75, 0x13, 1) ) +    ( tvrx2_tda18272_freq_map_t(191488000, 0x73, 0x13, 1) ) +    ( tvrx2_tda18272_freq_map_t(192512000, 0x71, 0x12, 1) ) +    ( tvrx2_tda18272_freq_map_t(192512000, 0x70, 0x11, 1) ) +    ( tvrx2_tda18272_freq_map_t(193536000, 0x6E, 0x11, 1) ) +    ( tvrx2_tda18272_freq_map_t(194560000, 0x6D, 0x10, 1) ) +    ( tvrx2_tda18272_freq_map_t(195584000, 0x6B, 0x10, 1) ) +    ( tvrx2_tda18272_freq_map_t(196608000, 0x6A, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(197632000, 0x68, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(198656000, 0x67, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(199680000, 0x65, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(200704000, 0x64, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(201728000, 0x63, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(202752000, 0x61, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(203776000, 0x60, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(204800000, 0x5F, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(205824000, 0x5D, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(206848000, 0x5C, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(207872000, 0x5B, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(208896000, 0x5A, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(209920000, 0x58, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(210944000, 0x57, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(211968000, 0x56, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(212992000, 0x55, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(214016000, 0x54, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(215040000, 0x53, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(216064000, 0x52, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(217088000, 0x50, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(218112000, 0x4F, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(219136000, 0x4E, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(220160000, 0x4D, 0x0E, 1) ) +    ( tvrx2_tda18272_freq_map_t(221184000, 0x4C, 0x0E, 1) ) +    ( tvrx2_tda18272_freq_map_t(222208000, 0x4B, 0x0E, 1) ) +    ( tvrx2_tda18272_freq_map_t(223232000, 0x4A, 0x0E, 1) ) +    ( tvrx2_tda18272_freq_map_t(224256000, 0x49, 0x0D, 1) ) +    ( tvrx2_tda18272_freq_map_t(225280000, 0x48, 0x0D, 1) ) +    ( tvrx2_tda18272_freq_map_t(226304000, 0x47, 0x0D, 1) ) +    ( tvrx2_tda18272_freq_map_t(227328000, 0x46, 0x0D, 1) ) +    ( tvrx2_tda18272_freq_map_t(228352000, 0x45, 0x0C, 1) ) +    ( tvrx2_tda18272_freq_map_t(229376000, 0x44, 0x0C, 1) ) +    ( tvrx2_tda18272_freq_map_t(230400000, 0x43, 0x0C, 1) ) +    ( tvrx2_tda18272_freq_map_t(231424000, 0x42, 0x0C, 1) ) +    ( tvrx2_tda18272_freq_map_t(232448000, 0x42, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(233472000, 0x41, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(234496000, 0x40, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(234496000, 0x3F, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(235520000, 0x3E, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(236544000, 0x3D, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(237568000, 0x3C, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(239616000, 0x3B, 0x0A, 1) ) +    ( tvrx2_tda18272_freq_map_t(240640000, 0x3A, 0x0A, 1) ) +    ( tvrx2_tda18272_freq_map_t(241664000, 0x39, 0x0A, 1) ) +    ( tvrx2_tda18272_freq_map_t(242688000, 0x38, 0x0A, 1) ) +    ( tvrx2_tda18272_freq_map_t(244736000, 0x37, 0x09, 1) ) +    ( tvrx2_tda18272_freq_map_t(245760000, 0x36, 0x09, 1) ) +    ( tvrx2_tda18272_freq_map_t(246784000, 0x35, 0x09, 1) ) +    ( tvrx2_tda18272_freq_map_t(248832000, 0x34, 0x09, 1) ) +    ( tvrx2_tda18272_freq_map_t(249856000, 0x33, 0x09, 1) ) +    ( tvrx2_tda18272_freq_map_t(250880000, 0x32, 0x08, 1) ) +    ( tvrx2_tda18272_freq_map_t(252928000, 0x31, 0x08, 1) ) +    ( tvrx2_tda18272_freq_map_t(253952000, 0x30, 0x08, 1) ) +    ( tvrx2_tda18272_freq_map_t(256000000, 0x2F, 0x08, 1) ) +    ( tvrx2_tda18272_freq_map_t(257024000, 0x2E, 0x08, 1) ) +    ( tvrx2_tda18272_freq_map_t(259072000, 0x2D, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(260096000, 0x2C, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(262144000, 0x2B, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(264192000, 0x2A, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(265216000, 0x29, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(267264000, 0x28, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(269312000, 0x27, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(270336000, 0x26, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(272384000, 0x25, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(274432000, 0x24, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(276480000, 0x23, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(277504000, 0x22, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(279552000, 0x21, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(281600000, 0x20, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(283648000, 0x1F, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(285696000, 0x1E, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(287744000, 0x1D, 0x0F, 1) ) +    ( tvrx2_tda18272_freq_map_t(289792000, 0x1C, 0x0E, 1) ) +    ( tvrx2_tda18272_freq_map_t(291840000, 0x1B, 0x0E, 1) ) +    ( tvrx2_tda18272_freq_map_t(293888000, 0x1A, 0x0D, 1) ) +    ( tvrx2_tda18272_freq_map_t(296960000, 0x19, 0x0D, 1) ) +    ( tvrx2_tda18272_freq_map_t(299008000, 0x18, 0x0C, 1) ) +    ( tvrx2_tda18272_freq_map_t(301056000, 0x17, 0x0C, 1) ) +    ( tvrx2_tda18272_freq_map_t(304128000, 0x16, 0x0C, 1) ) +    ( tvrx2_tda18272_freq_map_t(306176000, 0x15, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(309248000, 0x14, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(312320000, 0x13, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(314368000, 0x12, 0x0B, 1) ) +    ( tvrx2_tda18272_freq_map_t(317440000, 0x11, 0x0A, 1) ) +    ( tvrx2_tda18272_freq_map_t(320512000, 0x10, 0x0A, 1) ) +    ( tvrx2_tda18272_freq_map_t(322560000, 0x0F, 0x0A, 1) ) +    ( tvrx2_tda18272_freq_map_t(325632000, 0x0E, 0x09, 1) ) +    ( tvrx2_tda18272_freq_map_t(328704000, 0x0D, 0x09, 1) ) +    ( tvrx2_tda18272_freq_map_t(331776000, 0x0C, 0x08, 1) ) +    ( tvrx2_tda18272_freq_map_t(335872000, 0x0B, 0x08, 1) ) +    ( tvrx2_tda18272_freq_map_t(338944000, 0x0A, 0x08, 1) ) +    ( tvrx2_tda18272_freq_map_t(343040000, 0x09, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(346112000, 0x08, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(350208000, 0x07, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(354304000, 0x06, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(358400000, 0x05, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(362496000, 0x04, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(365568000, 0x04, 0x07, 1) ) +    ( tvrx2_tda18272_freq_map_t(367616000, 0xDA, 0x2A, 2) ) +    ( tvrx2_tda18272_freq_map_t(367616000, 0xD9, 0x27, 2) ) +    ( tvrx2_tda18272_freq_map_t(368640000, 0xD8, 0x27, 2) ) +    ( tvrx2_tda18272_freq_map_t(369664000, 0xD6, 0x27, 2) ) +    ( tvrx2_tda18272_freq_map_t(370688000, 0xD5, 0x27, 2) ) +    ( tvrx2_tda18272_freq_map_t(371712000, 0xD3, 0x25, 2) ) +    ( tvrx2_tda18272_freq_map_t(372736000, 0xD2, 0x23, 2) ) +    ( tvrx2_tda18272_freq_map_t(373760000, 0xD0, 0x23, 2) ) +    ( tvrx2_tda18272_freq_map_t(374784000, 0xCF, 0x21, 2) ) +    ( tvrx2_tda18272_freq_map_t(375808000, 0xCD, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(376832000, 0xCC, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(377856000, 0xCA, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(378880000, 0xC9, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(379904000, 0xC7, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(380928000, 0xC6, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(381952000, 0xC4, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(382976000, 0xC3, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(384000000, 0xC1, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(385024000, 0xC0, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(386048000, 0xBF, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(387072000, 0xBD, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(388096000, 0xBC, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(389120000, 0xBB, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(390144000, 0xB9, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(391168000, 0xB8, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(392192000, 0xB7, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(393216000, 0xB5, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(394240000, 0xB4, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(395264000, 0xB3, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(396288000, 0xB1, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(397312000, 0xB0, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(398336000, 0xAF, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(399360000, 0xAD, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(400384000, 0xAC, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(401408000, 0xAB, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(402432000, 0xAA, 0x1F, 2) ) +    ( tvrx2_tda18272_freq_map_t(403456000, 0xA8, 0x1E, 2) ) +    ( tvrx2_tda18272_freq_map_t(404480000, 0xA7, 0x1D, 2) ) +    ( tvrx2_tda18272_freq_map_t(405504000, 0xA6, 0x1D, 2) ) +    ( tvrx2_tda18272_freq_map_t(405504000, 0xA5, 0x1C, 2) ) +    ( tvrx2_tda18272_freq_map_t(406528000, 0xA3, 0x1C, 2) ) +    ( tvrx2_tda18272_freq_map_t(407552000, 0xA2, 0x1B, 2) ) +    ( tvrx2_tda18272_freq_map_t(408576000, 0xA1, 0x1B, 2) ) +    ( tvrx2_tda18272_freq_map_t(409600000, 0xA0, 0x1B, 2) ) +    ( tvrx2_tda18272_freq_map_t(410624000, 0x9F, 0x1A, 2) ) +    ( tvrx2_tda18272_freq_map_t(411648000, 0x9D, 0x1A, 2) ) +    ( tvrx2_tda18272_freq_map_t(412672000, 0x9C, 0x19, 2) ) +    ( tvrx2_tda18272_freq_map_t(413696000, 0x9B, 0x18, 2) ) +    ( tvrx2_tda18272_freq_map_t(414720000, 0x9A, 0x18, 2) ) +    ( tvrx2_tda18272_freq_map_t(415744000, 0x99, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(416768000, 0x98, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(417792000, 0x97, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(418816000, 0x95, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(419840000, 0x94, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(420864000, 0x93, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(421888000, 0x92, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(422912000, 0x91, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(423936000, 0x90, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(424960000, 0x8F, 0x17, 2) ) +    ( tvrx2_tda18272_freq_map_t(425984000, 0x8E, 0x16, 2) ) +    ( tvrx2_tda18272_freq_map_t(427008000, 0x8D, 0x16, 2) ) +    ( tvrx2_tda18272_freq_map_t(428032000, 0x8C, 0x15, 2) ) +    ( tvrx2_tda18272_freq_map_t(429056000, 0x8B, 0x15, 2) ) +    ( tvrx2_tda18272_freq_map_t(430080000, 0x8A, 0x15, 2) ) +    ( tvrx2_tda18272_freq_map_t(431104000, 0x88, 0x14, 2) ) +    ( tvrx2_tda18272_freq_map_t(432128000, 0x87, 0x14, 2) ) +    ( tvrx2_tda18272_freq_map_t(433152000, 0x86, 0x14, 2) ) +    ( tvrx2_tda18272_freq_map_t(434176000, 0x85, 0x13, 2) ) +    ( tvrx2_tda18272_freq_map_t(435200000, 0x84, 0x13, 2) ) +    ( tvrx2_tda18272_freq_map_t(436224000, 0x83, 0x13, 2) ) +    ( tvrx2_tda18272_freq_map_t(437248000, 0x82, 0x13, 2) ) +    ( tvrx2_tda18272_freq_map_t(438272000, 0x81, 0x13, 2) ) +    ( tvrx2_tda18272_freq_map_t(439296000, 0x80, 0x12, 2) ) +    ( tvrx2_tda18272_freq_map_t(440320000, 0x7F, 0x12, 2) ) +    ( tvrx2_tda18272_freq_map_t(441344000, 0x7E, 0x12, 2) ) +    ( tvrx2_tda18272_freq_map_t(442368000, 0x7D, 0x11, 2) ) +    ( tvrx2_tda18272_freq_map_t(444416000, 0x7C, 0x11, 2) ) +    ( tvrx2_tda18272_freq_map_t(445440000, 0x7B, 0x10, 2) ) +    ( tvrx2_tda18272_freq_map_t(446464000, 0x7A, 0x10, 2) ) +    ( tvrx2_tda18272_freq_map_t(447488000, 0x79, 0x10, 2) ) +    ( tvrx2_tda18272_freq_map_t(448512000, 0x78, 0x10, 2) ) +    ( tvrx2_tda18272_freq_map_t(448512000, 0x77, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(449536000, 0x76, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(450560000, 0x75, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(451584000, 0x74, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(452608000, 0x73, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(453632000, 0x72, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(454656000, 0x71, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(455680000, 0x70, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(457728000, 0x6F, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(458752000, 0x6E, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(459776000, 0x6D, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(460800000, 0x6C, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(461824000, 0x6B, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(462848000, 0x6A, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(464896000, 0x69, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(465920000, 0x68, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(466944000, 0x67, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(467968000, 0x66, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(468992000, 0x65, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(471040000, 0x64, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(472064000, 0x63, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(473088000, 0x62, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(474112000, 0x61, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(476160000, 0x60, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(477184000, 0x5F, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(478208000, 0x5E, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(479232000, 0x5D, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(481280000, 0x5C, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(482304000, 0x5B, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(483328000, 0x5A, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(485376000, 0x59, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(486400000, 0x58, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(487424000, 0x57, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(489472000, 0x56, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(490496000, 0x55, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(490496000, 0x54, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(492544000, 0x53, 0x0E, 2) ) +    ( tvrx2_tda18272_freq_map_t(493568000, 0x52, 0x0E, 2) ) +    ( tvrx2_tda18272_freq_map_t(495616000, 0x51, 0x0E, 2) ) +    ( tvrx2_tda18272_freq_map_t(496640000, 0x50, 0x0E, 2) ) +    ( tvrx2_tda18272_freq_map_t(497664000, 0x4F, 0x0E, 2) ) +    ( tvrx2_tda18272_freq_map_t(499712000, 0x4E, 0x0D, 2) ) +    ( tvrx2_tda18272_freq_map_t(500736000, 0x4D, 0x0D, 2) ) +    ( tvrx2_tda18272_freq_map_t(502784000, 0x4C, 0x0D, 2) ) +    ( tvrx2_tda18272_freq_map_t(503808000, 0x4B, 0x0D, 2) ) +    ( tvrx2_tda18272_freq_map_t(505856000, 0x4A, 0x0C, 2) ) +    ( tvrx2_tda18272_freq_map_t(506880000, 0x49, 0x0C, 2) ) +    ( tvrx2_tda18272_freq_map_t(508928000, 0x48, 0x0C, 2) ) +    ( tvrx2_tda18272_freq_map_t(509952000, 0x47, 0x0C, 2) ) +    ( tvrx2_tda18272_freq_map_t(512000000, 0x46, 0x0C, 2) ) +    ( tvrx2_tda18272_freq_map_t(513024000, 0x45, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(515072000, 0x44, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(517120000, 0x43, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(518144000, 0x42, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(520192000, 0x41, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(521216000, 0x40, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(523264000, 0x3F, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(525312000, 0x3E, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(526336000, 0x3D, 0x0B, 2) ) +    ( tvrx2_tda18272_freq_map_t(528384000, 0x3C, 0x0A, 2) ) +    ( tvrx2_tda18272_freq_map_t(530432000, 0x3B, 0x0A, 2) ) +    ( tvrx2_tda18272_freq_map_t(531456000, 0x3A, 0x0A, 2) ) +    ( tvrx2_tda18272_freq_map_t(533504000, 0x39, 0x0A, 2) ) +    ( tvrx2_tda18272_freq_map_t(534528000, 0x38, 0x0A, 2) ) +    ( tvrx2_tda18272_freq_map_t(536576000, 0x37, 0x0A, 2) ) +    ( tvrx2_tda18272_freq_map_t(537600000, 0x36, 0x09, 2) ) +    ( tvrx2_tda18272_freq_map_t(539648000, 0x35, 0x09, 2) ) +    ( tvrx2_tda18272_freq_map_t(541696000, 0x34, 0x09, 2) ) +    ( tvrx2_tda18272_freq_map_t(543744000, 0x33, 0x09, 2) ) +    ( tvrx2_tda18272_freq_map_t(544768000, 0x32, 0x09, 2) ) +    ( tvrx2_tda18272_freq_map_t(546816000, 0x31, 0x09, 2) ) +    ( tvrx2_tda18272_freq_map_t(548864000, 0x30, 0x08, 2) ) +    ( tvrx2_tda18272_freq_map_t(550912000, 0x2F, 0x08, 2) ) +    ( tvrx2_tda18272_freq_map_t(552960000, 0x2E, 0x08, 2) ) +    ( tvrx2_tda18272_freq_map_t(555008000, 0x2D, 0x08, 2) ) +    ( tvrx2_tda18272_freq_map_t(557056000, 0x2C, 0x08, 2) ) +    ( tvrx2_tda18272_freq_map_t(559104000, 0x2B, 0x08, 2) ) +    ( tvrx2_tda18272_freq_map_t(561152000, 0x2A, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(563200000, 0x29, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(565248000, 0x28, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(567296000, 0x27, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(569344000, 0x26, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(570368000, 0x26, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(571392000, 0x25, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(573440000, 0x24, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(575488000, 0x23, 0x07, 2) ) +    ( tvrx2_tda18272_freq_map_t(577536000, 0x22, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(578560000, 0x21, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(580608000, 0x20, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(583680000, 0x1F, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(585728000, 0x1E, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(587776000, 0x1D, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(589824000, 0x1C, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(592896000, 0x1B, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(594944000, 0x1A, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(596992000, 0x19, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(600064000, 0x18, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(602112000, 0x17, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(604160000, 0x16, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(607232000, 0x15, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(609280000, 0x14, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(612352000, 0x13, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(615424000, 0x12, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(617472000, 0x11, 0x0F, 2) ) +    ( tvrx2_tda18272_freq_map_t(619520000, 0x10, 0x0E, 2) ) +    ( tvrx2_tda18272_freq_map_t(621568000, 0x0F, 0x0E, 2) ) +    ( tvrx2_tda18272_freq_map_t(623616000, 0x0F, 0x0E, 2) ) +    ( tvrx2_tda18272_freq_map_t(624640000, 0xA3, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(625664000, 0xA2, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(626688000, 0xA1, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(627712000, 0xA0, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(628736000, 0x9F, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(630784000, 0x9E, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(631808000, 0x9D, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(632832000, 0x9C, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(633856000, 0x9B, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(635904000, 0x9A, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(636928000, 0x99, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(637952000, 0x98, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(638976000, 0x97, 0x1F, 3) ) +    ( tvrx2_tda18272_freq_map_t(641024000, 0x96, 0x1E, 3) ) +    ( tvrx2_tda18272_freq_map_t(642048000, 0x95, 0x1E, 3) ) +    ( tvrx2_tda18272_freq_map_t(643072000, 0x94, 0x1E, 3) ) +    ( tvrx2_tda18272_freq_map_t(644096000, 0x93, 0x1D, 3) ) +    ( tvrx2_tda18272_freq_map_t(646144000, 0x92, 0x1D, 3) ) +    ( tvrx2_tda18272_freq_map_t(647168000, 0x91, 0x1C, 3) ) +    ( tvrx2_tda18272_freq_map_t(648192000, 0x90, 0x1C, 3) ) +    ( tvrx2_tda18272_freq_map_t(650240000, 0x8F, 0x1B, 3) ) +    ( tvrx2_tda18272_freq_map_t(651264000, 0x8E, 0x1B, 3) ) +    ( tvrx2_tda18272_freq_map_t(652288000, 0x8D, 0x1B, 3) ) +    ( tvrx2_tda18272_freq_map_t(654336000, 0x8C, 0x1B, 3) ) +    ( tvrx2_tda18272_freq_map_t(655360000, 0x8B, 0x1B, 3) ) +    ( tvrx2_tda18272_freq_map_t(656384000, 0x8A, 0x1B, 3) ) +    ( tvrx2_tda18272_freq_map_t(658432000, 0x89, 0x1A, 3) ) +    ( tvrx2_tda18272_freq_map_t(659456000, 0x88, 0x1A, 3) ) +    ( tvrx2_tda18272_freq_map_t(660480000, 0x87, 0x1A, 3) ) +    ( tvrx2_tda18272_freq_map_t(661504000, 0x86, 0x19, 3) ) +    ( tvrx2_tda18272_freq_map_t(662528000, 0x85, 0x19, 3) ) +    ( tvrx2_tda18272_freq_map_t(664576000, 0x84, 0x18, 3) ) +    ( tvrx2_tda18272_freq_map_t(665600000, 0x83, 0x18, 3) ) +    ( tvrx2_tda18272_freq_map_t(666624000, 0x82, 0x18, 3) ) +    ( tvrx2_tda18272_freq_map_t(668672000, 0x81, 0x18, 3) ) +    ( tvrx2_tda18272_freq_map_t(669696000, 0x80, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(671744000, 0x7F, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(672768000, 0x7E, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(674816000, 0x7D, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(675840000, 0x7C, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(676864000, 0x7B, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(678912000, 0x7A, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(679936000, 0x79, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(681984000, 0x78, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(683008000, 0x77, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(685056000, 0x76, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(686080000, 0x75, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(688128000, 0x74, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(689152000, 0x73, 0x17, 3) ) +    ( tvrx2_tda18272_freq_map_t(691200000, 0x72, 0x16, 3) ) +    ( tvrx2_tda18272_freq_map_t(693248000, 0x71, 0x16, 3) ) +    ( tvrx2_tda18272_freq_map_t(694272000, 0x70, 0x16, 3) ) +    ( tvrx2_tda18272_freq_map_t(696320000, 0x6F, 0x15, 3) ) +    ( tvrx2_tda18272_freq_map_t(697344000, 0x6E, 0x15, 3) ) +    ( tvrx2_tda18272_freq_map_t(699392000, 0x6D, 0x15, 3) ) +    ( tvrx2_tda18272_freq_map_t(700416000, 0x6C, 0x15, 3) ) +    ( tvrx2_tda18272_freq_map_t(702464000, 0x6B, 0x14, 3) ) +    ( tvrx2_tda18272_freq_map_t(704512000, 0x6A, 0x14, 3) ) +    ( tvrx2_tda18272_freq_map_t(704512000, 0x69, 0x14, 3) ) +    ( tvrx2_tda18272_freq_map_t(706560000, 0x68, 0x14, 3) ) +    ( tvrx2_tda18272_freq_map_t(707584000, 0x67, 0x13, 3) ) +    ( tvrx2_tda18272_freq_map_t(709632000, 0x66, 0x13, 3) ) +    ( tvrx2_tda18272_freq_map_t(711680000, 0x65, 0x13, 3) ) +    ( tvrx2_tda18272_freq_map_t(712704000, 0x64, 0x13, 3) ) +    ( tvrx2_tda18272_freq_map_t(714752000, 0x63, 0x13, 3) ) +    ( tvrx2_tda18272_freq_map_t(716800000, 0x62, 0x13, 3) ) +    ( tvrx2_tda18272_freq_map_t(717824000, 0x61, 0x13, 3) ) +    ( tvrx2_tda18272_freq_map_t(719872000, 0x60, 0x13, 3) ) +    ( tvrx2_tda18272_freq_map_t(721920000, 0x5F, 0x12, 3) ) +    ( tvrx2_tda18272_freq_map_t(723968000, 0x5E, 0x12, 3) ) +    ( tvrx2_tda18272_freq_map_t(724992000, 0x5D, 0x12, 3) ) +    ( tvrx2_tda18272_freq_map_t(727040000, 0x5C, 0x12, 3) ) +    ( tvrx2_tda18272_freq_map_t(729088000, 0x5B, 0x11, 3) ) +    ( tvrx2_tda18272_freq_map_t(731136000, 0x5A, 0x11, 3) ) +    ( tvrx2_tda18272_freq_map_t(732160000, 0x59, 0x11, 3) ) +    ( tvrx2_tda18272_freq_map_t(734208000, 0x58, 0x11, 3) ) +    ( tvrx2_tda18272_freq_map_t(736256000, 0x57, 0x10, 3) ) +    ( tvrx2_tda18272_freq_map_t(738304000, 0x56, 0x10, 3) ) +    ( tvrx2_tda18272_freq_map_t(740352000, 0x55, 0x10, 3) ) +    ( tvrx2_tda18272_freq_map_t(741376000, 0x54, 0x10, 3) ) +    ( tvrx2_tda18272_freq_map_t(743424000, 0x53, 0x10, 3) ) +    ( tvrx2_tda18272_freq_map_t(745472000, 0x52, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(746496000, 0x51, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(748544000, 0x50, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(750592000, 0x4F, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(752640000, 0x4E, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(753664000, 0x4D, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(755712000, 0x4C, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(757760000, 0x4B, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(759808000, 0x4A, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(761856000, 0x49, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(762880000, 0x49, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(763904000, 0x48, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(765952000, 0x47, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(768000000, 0x46, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(770048000, 0x45, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(772096000, 0x44, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(774144000, 0x43, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(776192000, 0x42, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(778240000, 0x41, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(780288000, 0x40, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(783360000, 0x3F, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(785408000, 0x3E, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(787456000, 0x3D, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(789504000, 0x3C, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(790528000, 0x3B, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(792576000, 0x3A, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(794624000, 0x39, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(797696000, 0x38, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(799744000, 0x37, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(801792000, 0x36, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(803840000, 0x35, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(806912000, 0x34, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(808960000, 0x33, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(809984000, 0x33, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(811008000, 0x32, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(813056000, 0x31, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(816128000, 0x30, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(818176000, 0x2F, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(820224000, 0x2E, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(823296000, 0x2D, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(825344000, 0x2C, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(828416000, 0x2B, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(830464000, 0x2A, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(832512000, 0x29, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(834560000, 0x28, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(836608000, 0x27, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(839680000, 0x26, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(841728000, 0x25, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(844800000, 0x24, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(847872000, 0x23, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(849920000, 0x22, 0x0F, 3) ) +    ( tvrx2_tda18272_freq_map_t(852992000, 0x21, 0x0E, 3) ) +    ( tvrx2_tda18272_freq_map_t(855040000, 0x20, 0x0E, 3) ) +    ( tvrx2_tda18272_freq_map_t(858112000, 0x1F, 0x0E, 3) ) +    ( tvrx2_tda18272_freq_map_t(861184000, 0x1E, 0x0E, 3) ) +    ( tvrx2_tda18272_freq_map_t(863232000, 0x1D, 0x0E, 3) ) +    ( tvrx2_tda18272_freq_map_t(866304000, 0x1C, 0x0E, 3) ) +    ( tvrx2_tda18272_freq_map_t(900096000, 0x10, 0x0C, 3) ) +    ( tvrx2_tda18272_freq_map_t(929792000, 0x07, 0x0B, 3) ) +    ( tvrx2_tda18272_freq_map_t(969728000, 0x00, 0x0A, 3) ) +; + +static const freq_range_t tvrx2_freq_range(42e6, 870e6); + +static const uhd::dict<std::string, std::string> tvrx2_sd_name_to_antennas = map_list_of +    ("RX1", "J100") +    ("RX2", "J140") +; + +static const uhd::dict<std::string, subdev_conn_t> tvrx2_sd_name_to_conn = map_list_of +    ("RX1",  SUBDEV_CONN_REAL_Q) +    ("RX2",  SUBDEV_CONN_REAL_I) +; + +static const uhd::dict<std::string, boost::uint8_t> tvrx2_sd_name_to_i2c_addr = map_list_of +    ("RX1", 0x63) +    ("RX2", 0x60) +; + +static const uhd::dict<std::string, boost::uint8_t> tvrx2_sd_name_to_irq_io = map_list_of +    ("RX1", (RX1_IRQ)) +    ("RX2", (RX2_IRQ)) +; + +static const uhd::dict<std::string, dboard_iface::aux_dac_t> tvrx2_sd_name_to_dac = map_list_of +    ("RX1", dboard_iface::AUX_DAC_A) +    ("RX2", dboard_iface::AUX_DAC_B) +; + +static const uhd::dict<std::string, gain_range_t> tvrx2_gain_ranges = map_list_of +//    ("LNA", gain_range_t(-12, 15, 3)) +//    ("RF_FILTER", gain_range_t(-11, -2, 3)) +//    ("IR_MIXER", gain_range_t(2, 14, 3)) +//    ("LPF", gain_range_t(0, 9, 3)) +    ("IF", gain_range_t(0, 30, 0.5)) +; + +/*********************************************************************** + * The TVRX2 dboard class + **********************************************************************/ +class tvrx2 : public rx_dboard_base{ +public: +    tvrx2(ctor_args_t args); +    ~tvrx2(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _freq_scalar; +    double _lo_freq; +    double _if_freq; +    double _bandwidth; +    uhd::dict<std::string, double> _gains; +    tda18272hnm_regs_t _tda18272hnm_regs; +    uhd::dict<boost::uint32_t, tvrx2_tda18272_rfcal_result_t> _rfcal_results; +    uhd::dict<boost::uint32_t, tvrx2_tda18272_rfcal_coeffs_t> _rfcal_coeffs; + +    bool _enabled; + +    void set_enabled(void); +    void set_disabled(void); + +    void set_lo_freq(double target_freq); +    void set_gain(double gain, const std::string &name); +    void set_bandwidth(double bandwidth); + +    void set_scaled_rf_freq(double rf_freq); +    double get_scaled_rf_freq(void); +    void set_scaled_if_freq(double if_freq); +    double get_scaled_if_freq(void); +    void send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg); +    void read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg); + +    freq_range_t get_tda18272_rfcal_result_freq_range(boost::uint32_t result); +    void tvrx2_tda18272_init_rfcal(void); +    void tvrx2_tda18272_tune_rf_filter(boost::uint32_t uRF); +    void soft_calibration(void); +    void transition_0(void); +    void transition_1(void); +    void transition_2(int rf_freq); +    void transition_3(void); +    void transition_4(int rf_freq); +    void wait_irq(void); +    void test_rf_filter_robustness(void); + +/*********************************************************************** + * The TVRX2 class helper functions + **********************************************************************/ +    /*! +     * Is the IRQ set or cleared? +     * \return true for set +     */ +    bool get_irq(void){ +        read_reg(0x8, 0x8); + +        //return irq status +        bool irq = _tda18272hnm_regs.irq_status == tda18272hnm_regs_t::IRQ_STATUS_SET; + +        UHD_LOGV(often) << boost::format( +            "TVRX2 (%s): IRQ %d" +        ) % (get_subdev_name()) % irq << std::endl; + +        return irq; +    } + +    /*! +     * In Power-On Reset State? +     *      Check POR logic for reset state (causes POR to clear) +     * \return true for reset +     */ +    bool get_power_reset(void){ +        read_reg(0x5, 0x5); + +        //return POR state +        bool por = _tda18272hnm_regs.por == tda18272hnm_regs_t::POR_RESET; + +        UHD_LOGV(often) << boost::format( +            "TVRX2 (%s): POR %d" +        ) % (get_subdev_name()) % int(_tda18272hnm_regs.por) << std::endl; + +        return por; +    } + +    /*! +     * Is the LO locked? +     * \return true for locked +     */ +    bool get_locked(void){ +        read_reg(0x5, 0x5); + +        //return lock detect +        bool locked = _tda18272hnm_regs.lo_lock == tda18272hnm_regs_t::LO_LOCK_LOCKED; + +        UHD_LOGV(often) << boost::format( +            "TVRX2 (%s): locked %d" +        ) % (get_subdev_name()) % locked << std::endl; + +        return locked; +    } + +    /*! +     * Read the RSSI from the registers +     * \return the rssi in dB(m?) FIXME +     */ +    double get_rssi(void){ +        //Launch RSSI calculation with MSM statemachine +        _tda18272hnm_regs.set_reg(0x19, 0x80); //set MSM_byte_1 for rssi calculation +        _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching rssi calculation + +        send_reg(0x19, 0x1A); + +        wait_irq(); + +        //read rssi in dBuV +        read_reg(0x7, 0x7); + +        //calculate the rssi from the voltage +        double rssi_dBuV = 40.0 + double(((110.0 - 40.0)/128.0) * _tda18272hnm_regs.get_reg(0x7)); +        return rssi_dBuV - 107.0; //convert to dBm in 50ohm environment ( -108.8 if 75ohm ) FIXME +    } + +    /*! +     * Read the Temperature from the registers +     * \return the temp in degC +     */ +    double get_temp(void){ +        //Enable Temperature reading +        _tda18272hnm_regs.tm_on = tda18272hnm_regs_t::TM_ON_SENSOR_ON; +        send_reg(0x4, 0x4); + +        //read temp in degC +        read_reg(0x3, 0x3); + +        UHD_LOGV(often) << boost::format( +            "TVRX2 (%s): Temperature %f C" +        ) % (get_subdev_name()) % (double(_tda18272hnm_regs.tm_d)) << std::endl; + +        //Disable Temperature reading +        _tda18272hnm_regs.tm_on = tda18272hnm_regs_t::TM_ON_SENSOR_OFF; +        send_reg(0x4, 0x4); + +        return (double(_tda18272hnm_regs.tm_d)); +    } +}; + +/*********************************************************************** + * Register the TVRX2 dboard + **********************************************************************/ +static dboard_base::sptr make_tvrx2(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new tvrx2(args)); +} + +UHD_STATIC_BLOCK(reg_tvrx2_dboard){ +    //register the factory function for the rx dbid +    dboard_manager::register_dboard(0x0046, &make_tvrx2, "TVRX2", tvrx2_sd_name_to_conn.keys()); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ +    //FIXME for USRP1, we can only support one TVRX2 installed + +    _rfcal_results = map_list_of +        (  0, tvrx2_tda18272_rfcal_result_t() ) +        (  1, tvrx2_tda18272_rfcal_result_t() ) +        (  2, tvrx2_tda18272_rfcal_result_t() ) +        (  3, tvrx2_tda18272_rfcal_result_t() ) +        (  4, tvrx2_tda18272_rfcal_result_t() ) +        (  5, tvrx2_tda18272_rfcal_result_t() ) +        (  6, tvrx2_tda18272_rfcal_result_t() ) +        (  7, tvrx2_tda18272_rfcal_result_t() ) +        (  8, tvrx2_tda18272_rfcal_result_t() ) +        (  9, tvrx2_tda18272_rfcal_result_t() ) +        ( 10, tvrx2_tda18272_rfcal_result_t() ) +        ( 11, tvrx2_tda18272_rfcal_result_t() ) +    ; + +    _rfcal_coeffs = map_list_of +        ( 0, tvrx2_tda18272_rfcal_coeffs_t(0) ) +        ( 1, tvrx2_tda18272_rfcal_coeffs_t(1) ) +        ( 2, tvrx2_tda18272_rfcal_coeffs_t(3) ) +        ( 3, tvrx2_tda18272_rfcal_coeffs_t(4) ) +        ( 4, tvrx2_tda18272_rfcal_coeffs_t(6) ) +        ( 5, tvrx2_tda18272_rfcal_coeffs_t(7) ) +        ( 6, tvrx2_tda18272_rfcal_coeffs_t(9) ) +        ( 7, tvrx2_tda18272_rfcal_coeffs_t(10) ) +    ; + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0); // All unused in atr +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, OUTPUT_MASK); // Set outputs + +    double ref_clock=0.0; + +    //configure ref_clock + +    /* +    std::vector<double> clock_rates = this->get_iface()->get_clock_rates(dboard_iface::UNIT_RX); +    BOOST_FOREACH(ref_clock, uhd::sorted(clock_rates)){ +        if (ref_clock < 16.0e6) continue;  +        if (ref_clock >= 16.0e6) break;  +    } +    this->get_iface()->set_clock_rate(dboard_iface::UNIT_RX, ref_clock); +    */ + +    ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); + +    if (ref_clock == 64.0e6) { +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV4); + +        UHD_LOGV(often) << boost::format( +            "TVRX2 (%s): Dividing Refclock by 4" +        ) % (get_subdev_name()) << std::endl; + +        _freq_scalar = (4*16.0e6)/(this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)); +    } else if (ref_clock == 100e6) { +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV6); + +        UHD_LOGV(often) << boost::format( +            "TVRX2 (%s): Dividing Refclock by 6" +        ) % (get_subdev_name()) << std::endl; + +        _freq_scalar = (6*16.0e6)/this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); +    } else { +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV6); +        UHD_MSG(warning) << boost::format("Unsupported ref_clock %0.2f, valid options 64e6 and 100e6") % ref_clock << std::endl; +        _freq_scalar = 1.0; +    } + +    //enable only the clocks we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    UHD_LOGV(often) << boost::format( +        "TVRX2 (%s): Refclock %f Hz, scalar = %f" +    ) % (get_subdev_name()) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % _freq_scalar << std::endl; + +    //set defaults for LO, gains, and filter bandwidth +    _bandwidth = 10e6; + +    _if_freq = 12.5e6; + +    _lo_freq = tvrx2_freq_range.start(); + +    _enabled = false; + +    //send initial register settings +    //this->read_reg(0x0, 0x43); +    //this->send_reg(0x0, 0x43); + +    //send magic xtal_cal_dac setting +    send_reg(0x65, 0x65); + +    _tda18272hnm_regs.irq_polarity = tda18272hnm_regs_t::IRQ_POLARITY_RAISED_VCC; +    _tda18272hnm_regs.irq_clear = tda18272hnm_regs_t::IRQ_CLEAR_TRUE; +    send_reg(0x37, 0x37); +    send_reg(0xA, 0xA); + +    send_reg(0x31, 0x31); //N_CP_Current +    send_reg(0x36, 0x36); //RSSI_Clock +    send_reg(0x24, 0x25); //AGC1_Do_step +    send_reg(0x2C, 0x2C); //AGC1_Do_step +    send_reg(0x2E, 0x2E); //AGC2_Do_step +    send_reg(0x0E, 0x0E); //AGCs_Up_step_assym +    send_reg(0x11, 0x11); //AGCs_Do_step_assym + +    //intialize i2c +    //soft_calibration(); +    //tvrx2_tda18272_init_rfcal(); +    transition_0(); +} + +void tvrx2::set_enabled(void){ +    //setup tuner parameters +    transition_1(); + +    transition_2(int(tvrx2_freq_range.start())); + +    test_rf_filter_robustness(); + +    BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){ +        set_gain(tvrx2_gain_ranges[name].start(), name); +    } + +    set_bandwidth(_bandwidth); // default bandwidth from datasheet + +    //transition_2 equivalent +    set_lo_freq(tvrx2_freq_range.start()); + +    //enter standby mode +    transition_3(); +    _enabled = true; +} + +tvrx2::~tvrx2(void){ +    UHD_LOGV(often) << boost::format( +        "TVRX2 (%s): Called Destructor" +    ) % (get_subdev_name()) << std::endl; +    UHD_SAFE_CALL(if (_enabled) set_disabled();) +} + +void tvrx2::set_disabled(void){ +    //enter standby mode +    transition_3(); +    _enabled = false; +} + + +/*********************************************************************** + * TDA18272 Register IO Functions + **********************************************************************/ +void tvrx2::set_scaled_rf_freq(double rf_freq){ +    _tda18272hnm_regs.set_rf_freq(_freq_scalar*rf_freq/1e3); +} + +double tvrx2::get_scaled_rf_freq(void){ +    return _tda18272hnm_regs.get_rf_freq()*1e3/_freq_scalar; +} + +void tvrx2::set_scaled_if_freq(double if_freq){ +    _tda18272hnm_regs.if_freq = int(_freq_scalar*if_freq/(50e3)); //max 12.8MHz?? +} + +double tvrx2::get_scaled_if_freq(void){ +    return _tda18272hnm_regs.if_freq*50e3/_freq_scalar; +} + +void tvrx2::send_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +    start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x43)); +    stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x43)); + +    for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t) - 1){ +        int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) - 1 ? sizeof(boost::uint32_t) - 1 : stop_reg - start_addr + 1; + +        //create buffer for register data (+1 for start address) +        byte_vector_t regs_vector(num_bytes + 1); + +        //first byte is the address of first register +        regs_vector[0] = start_addr; + +        //get the register data +        for(int i=0; i<num_bytes; i++){ +            regs_vector[1+i] = _tda18272hnm_regs.get_reg(start_addr+i); +            UHD_LOGV(often) << boost::format( +                "TVRX2 (%s, 0x%02x): send reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" +            ) % (get_subdev_name()) % int(tvrx2_sd_name_to_i2c_addr[get_subdev_name()]) % int(start_addr+i) % int(regs_vector[1+i]) % int(start_addr) % num_bytes << std::endl; +        } + +        //send the data +        this->get_iface()->write_i2c( +            tvrx2_sd_name_to_i2c_addr[get_subdev_name()], regs_vector +        ); +    } +} + +void tvrx2::read_reg(boost::uint8_t start_reg, boost::uint8_t stop_reg){ +    static const boost::uint8_t status_addr = 0x0; +    start_reg = boost::uint8_t(uhd::clip(int(start_reg), 0x0, 0x43)); +    stop_reg = boost::uint8_t(uhd::clip(int(stop_reg), 0x0, 0x43)); + +    for(boost::uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(boost::uint32_t)){ +        int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(boost::uint32_t)) ? sizeof(boost::uint32_t) : stop_reg - start_addr + 1; + +        //create buffer for starting address +        byte_vector_t start_address_vector(1); + +        //first byte is the address of first register +        start_address_vector[0] = start_addr; + +        //send the start address +        this->get_iface()->write_i2c( +            tvrx2_sd_name_to_i2c_addr[get_subdev_name()], start_address_vector +        ); + +        //create buffer for register data +        byte_vector_t regs_vector(num_bytes); + +        //read from i2c +        regs_vector = this->get_iface()->read_i2c( +            tvrx2_sd_name_to_i2c_addr[get_subdev_name()], num_bytes +        ); + +        for(boost::uint8_t i=0; i < num_bytes; i++){ +            if (i + start_addr >= status_addr){ +                _tda18272hnm_regs.set_reg(i + start_addr, regs_vector[i]); +            } +            UHD_LOGV(often) << boost::format( +                "TVRX2 (%s, 0x%02x): read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" +            ) % (get_subdev_name()) % int(tvrx2_sd_name_to_i2c_addr[get_subdev_name()]) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes << std::endl; +        } +    } +} + + +/*********************************************************************** + * TDA18272 Calibration Functions + **********************************************************************/ +freq_range_t tvrx2::get_tda18272_rfcal_result_freq_range(boost::uint32_t result) +{ + +    uhd::dict<boost::uint32_t, freq_range_t> result_to_cal_freq_ranges_map = map_list_of +        ( 0, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[0].cal_freq[_tda18272hnm_regs.rfcal_freq0] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[1].cal_freq[_tda18272hnm_regs.rfcal_freq1] * _freq_scalar +                 ) ) +        ( 1, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[1].cal_freq[_tda18272hnm_regs.rfcal_freq1] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[2].cal_freq[_tda18272hnm_regs.rfcal_freq2] * _freq_scalar +                 ) ) +        ( 2, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[2].cal_freq[_tda18272hnm_regs.rfcal_freq2] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[3].cal_freq[_tda18272hnm_regs.rfcal_freq3] * _freq_scalar +                 ) ) +        ( 3, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[3].cal_freq[_tda18272hnm_regs.rfcal_freq3] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[4].cal_freq[_tda18272hnm_regs.rfcal_freq4] * _freq_scalar +                 ) ) +        ( 4, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[4].cal_freq[_tda18272hnm_regs.rfcal_freq4] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[5].cal_freq[_tda18272hnm_regs.rfcal_freq5] * _freq_scalar +                 ) ) +        ( 5, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[5].cal_freq[_tda18272hnm_regs.rfcal_freq5] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[6].cal_freq[_tda18272hnm_regs.rfcal_freq6] * _freq_scalar +                 ) ) +        ( 6, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[6].cal_freq[_tda18272hnm_regs.rfcal_freq6] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[7].cal_freq[_tda18272hnm_regs.rfcal_freq7] * _freq_scalar +                 ) ) +        ( 7, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[7].cal_freq[_tda18272hnm_regs.rfcal_freq7] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[8].cal_freq[_tda18272hnm_regs.rfcal_freq8] * _freq_scalar +                 ) ) +        ( 8, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[8].cal_freq[_tda18272hnm_regs.rfcal_freq8] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[9].cal_freq[_tda18272hnm_regs.rfcal_freq9] * _freq_scalar +                 ) ) +        ( 9, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[9].cal_freq[_tda18272hnm_regs.rfcal_freq9] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[10].cal_freq[_tda18272hnm_regs.rfcal_freq10] * _freq_scalar +                 ) ) +        (10, freq_range_t( +                 (double) tvrx2_tda18272_cal_map[10].cal_freq[_tda18272hnm_regs.rfcal_freq10] * _freq_scalar, +                 (double) tvrx2_tda18272_cal_map[11].cal_freq[_tda18272hnm_regs.rfcal_freq11] * _freq_scalar +                 ) ) +    ; + +    if (result < 11) +        return result_to_cal_freq_ranges_map[result];  +    else +        return freq_range_t(0.0, 0.0); +} + + +/* + * Initialize the RF Filter calibration maps after hardware init + */ +void tvrx2::tvrx2_tda18272_init_rfcal(void) +{ + +    /* read byte 0x38-0x43 */ +    read_reg(0x38, 0x43); + +    uhd::dict<boost::uint32_t, boost::uint8_t> result_to_cal_regs = map_list_of +        ( 0, _tda18272hnm_regs.rfcal_log_1) +        ( 1, _tda18272hnm_regs.rfcal_log_2) +        ( 2, _tda18272hnm_regs.rfcal_log_3) +        ( 3, _tda18272hnm_regs.rfcal_log_4) +        ( 4, _tda18272hnm_regs.rfcal_log_5) +        ( 5, _tda18272hnm_regs.rfcal_log_6) +        ( 6, _tda18272hnm_regs.rfcal_log_7) +        ( 7, _tda18272hnm_regs.rfcal_log_8) +        ( 8, _tda18272hnm_regs.rfcal_log_9) +        ( 9, _tda18272hnm_regs.rfcal_log_10) +        (10, _tda18272hnm_regs.rfcal_log_11) +        (11, _tda18272hnm_regs.rfcal_log_12) +    ; + + +    // Loop through rfcal_log_* registers, initialize _rfcal_results +    BOOST_FOREACH(const boost::uint32_t &result, result_to_cal_regs.keys()) +        _rfcal_results[result].delta_c = result_to_cal_regs[result] > 63 ? result_to_cal_regs[result] - 128 : result_to_cal_regs[result]; + +    /* read byte 0x26-0x2B */ +    read_reg(0x26, 0x2B); + +    // Loop through rfcal_byte_* registers, initialize _rfcal_coeffs +    BOOST_FOREACH(const boost::uint32_t &subband, _rfcal_coeffs.keys()) +    { +        freq_range_t subband_freqs; + +        boost::uint32_t result = _rfcal_coeffs[subband].cal_number; + +        subband_freqs = get_tda18272_rfcal_result_freq_range(result); + +        _rfcal_coeffs[subband].RF_B1 = _rfcal_results[result].delta_c + tvrx2_tda18272_cal_map[result].c_offset[_rfcal_results[result].c_offset]; + +        boost::uint32_t quotient = (((_rfcal_results[result+1].delta_c + tvrx2_tda18272_cal_map[result+1].c_offset[_rfcal_results[result].c_offset]) +                                        - (_rfcal_results[result].delta_c + tvrx2_tda18272_cal_map[result].c_offset[_rfcal_results[result].c_offset])) * 1000000); + +        boost::uint32_t divisor = ((boost::int32_t)(subband_freqs.stop() - subband_freqs.start())/1000); + +        _rfcal_coeffs[subband].RF_A1 = quotient / divisor; + +    } + +} + +/* + * Apply calibration coefficients to RF Filter tuning + */ +void tvrx2::tvrx2_tda18272_tune_rf_filter(boost::uint32_t uRF) +{ +    boost::uint32_t                  uCounter = 0; +    boost::uint8_t                   cal_result = 0; +    boost::uint32_t                  uRFCal0 = 0; +    boost::uint32_t                  uRFCal1 = 0; +    boost::uint8_t                   subband = 0; +    boost::int32_t                   cProg = 0; +    boost::uint8_t                   gain_taper = 0; +    boost::uint8_t                   RFBand = 0; +    boost::int32_t                   RF_A1 = 0; +    boost::int32_t                   RF_B1 = 0; +    freq_range_t                     subband_freqs; + +    /* read byte 0x26-0x2B */ +    read_reg(0x26, 0x2B); + +    subband_freqs = get_tda18272_rfcal_result_freq_range(1); +    uRFCal0 = subband_freqs.start(); +    subband_freqs = get_tda18272_rfcal_result_freq_range(4); +    uRFCal1 = subband_freqs.start(); + +    if(uRF < uRFCal0) +        subband = 0; +    else if(uRF < 145700000) +        subband = 1; +    else if(uRF < uRFCal1) +        subband = 2; +    else if(uRF < 367400000) +        subband = 3; +    else +    { +        subband_freqs = get_tda18272_rfcal_result_freq_range(7); +        uRFCal0 = subband_freqs.start(); +        subband_freqs = get_tda18272_rfcal_result_freq_range(10); +        uRFCal1 = subband_freqs.start(); + +        if(uRF < uRFCal0) +            subband = 4; +        else if(uRF < 625000000) +            subband = 5; +        else if(uRF < uRFCal1) +            subband = 6; +        else +            subband = 7; +    } + +    cal_result = _rfcal_coeffs[subband].cal_number; +    subband_freqs = get_tda18272_rfcal_result_freq_range(cal_result); +    uRFCal0 = subband_freqs.start(); + +    RF_A1 = _rfcal_coeffs[subband].RF_A1; +    RF_B1 = _rfcal_coeffs[subband].RF_B1; + +    uCounter = 0; +    do uCounter ++; +    while (uRF >= tvrx2_tda18272_freq_map[uCounter].rf_max && uCounter < TVRX2_TDA18272_FREQ_MAP_ENTRIES); + +    cProg = tvrx2_tda18272_freq_map[uCounter - 1].c_prog; +    gain_taper = tvrx2_tda18272_freq_map[uCounter - 1].gain_taper; +    RFBand = tvrx2_tda18272_freq_map[uCounter - 1].rf_band; + +    cProg = (boost::int32_t)(cProg + RF_B1 + (RF_A1*((boost::int32_t)(uRF - uRFCal0)/1000))/1000000); + +    if(cProg>255)   cProg = 255; +    if(cProg<0)     cProg = 0; + +    _tda18272hnm_regs.rf_filter_bypass = 1; +    _tda18272hnm_regs.rf_filter_cap = (boost::uint8_t) cProg; +    _tda18272hnm_regs.gain_taper = gain_taper; +    _tda18272hnm_regs.rf_filter_band = RFBand; + +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Software Calibration:\n" +        "\tRF Filter Bypass = %d\n" +        "\tRF Filter Cap    = %d\n" +        "\tRF Filter Band   = %d\n" +        "\tGain Taper       = %d\n")  +        % (get_subdev_name()) +        % int(_tda18272hnm_regs.rf_filter_bypass) +        % int(_tda18272hnm_regs.rf_filter_cap) +        % int(_tda18272hnm_regs.rf_filter_band)  +        % int(_tda18272hnm_regs.gain_taper) +        << std::endl; + +    send_reg(0x2c, 0x2f); +} + +void tvrx2::soft_calibration(void){ +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Software Calibration: Initialize Tuner, Calibrate and Standby\n") % (get_subdev_name()) << std::endl; + +    _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL; +    _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON; +    _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON; + +    send_reg(0x6, 0x6); +    read_reg(0x6, 0x6); + +    read_reg(0x19, 0x1A); +    read_reg(0x26, 0x2B); + +    _tda18272hnm_regs.rfcal_freq0  = 0x2; +    _tda18272hnm_regs.rfcal_freq1  = 0x2; +    _tda18272hnm_regs.rfcal_freq2  = 0x2; +    _tda18272hnm_regs.rfcal_freq3  = 0x2; +    _tda18272hnm_regs.rfcal_freq4  = 0x2; +    _tda18272hnm_regs.rfcal_freq5  = 0x2; +    _tda18272hnm_regs.rfcal_freq6  = 0x2; +    _tda18272hnm_regs.rfcal_freq7  = 0x2; +    _tda18272hnm_regs.rfcal_freq8  = 0x2; +    _tda18272hnm_regs.rfcal_freq9  = 0x2; +    _tda18272hnm_regs.rfcal_freq10 = 0x2; +    _tda18272hnm_regs.rfcal_freq11 = 0x2; + +    send_reg(0x26, 0x2B); + +    _tda18272hnm_regs.set_reg(0x19, 0x3B); //set MSM_byte_1 for calibration per datasheet +    _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration + +    send_reg(0x19, 0x1A); + +    wait_irq(); + +    send_reg(0x1D, 0x1D); //Fmax_LO +    send_reg(0x0C, 0x0C); //LT_Enable +    send_reg(0x1B, 0x1B); //PSM_AGC1 +    send_reg(0x0C, 0x0C); //AGC1_6_15dB + +    //set spread spectrum for clock  +    //FIXME: NXP turns clock spread on and off  +    //   based on where clock spurs would be relative to RF frequency +    //   we should do this also +    _tda18272hnm_regs.digital_clock = tda18272hnm_regs_t::DIGITAL_CLOCK_SPREAD_OFF; +    if (get_subdev_name() == "RX1") +        //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO; +        _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ; +    else +        //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO; +        _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ; +     +    send_reg(0x14, 0x14); + +    _tda18272hnm_regs.set_reg(0x36, 0x0E); //sets clock mode +    send_reg(0x36, 0x36); + +    //go to standby mode +    _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY; +    send_reg(0x6, 0x6); +} + +void tvrx2::test_rf_filter_robustness(void){ +    typedef uhd::dict<std::string, std::string> tvrx2_filter_ratings_t; +    typedef uhd::dict<std::string, double> tvrx2_filter_margins_t; + +    tvrx2_filter_margins_t _filter_margins; +    tvrx2_filter_ratings_t _filter_ratings; + +    read_reg(0x38, 0x43); + +    uhd::dict<std::string, boost::uint8_t> filter_cal_regs = map_list_of +        ("VHFLow_0", 0x38) +        ("VHFLow_1", 0x3a) +        ("VHFHigh_0", 0x3b) +        ("VHFHigh_1", 0x3d) +        ("UHFLow_0", 0x3e) +        ("UHFLow_1", 0x40) +        ("UHFHigh_0", 0x41) +        ("UHFHigh_1", 0x43) +    ; + +    BOOST_FOREACH(const std::string &name, filter_cal_regs.keys()){ +        boost::uint8_t cal_result = _tda18272hnm_regs.get_reg(filter_cal_regs[name]); +        if (cal_result & 0x80) { +            _filter_ratings.set(name, "E"); +            _filter_margins.set(name, 0.0); +        } +        else { +            double partial; + +            if (name == "VHFLow_0") +                partial = 100 * (45 - 39.8225 * (1 + (0.31 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / 45.0; + +            else if (name == "VHFLow_1") +                partial = 100 * (152.1828 * (1 + (1.53 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (144.896 - 6)) / (144.896 - 6);  + +            else if (name == "VHFHigh_0") +                partial = 100 * ((144.896 + 6) - 135.4063 * (1 + (0.27 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (144.896 + 6); + +            else if (name == "VHFHigh_1") +                partial = 100 * (383.1455 * (1 + (0.91 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (367.104 - 8)) / (367.104 - 8); + +            else if (name == "UHFLow_0") +                partial = 100 * ((367.104 + 8) - 342.6224 * (1 + (0.21 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (367.104 + 8); + +            else if (name == "UHFLow_1") +                partial = 100 * (662.5595 * (1 + (0.33 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (624.128 - 2)) / (624.128 - 2); + +            else if (name == "UHFHigh_0") +                partial = 100 * ((624.128 + 2) - 508.2747 * (1 + (0.23 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0)) / (624.128 + 2); + +            else if (name == "UHFHigh_1") +                partial = 100 * (947.8913 * (1 + (0.3 * (cal_result < 64 ? cal_result : cal_result - 128)) / 1.0 / 100.0) - (866 - 14)) / (866 - 14); + +            else +                UHD_THROW_INVALID_CODE_PATH(); + +            _filter_margins.set(name, 0.0024 * partial * partial * partial - 0.101 * partial * partial + 1.629 * partial + 1.8266); +            _filter_ratings.set(name, _filter_margins[name] >= 0.0 ? "H" : "L"); +        } +    } + +    std::stringstream robustness_message; +    robustness_message << boost::format("TVRX2 (%s): RF Filter Robustness Results:") % (get_subdev_name()) << std::endl; +    BOOST_FOREACH(const std::string &name, uhd::sorted(_filter_ratings.keys())){ +        robustness_message << boost::format("\t%s:\tMargin = %0.2f,\tRobustness = %c") % name % (_filter_margins[name]) % (_filter_ratings[name]) << std::endl; +    } + +    UHD_LOGV(often) << robustness_message.str(); +} + +/*********************************************************************** + * TDA18272 State Functions + **********************************************************************/ +void tvrx2::transition_0(void){ +    //Transition 0: Initialize Tuner and place in standby +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Transistion 0: Initialize Tuner, Calibrate and Standby\n") % (get_subdev_name()) << std::endl; + +    //Check for Power-On Reset, if reset, initialze tuner +    if (get_power_reset()) { +        _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL; +        _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON; +        _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON; + +        send_reg(0x6, 0x6); +        read_reg(0x6, 0x6); + +        read_reg(0x19, 0x1A); + +        _tda18272hnm_regs.set_reg(0x19, 0x3B); //set MSM_byte_1 for calibration per datasheet +        _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration + +        send_reg(0x19, 0x1A); + +        wait_irq(); +    } + +    //send magic xtal_cal_dac setting +    send_reg(0x65, 0x65); + +    send_reg(0x1D, 0x1D); //Fmax_LO +    send_reg(0x0C, 0x0C); //LT_Enable +    send_reg(0x1B, 0x1B); //PSM_AGC1 +    send_reg(0x0C, 0x0C); //AGC1_6_15dB + +    //set spread spectrum for clock  +    //FIXME: NXP turns clock spread on and off  +    //   based on where clock spurs would be relative to RF frequency +    //   we should do this also +    _tda18272hnm_regs.digital_clock = tda18272hnm_regs_t::DIGITAL_CLOCK_SPREAD_OFF; +    if (get_subdev_name() == "RX1") +        //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO; +        _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ; +    else +        //_tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_NO; +        _tda18272hnm_regs.xtout = tda18272hnm_regs_t::XTOUT_16MHZ; +     +    send_reg(0x14, 0x14); + +    _tda18272hnm_regs.set_reg(0x36, 0x0E); //sets clock mode +    send_reg(0x36, 0x36); + +    //go to standby mode +    _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY; +    send_reg(0x6, 0x6); +} + +void tvrx2::transition_1(void){ +    //Transition 1: Select TV Standard +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Transistion 1: Select TV Standard\n") % (get_subdev_name()) << std::endl; + +    //send magic xtal_cal_dac setting +    send_reg(0x65, 0x65); + +    //Choose IF Byte 1 Setting +    //_tda18272hnm_regs.if_hp_fc = tda18272hnm_regs_t::IF_HP_FC_0_4MHZ; +    //_tda18272hnm_regs.if_notch = tda18272hnm_regs_t::IF_NOTCH_OFF; +    //_tda18272hnm_regs.lp_fc_offset = tda18272hnm_regs_t::LP_FC_OFFSET_0_PERCENT; +    //_tda18272hnm_regs.lp_fc = tda18272hnm_regs_t::LP_FC_10_0MHZ; +    //send_reg(0x13, 0x13); + +    //Choose IR Mixer Byte 2 Setting +    //_tda18272hnm_regs.hi_pass = tda18272hnm_regs_t::HI_PASS_DISABLE; +    //_tda18272hnm_regs.dc_notch = tda18272hnm_regs_t::DC_NOTCH_OFF; +    send_reg(0x23, 0x23); + +    //Set AGC TOP Bytes +    send_reg(0x0C, 0x13); + +    //Set PSM Byt1 +    send_reg(0x1B, 0x1B); + +    //Choose IF Frequency, setting is 50KHz steps +    set_scaled_if_freq(_if_freq); +    send_reg(0x15, 0x15); +} + +void tvrx2::transition_2(int rf_freq){ +    //Transition 2: Select RF Frequency after changing TV Standard +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Transistion 2: Select RF Frequency after changing TV Standard\n") % (get_subdev_name()) << std::endl; + +    //send magic xtal_cal_dac setting +    send_reg(0x65, 0x65); + +    //Wake up from Standby +    _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_NORMAL; +    _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_ON; +    _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_ON; +     +    send_reg(0x6, 0x6); +     +    //Set Clock Mode +    _tda18272hnm_regs.set_reg(0x36, 0x00); +    send_reg(0x36, 0x36); +     +    //Set desired RF Frequency +    set_scaled_rf_freq(rf_freq); +    send_reg(0x16, 0x18); +     +    //Lock PLL and tune RF Filters +    _tda18272hnm_regs.set_reg(0x19, 0x41); //set MSM_byte_1 for RF Filters Tuning, PLL Locking +    _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration +     +    send_reg(0x19, 0x1A); +     +    wait_irq(); + +    tvrx2_tda18272_tune_rf_filter(rf_freq); + +    ////LO Lock state in Reg 0x5 LSB +    //read_reg(0x6, 0x6); + +} + +void tvrx2::transition_3(void){ +    //Transition 3: Standby Mode +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Transistion 3: Standby Mode\n") % (get_subdev_name()) << std::endl; + +    //send magic xtal_cal_dac setting +    send_reg(0x65, 0x65); + +    //Set clock mode +    _tda18272hnm_regs.set_reg(0x36, 0x0E); +    send_reg(0x36, 0x36); + +    //go to standby mode +    _tda18272hnm_regs.sm = tda18272hnm_regs_t::SM_STANDBY; +    _tda18272hnm_regs.sm_lna = tda18272hnm_regs_t::SM_LNA_OFF; +    _tda18272hnm_regs.sm_pll = tda18272hnm_regs_t::SM_PLL_OFF; +    send_reg(0x6, 0x6); +} + +void tvrx2::transition_4(int rf_freq){ +    //Transition 4: Change RF Frequency without changing TV Standard +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Transistion 4: Change RF Frequency without changing TV Standard\n") % (get_subdev_name()) << std::endl; + +    //send magic xtal_cal_dac setting +    send_reg(0x65, 0x65); + +    //Set desired RF Frequency +    set_scaled_rf_freq(rf_freq); +    send_reg(0x16, 0x18); +     +    //Lock PLL and tune RF Filters +    _tda18272hnm_regs.set_reg(0x19, 0x41); //set MSM_byte_1 for RF Filters Tuning, PLL Locking +    _tda18272hnm_regs.set_reg(0x1A, 0x01); //set MSM_byte_2 for launching calibration +     +    send_reg(0x19, 0x1A); +     +    wait_irq(); + +    tvrx2_tda18272_tune_rf_filter(rf_freq); + +    ////LO Lock state in Reg 0x5 LSB +    //read_reg(0x5, 0x6); + +} + +void tvrx2::wait_irq(void){ +    int timeout = 20; //irq waiting timeout in milliseconds +    //int irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & int(tvrx2_sd_name_to_irq_io[get_subdev_name()])); +    bool irq = get_irq(); +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Waiting on IRQ, subdev = %d, mask = 0x%x, Status: 0x%x\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq << std::endl; + +    while (not irq and timeout > 0) { +        //irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & tvrx2_sd_name_to_irq_io[get_subdev_name()]); +        irq = get_irq(); +        boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +        timeout -= 1; +    } + +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): IRQ Raised, subdev = %d, mask = 0x%x, Status: 0x%x, Timeout: %d\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq % timeout << std::endl; + +    read_reg(0xA, 0xB); +    //UHD_ASSERT_THROW(timeout > 0); +    if(timeout <= 0) UHD_MSG(warning) << boost::format( +        "\nTVRX2 (%s): Timeout waiting on IRQ\n") % (get_subdev_name()) << std::endl; + +    _tda18272hnm_regs.irq_clear = tda18272hnm_regs_t::IRQ_CLEAR_TRUE; +    send_reg(0xA, 0xA); +    read_reg(0xA, 0xB); + +    irq = (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & tvrx2_sd_name_to_irq_io[get_subdev_name()]); + +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): Cleared IRQ, subdev = %d, mask = 0x%x, Status: 0x%x\n") % (get_subdev_name()) % get_subdev_name() % (int(tvrx2_sd_name_to_irq_io[get_subdev_name()])) % irq << std::endl; +} + + + +/*********************************************************************** + * Tuning + **********************************************************************/ +void tvrx2::set_lo_freq(double target_freq){ +    //target_freq = std::clip(target_freq, tvrx2_freq_range.min, tvrx2_freq_range.max); + +    read_reg(0x6, 0x6); + +    if (_tda18272hnm_regs.sm == tda18272hnm_regs_t::SM_STANDBY) { +        transition_2(int(target_freq + _bandwidth/2 - get_scaled_if_freq())); +    } else { +        transition_4(int(target_freq + _bandwidth/2 - get_scaled_if_freq())); +    } +    read_reg(0x16, 0x18); + +    //compute actual tuned frequency +    _lo_freq = get_scaled_rf_freq() + get_scaled_if_freq(); // - _bandwidth/2; + +    //debug output of calculated variables +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): LO Frequency\n" +        "\tRequested: \t%f\n" +        "\tComputed: \t%f\n" +        "\tReadback: \t%f\n" +        "\tIF Frequency: \t%f\n") % (get_subdev_name()) % target_freq % double(int(target_freq/1e3)*1e3) % get_scaled_rf_freq() % get_scaled_if_freq() << std::endl; + +    get_locked(); + +    test_rf_filter_robustness(); + +    UHD_LOGV(often) << boost::format( +        "\nTVRX2 (%s): RSSI = %f dBm\n" +    ) % (get_subdev_name()) % (get_rssi()) << std::endl; +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/* + * Convert the requested gain into a dac voltage + */ +static double gain_to_if_gain_dac(double &gain){ +    //clip the input +    gain = tvrx2_gain_ranges["IF"].clip(gain); + +    //voltage level constants +    static const double max_volts = double(1.7), min_volts = double(0.5); +    static const double slope = (max_volts-min_volts)/tvrx2_gain_ranges["IF"].stop(); + +    //calculate the voltage for the aux dac +    double dac_volts = gain*slope + min_volts; + +    UHD_LOGV(often) << boost::format( +        "TVRX2 IF Gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + +void tvrx2::set_gain(double gain, const std::string &name){ +    assert_has(tvrx2_gain_ranges.keys(), name, "tvrx2 gain name"); + +    if (name == "IF"){ +        //write voltage to aux_dac +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, tvrx2_sd_name_to_dac[get_subdev_name()], gain_to_if_gain_dac(gain)); +    } +    else UHD_THROW_INVALID_CODE_PATH(); + +    //shadow gain setting +    _gains[name] = gain; +} + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +static tda18272hnm_regs_t::lp_fc_t bandwidth_to_lp_fc_reg(double &bandwidth){ +    int reg = uhd::clip(boost::math::iround((bandwidth-5.0e6)/1.0e6), 0, 4); + +    switch(reg){ +    case 0: +        bandwidth = 1.7e6; +        return tda18272hnm_regs_t::LP_FC_1_7MHZ; +    case 1: +        bandwidth = 6e6; +        return tda18272hnm_regs_t::LP_FC_6_0MHZ; +    case 2: +        bandwidth = 7e6; +        return tda18272hnm_regs_t::LP_FC_7_0MHZ; +    case 3: +        bandwidth = 8e6; +        return tda18272hnm_regs_t::LP_FC_8_0MHZ; +    case 4: +        bandwidth = 10e6; +        return tda18272hnm_regs_t::LP_FC_10_0MHZ; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +void tvrx2::set_bandwidth(double bandwidth){ +    //compute low pass cutoff frequency setting +    _tda18272hnm_regs.lp_fc = bandwidth_to_lp_fc_reg(bandwidth); + +    //shadow bandwidth setting +    _bandwidth = bandwidth; + +    //update register +    send_reg(0x13, 0x13); + +    UHD_LOGV(often) << boost::format( +        "TVRX2 (%s) Bandwidth (lp_fc): %f Hz, reg: %d" +    ) % (get_subdev_name()) % _bandwidth % (int(_tda18272hnm_regs.lp_fc)) << std::endl; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void tvrx2::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_gains.keys(), key.name, "tvrx2 gain name"); +        val = _gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(tvrx2_gain_ranges.keys(), key.name, "tvrx2 gain name"); +        val = tvrx2_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(tvrx2_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = tvrx2_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = tvrx2_sd_name_to_antennas[get_subdev_name()]; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, tvrx2_sd_name_to_antennas[get_subdev_name()]); +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = tvrx2_sd_name_to_conn[get_subdev_name()]; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = _enabled; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        if (key.name == "lo_locked") +            val = sensor_value_t("LO", this->get_locked(), "locked", "unlocked"); +        else if (key.name == "rssi") +            val = sensor_value_t("RSSI", this->get_rssi(), "dBm"); +        else if (key.name == "temperature") +            val = sensor_value_t("TEMP", this->get_temp(), "degC"); +        else +            UHD_THROW_INVALID_CODE_PATH(); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES:{ +            prop_names_t names = list_of("lo_locked")("rssi")("temperature"); +            val = names; +        } +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = _bandwidth; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void tvrx2::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_gain( val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        return; + +    case SUBDEV_PROP_ENABLED: +        if ((val.as<bool>())) this->set_enabled(); +        else if (not (val.as<bool>())) this->set_disabled(); + +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_bandwidth(val.as<double>()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_unknown.cpp b/host/lib/usrp/dboard/db_unknown.cpp new file mode 100644 index 000000000..0587d015c --- /dev/null +++ b/host/lib/usrp/dboard/db_unknown.cpp @@ -0,0 +1,294 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/types/ranges.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/tuple/tuple.hpp> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * Utility functions + **********************************************************************/ +static void warn_if_old_rfx(const dboard_id_t &dboard_id, const std::string &xx){ +    typedef boost::tuple<std::string, dboard_id_t, dboard_id_t> old_ids_t; //name, rx_id, tx_id +    static const std::vector<old_ids_t> old_rfx_ids = list_of +        (old_ids_t("Flex 400 Classic",  0x0004, 0x0008)) +        (old_ids_t("Flex 900 Classic",  0x0005, 0x0009)) +        (old_ids_t("Flex 1200 Classic", 0x0006, 0x000a)) +        (old_ids_t("Flex 1800 Classic", 0x0030, 0x0031)) +        (old_ids_t("Flex 2400 Classic", 0x0007, 0x000b)) +    ; +    BOOST_FOREACH(const old_ids_t &old_id, old_rfx_ids){ +        std::string name; dboard_id_t rx_id, tx_id; +        boost::tie(name, rx_id, tx_id) = old_id; +        if ( +            (xx == "RX" and rx_id == dboard_id) or +            (xx == "TX" and tx_id == dboard_id) +        ) UHD_MSG(warning) << boost::format( +            "Detected %s daughterboard %s\n" +            "This board requires modification to use.\n" +            "See the daughterboard application notes.\n" +        ) % xx % name; +    } +} + +/*********************************************************************** + * The unknown boards: + *   Like a basic board, but with only one subdev. + **********************************************************************/ +class unknown_rx : public rx_dboard_base{ +public: +    unknown_rx(ctor_args_t args); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); +}; + +class unknown_tx : public tx_dboard_base{ +public: +    unknown_tx(ctor_args_t args); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); +}; + +/*********************************************************************** + * Register the unknown dboards + **********************************************************************/ +static dboard_base::sptr make_unknown_rx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new unknown_rx(args)); +} + +static dboard_base::sptr make_unknown_tx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new unknown_tx(args)); +} + +UHD_STATIC_BLOCK(reg_unknown_dboards){ +    dboard_manager::register_dboard(0xfff0, &make_unknown_tx, "Unknown TX"); +    dboard_manager::register_dboard(0xfff1, &make_unknown_rx, "Unknown RX"); +} + +/*********************************************************************** + * Unknown RX dboard + **********************************************************************/ +unknown_rx::unknown_rx(ctor_args_t args) : rx_dboard_base(args){ +    warn_if_old_rfx(this->get_rx_id(), "RX"); +} + +void unknown_rx::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = "Unknown - " + get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        val = double(0); +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        val = gain_range_t(0, 0, 0); +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_FREQ: +        val = double(0); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 0.0; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void unknown_rx::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        UHD_ASSERT_THROW(val.as<double>() == double(0)); +        return; + +    case SUBDEV_PROP_ANTENNA: +        UHD_ASSERT_THROW(val.as<std::string>() == std::string("")); +        return; + +    case SUBDEV_PROP_FREQ: +        return; // it wont do you much good, but you can set it + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "Unknown Daughterboard: No tunable bandwidth, fixed filtered to 0.0MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * Unknown TX dboard + **********************************************************************/ +unknown_tx::unknown_tx(ctor_args_t args) : tx_dboard_base(args){ +    warn_if_old_rfx(this->get_tx_id(), "TX"); +} + +void unknown_tx::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = "Unknown - " + get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        val = double(0); +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        val = gain_range_t(0, 0, 0); +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_FREQ: +        val = double(0); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, ""); //vector of 1 empty string +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 0.0; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void unknown_tx::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        UHD_ASSERT_THROW(val.as<double>() == double(0)); +        return; + +    case SUBDEV_PROP_ANTENNA: +        UHD_ASSERT_THROW(val.as<std::string>() == std::string("")); +        return; + +    case SUBDEV_PROP_FREQ: +        return; // it wont do you much good, but you can set it + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "Unknown Daughterboard: No tunable bandwidth, fixed filtered to 0.0MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp new file mode 100644 index 000000000..1089dc7c2 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_common.cpp @@ -0,0 +1,656 @@ +// +// 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/>. +// + +// Common IO Pins +#define ADF4350_CE      (1 << 3) +#define ADF4350_PDBRF   (1 << 2) +#define ADF4350_MUXOUT  (1 << 1)                // INPUT!!! +#define LOCKDET_MASK    (1 << 0)                // INPUT!!! + +// TX IO Pins +#define TX_PUP_5V       (1 << 7)                // enables 5.0V power supply +#define TX_PUP_3V       (1 << 6)                // enables 3.3V supply +#define TXMOD_EN        (1 << 4)                // on UNIT_TX, 1 enables TX Modulator + +// RX IO Pins +#define RX_PUP_5V       (1 << 7)                // enables 5.0V power supply +#define RX_PUP_3V       (1 << 6)                // enables 3.3V supply +#define RXBB_PDB        (1 << 4)                // on UNIT_RX, 1 powers up RX baseband + +// RX Attenuator Pins +#define RX_ATTN_SHIFT   8                       // lsb of RX Attenuator Control +#define RX_ATTN_MASK    (63 << RX_ATTN_SHIFT)      // valid bits of RX Attenuator Control + +// TX Attenuator Pins (v3 only) +#define TX_ATTN_16      (1 << 14) +#define TX_ATTN_8       (1 << 5) +#define TX_ATTN_4       (1 << 4) +#define TX_ATTN_2       (1 << 3) +#define TX_ATTN_1       (1 << 1) +#define TX_ATTN_MASK    (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1)      // valid bits of TX Attenuator Control + +// Mixer functions +#define TX_MIXER_ENB    (TXMOD_EN|ADF4350_PDBRF)    // for v3, TXMOD_EN tied to ADF4350_PDBRF rather than separate +#define TX_MIXER_DIS    0 + +#define RX_MIXER_ENB    (RXBB_PDB|ADF4350_PDBRF) +#define RX_MIXER_DIS    0 + +// Power functions +#define TX_POWER_UP     (TX_PUP_5V|TX_PUP_3V) // high enables power supply +#define TX_POWER_DOWN   0 + +#define RX_POWER_UP     (RX_PUP_5V|RX_PUP_3V|ADF4350_CE) // high enables power supply +#define RX_POWER_DOWN   0 + +#include "db_wbx_common.hpp" +#include "adf4350_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The WBX Common dboard constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_tx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 25, 0.05)) +; + +static const uhd::dict<std::string, gain_range_t> wbx_v3_tx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 31, 1.0)) +; + +static const uhd::dict<std::string, gain_range_t> wbx_rx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 31.5, 0.5)) +; + +/*********************************************************************** + * WBX Common Implementation + **********************************************************************/ +wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ + +    //enable the clocks that we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //v3 has different io bits for attenuator control +    int v3_iobits = is_v3() ? TX_ATTN_MASK : ADF4350_CE; +    int v3_tx_mod = is_v3() ? ADF4350_PDBRF : TXMOD_EN|ADF4350_PDBRF; + +    //set the gpio directions and atr controls +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v3_tx_mod); +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4350_PDBRF); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v3_tx_mod|v3_iobits); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK); + +    //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); + +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE,        RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY,     RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY,     RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + +    //set some default values +    if (is_v3()) { +        BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){ +            set_tx_gain(wbx_v3_tx_gain_ranges[name].start(), name); +        } +    } +    else { +        BOOST_FOREACH(const std::string &name, wbx_tx_gain_ranges.keys()){ +            set_tx_gain(wbx_tx_gain_ranges[name].start(), name); +        } +    } + +    BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ +        set_rx_gain(wbx_rx_gain_ranges[name].start(), name); +    } +    set_rx_enabled(false); +    set_tx_enabled(false); +} + +wbx_base::~wbx_base(void){ +    /* NOP */ +} + +/*********************************************************************** + * Enables + **********************************************************************/ +void wbx_base::set_rx_enabled(bool enb){ +    this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, +        (enb)? RX_POWER_UP : RX_POWER_DOWN, RX_POWER_UP | RX_POWER_DOWN +    ); +} + +void wbx_base::set_tx_enabled(bool enb){ +    this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, +        (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | (is_v3() ? 0 : ADF4350_CE) +    ); +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +static int rx_pga0_gain_to_iobits(double &gain){ +    //clip the input +    gain = wbx_rx_gain_ranges["PGA0"].clip(gain); + +    //convert to attenuation +    double attn = wbx_rx_gain_ranges["PGA0"].stop() - gain; + +    //calculate the attenuation +    int attn_code = boost::math::iround(attn*2); +    int iobits = ((~attn_code) << RX_ATTN_SHIFT) & RX_ATTN_MASK; + +    UHD_LOGV(often) << boost::format( +        "WBX RX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" +    ) % attn % attn_code % (iobits & RX_ATTN_MASK) % RX_ATTN_MASK << std::endl; + +    //the actual gain setting +    gain = wbx_rx_gain_ranges["PGA0"].stop() - double(attn_code)/2; + +    return iobits; +} + +//v3 TX gains +static int tx_pga0_gain_to_iobits(double &gain){ +    //clip the input +    gain = wbx_v3_tx_gain_ranges["PGA0"].clip(gain); + +    //convert to attenuation +    double attn = wbx_v3_tx_gain_ranges["PGA0"].stop() - gain; + +    //calculate the attenuation +    int attn_code = boost::math::iround(attn); +    int iobits = ( +            (attn_code & 16 ? 0 : TX_ATTN_16) | +            (attn_code &  8 ? 0 : TX_ATTN_8) | +            (attn_code &  4 ? 0 : TX_ATTN_4) | +            (attn_code &  2 ? 0 : TX_ATTN_2) | +            (attn_code &  1 ? 0 : TX_ATTN_1)  +        ) & TX_ATTN_MASK; + +    UHD_LOGV(often) << boost::format( +        "WBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" +    ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl; + +    //the actual gain setting +    gain = wbx_v3_tx_gain_ranges["PGA0"].stop() - double(attn_code)/2; + +    return iobits; +} + +//Pre v3 TX gains +static double tx_pga0_gain_to_dac_volts(double &gain){ +    //clip the input +    gain = wbx_tx_gain_ranges["PGA0"].clip(gain); + +    //voltage level constants +    static const double max_volts = 0.5, min_volts = 1.4; +    static const double slope = (max_volts-min_volts)/wbx_tx_gain_ranges["PGA0"].stop(); + +    //calculate the voltage for the aux dac +    double dac_volts = gain*slope + min_volts; + +    UHD_LOGV(often) << boost::format( +        "WBX TX Gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + +void wbx_base::set_tx_gain(double gain, const std::string &name){ +    if (is_v3()) { +        assert_has(wbx_v3_tx_gain_ranges.keys(), name, "wbx tx gain name"); +        if(name == "PGA0"){ +            boost::uint16_t io_bits = tx_pga0_gain_to_iobits(gain); +            _tx_gains[name] = gain; + +            //write the new gain to tx gpio outputs +            this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, io_bits, TX_ATTN_MASK); +        } +        else UHD_THROW_INVALID_CODE_PATH(); +    } +    else { +        assert_has(wbx_tx_gain_ranges.keys(), name, "wbx tx gain name"); +        if(name == "PGA0"){ +            double dac_volts = tx_pga0_gain_to_dac_volts(gain); +            _tx_gains[name] = gain; + +            //write the new voltage to the aux dac +            this->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts); +        } +        else UHD_THROW_INVALID_CODE_PATH(); +    } +} + +void wbx_base::set_rx_gain(double gain, const std::string &name){ +    assert_has(wbx_rx_gain_ranges.keys(), name, "wbx rx gain name"); +    if(name == "PGA0"){ +        boost::uint16_t io_bits = rx_pga0_gain_to_iobits(gain); +        _rx_gains[name] = gain; + +        //write the new gain to rx gpio outputs +        this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, io_bits, RX_ATTN_MASK); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +double wbx_base::set_lo_freq( +    dboard_iface::unit_t unit, +    double target_freq +){ +    UHD_LOGV(often) << boost::format( +        "WBX tune: target frequency %f Mhz" +    ) % (target_freq/1e6) << std::endl; + +    //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) +    static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of +        (0,23) //adf4350_regs_t::PRESCALER_4_5 +        (1,75) //adf4350_regs_t::PRESCALER_8_9 +    ; + +    //map rf divider select output dividers to enums +    static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of +        (1,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) +        (2,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) +        (4,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) +        (8,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) +        (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) +    ; + +    double actual_freq, pfd_freq; +    double ref_freq = this->get_iface()->get_clock_rate(unit); +    int R=0, BS=0, N=0, FRAC=0, MOD=0; +    int RFdiv = 1; +    adf4350_regs_t::reference_divide_by_2_t T     = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; +    adf4350_regs_t::reference_doubler_t     D     = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;     + +    //Reference doubler for 50% duty cycle +    // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 +    if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; + +    //increase RF divider until acceptable VCO frequency +    //start with target_freq*2 because mixer has divide by 2 +    double vco_freq = target_freq*2; +    while (vco_freq < 2.2e9) { +        vco_freq *= 2; +        RFdiv *= 2; +    } + +    //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) +    adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + +    /* +     * The goal here is to loop though possible R dividers, +     * band select clock dividers, N (int) dividers, and FRAC  +     * (frac) dividers. +     * +     * Calculate the N and F dividers for each set of values. +     * The loop exists when it meets all of the constraints. +     * The resulting loop values are loaded into the registers. +     * +     * from pg.21 +     * +     * f_pfd = f_ref*(1+D)/(R*(1+T)) +     * f_vco = (N + (FRAC/MOD))*f_pfd +     *    N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD +     * f_rf = f_vco/RFdiv) +     * f_actual = f_rf/2 +     */ +    for(R = 1; R <= 1023; R+=1){ +        //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) +        pfd_freq = ref_freq*(1+D)/(R*(1+T)); + +        //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) +        if (pfd_freq > 25e6) continue; + +        //ignore fractional part of tuning +        N = int(std::floor(vco_freq/pfd_freq)); + +        //keep N > minimum int divider requirement +        if (N < prescaler_to_min_int_div[prescaler]) continue; + +        for(BS=1; BS <= 255; BS+=1){ +            //keep the band select frequency at or below 100KHz +            //constraint on band select clock +            if (pfd_freq/BS > 100e3) continue; +            goto done_loop; +        } +    } done_loop: + +    //Fractional-N calculation +    MOD = 4095; //max fractional accuracy +    FRAC = int((vco_freq/pfd_freq - N)*MOD); + +    //Reference divide-by-2 for 50% duty cycle +    // if R even, move one divide by 2 to to regs.reference_divide_by_2 +    if(R % 2 == 0){ +        T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; +        R /= 2; +    } + +    //actual frequency calculation +    actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); + + +    UHD_LOGV(often) +        << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + +        << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%d" +            ) % R % BS % N % FRAC % MOD % T % D % RFdiv % get_locked(unit)<< std::endl +        << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" +            ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + +    //load the register values +    adf4350_regs_t regs; + +    regs.frac_12_bit = FRAC; +    regs.int_16_bit = N; +    regs.mod_12_bit = MOD; +    regs.prescaler = prescaler; +    regs.r_counter_10_bit = R; +    regs.reference_divide_by_2 = T; +    regs.reference_doubler = D; +    regs.band_select_clock_div = BS; +    UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); +    regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + +    if (unit == dboard_iface::UNIT_RX) { +        freq_range_t rx_lo_5dbm = list_of +            (range_t(0.05e9, 1.4e9)) +        ; + +        freq_range_t rx_lo_2dbm = list_of +            (range_t(1.4e9, 2.2e9)) +        ; + +        if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + +        if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; + +    } else if (unit == dboard_iface::UNIT_TX) { +        freq_range_t tx_lo_5dbm = list_of +            (range_t(0.05e9, 1.7e9)) +            (range_t(1.9e9, 2.2e9)) +        ; + +        freq_range_t tx_lo_m1dbm = list_of +            (range_t(1.7e9, 1.9e9)) +        ; + +        if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + +        if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM; + +    } + +    //write the registers +    //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) +    int addr; + +    for(addr=5; addr>=0; addr--){ +        UHD_LOGV(often) << boost::format( +            "WBX SPI Reg (0x%02x): 0x%08x" +        ) % addr % regs.get_reg(addr) << std::endl; +        this->get_iface()->write_spi( +            unit, spi_config_t::EDGE_RISE, +            regs.get_reg(addr), 32 +        ); +    } + +    //return the actual frequency +    UHD_LOGV(often) << boost::format( +        "WBX tune: actual frequency %f Mhz" +    ) % (actual_freq/1e6) << std::endl; +    return actual_freq; +} + +bool wbx_base::get_locked(dboard_iface::unit_t unit){ +    return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; +} + +bool wbx_base::is_v3(void){ +    return get_rx_id().to_uint16() == 0x057; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void wbx_base::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_rx_gains.keys(), key.name, "wbx rx gain name"); +        val = _rx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(wbx_rx_gain_ranges.keys(), key.name, "wbx rx gain name"); +        val = wbx_rx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(wbx_rx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = 0.0; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0, 0.0);; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = _rx_enabled; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", this->get_locked(dboard_iface::UNIT_RX), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void wbx_base::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        this->set_rx_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ENABLED: +        _rx_enabled = val.as<bool>(); +        this->set_rx_enabled(_rx_enabled); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "WBX: No tunable bandwidth, fixed filtered to 40MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void wbx_base::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_tx_gains.keys(), key.name, "wbx tx gain name"); +        val = _tx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        if (is_v3()) { +            assert_has(wbx_v3_tx_gain_ranges.keys(), key.name, "wbx tx gain name"); +            val = wbx_v3_tx_gain_ranges[key.name]; +        } +        else { +            assert_has(wbx_tx_gain_ranges.keys(), key.name, "wbx tx gain name"); +            val = wbx_tx_gain_ranges[key.name]; +        } +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        if (is_v3()) +            val = prop_names_t(wbx_v3_tx_gain_ranges.keys()); +        else +            val = prop_names_t(wbx_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = 0.0; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0, 0.0); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = _tx_enabled; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", this->get_locked(dboard_iface::UNIT_TX), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void wbx_base::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_GAIN: +        this->set_tx_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ENABLED: +        _tx_enabled = val.as<bool>(); +        this->set_tx_enabled(_tx_enabled); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        UHD_MSG(warning) << "WBX: No tunable bandwidth, fixed filtered to 40MHz"; +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp new file mode 100644 index 000000000..5d33ddce9 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_common.hpp @@ -0,0 +1,78 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP +#define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP + +#include "adf4350_regs.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/props.hpp> +#include <uhd/usrp/dboard_base.hpp> + +namespace uhd{ namespace usrp{ + +/*********************************************************************** + * The WBX dboard base class + **********************************************************************/ +class wbx_base : public xcvr_dboard_base{ +public: +    wbx_base(ctor_args_t args); +    virtual ~wbx_base(void); + +protected: +    virtual void set_rx_gain(double gain, const std::string &name); +    virtual void set_tx_gain(double gain, const std::string &name); + +    virtual void set_rx_enabled(bool enb); +    virtual void set_tx_enabled(bool enb); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +    /*! +     * Set the LO frequency for the particular dboard unit. +     * \param unit which unit rx or tx +     * \param target_freq the desired frequency in Hz +     * \return the actual frequency in Hz +     */ +    virtual double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + +    /*! +     * Get the lock detect status of the LO. +     * \param unit which unit rx or tx +     * \return true for locked +     */ +    virtual bool get_locked(dboard_iface::unit_t unit); + +    /*! +     * Detect if this a v3 WBX +     * \return true for locked +     */ +    virtual bool is_v3(void); + +private: +    uhd::dict<std::string, double> _tx_gains, _rx_gains; +    bool _rx_enabled, _tx_enabled; +}; + +}} //namespace uhd::usrp + +#endif /* INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP */ diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp new file mode 100644 index 000000000..990bacbc8 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_simple.cpp @@ -0,0 +1,255 @@ +// +// 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/>. +// + +// Antenna constants +#define ANTSW_IO        ((1 << 15))             // on UNIT_TX, 0 = TX, 1 = RX, on UNIT_RX 0 = main ant, 1 = RX2 +#define ANT_TX          0                       //the tx line is transmitting +#define ANT_RX          ANTSW_IO                //the tx line is receiving +#define ANT_TXRX        0                       //the rx line is on txrx +#define ANT_RX2         ANTSW_IO                //the rx line in on rx2 + +#include "db_wbx_common.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The WBX Simple dboard constants + **********************************************************************/ +static const freq_range_t wbx_freq_range(68.75e6, 2.2e9); + +static const prop_names_t wbx_tx_antennas = list_of("TX/RX"); + +static const prop_names_t wbx_rx_antennas = list_of("TX/RX")("RX2"); + +/*********************************************************************** + * The WBX simple implementation + **********************************************************************/ +class wbx_simple : public wbx_base{ +public: +    wbx_simple(ctor_args_t args); +    ~wbx_simple(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    void set_rx_lo_freq(double freq); +    void set_tx_lo_freq(double freq); +    double _rx_lo_freq, _tx_lo_freq; + +    void set_rx_ant(const std::string &ant); +    void set_tx_ant(const std::string &ant); +    std::string _rx_ant; +}; + +/*********************************************************************** + * Register the WBX simple implementation + **********************************************************************/ +static dboard_base::sptr make_wbx_simple(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new wbx_simple(args)); +} + +UHD_STATIC_BLOCK(reg_wbx_simple_dboards){ +    dboard_manager::register_dboard(0x0053, 0x0052, &make_wbx_simple, "WBX"); +    dboard_manager::register_dboard(0x0053, 0x004f, &make_wbx_simple, "WBX + Simple GDB"); +    dboard_manager::register_dboard(0x0057, 0x0056, &make_wbx_simple, "WBX v3"); +    dboard_manager::register_dboard(0x0057, 0x004f, &make_wbx_simple, "WBX v3 + Simple GDB"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ + +    //set the gpio directions and atr controls (antenna switches all under ATR) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, ANTSW_IO, ANTSW_IO); +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, ANTSW_IO, ANTSW_IO); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); + +    //setup ATR for the antenna switches (constant) +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        ANT_RX, ANTSW_IO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     ANT_RX, ANTSW_IO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     ANT_TX, ANTSW_IO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE,        ANT_TXRX, ANTSW_IO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY,     ANT_RX2, ANTSW_IO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); + +    //set some default values +    set_rx_lo_freq((wbx_freq_range.start() + wbx_freq_range.stop())/2.0); +    set_tx_lo_freq((wbx_freq_range.start() + wbx_freq_range.stop())/2.0); +    set_rx_ant("RX2"); +} + +wbx_simple::~wbx_simple(void){ +    /* NOP */ +} + +/*********************************************************************** + * Antennas + **********************************************************************/ +void wbx_simple::set_rx_ant(const std::string &ant){ +    //validate input +    assert_has(wbx_rx_antennas, ant, "wbx rx antenna name"); + +    //shadow the setting +    _rx_ant = ant; + +    //write the new antenna setting to atr regs +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO); +} + +void wbx_simple::set_tx_ant(const std::string &ant){ +    assert_has(wbx_tx_antennas, ant, "wbx tx antenna name"); +    //only one antenna option, do nothing +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +void wbx_simple::set_rx_lo_freq(double freq){ +    _rx_lo_freq = set_lo_freq(dboard_iface::UNIT_RX, wbx_freq_range.clip(freq)); +} + +void wbx_simple::set_tx_lo_freq(double freq){ +    _tx_lo_freq = set_lo_freq(dboard_iface::UNIT_TX, wbx_freq_range.clip(freq)); +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void wbx_simple::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        if (is_v3()) +            val = std::string("WBX v3 RX + Simple GDB"); +        else +            val = std::string("WBX RX + Simple GDB"); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _rx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = wbx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _rx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = wbx_rx_antennas; +        return; + +    default: +        //call into the base class for other properties +        wbx_base::rx_get(key_, val); +    } +} + +void wbx_simple::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_rx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_rx_ant(val.as<std::string>()); +        return; + +    default: +        //call into the base class for other properties +        wbx_base::rx_set(key_, val); +    } +} + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void wbx_simple::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        if (is_v3()) +            val = std::string("WBX v3 TX + Simple GDB"); +        else +            val = std::string("WBX TX + Simple GDB"); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _tx_lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = wbx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string("TX/RX"); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = wbx_tx_antennas; +        return; + +    default: +        //call into the base class for other properties +        wbx_base::tx_get(key_, val); +    } +} + +void wbx_simple::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_tx_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_tx_ant(val.as<std::string>()); +        return; + +    default: +        //call into the base class for other properties +        wbx_base::tx_set(key_, val); +    } +} diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp new file mode 100644 index 000000000..b88eb2a95 --- /dev/null +++ b/host/lib/usrp/dboard/db_xcvr2450.cpp @@ -0,0 +1,781 @@ +// +// 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/>. +// + +// TX IO Pins +#define HB_PA_OFF_TXIO      (1 << 15)    // 5GHz PA, 1 = off, 0 = on +#define LB_PA_OFF_TXIO      (1 << 14)    // 2.4GHz PA, 1 = off, 0 = on +#define ANTSEL_TX1_RX2_TXIO (1 << 13)    // 1 = Ant 1 to TX, Ant 2 to RX +#define ANTSEL_TX2_RX1_TXIO (1 << 12)    // 1 = Ant 2 to TX, Ant 1 to RX +#define TX_EN_TXIO          (1 << 11)    // 1 = TX on, 0 = TX off +#define AD9515DIV_TXIO      (1 << 4)     // 1 = Div  by 3, 0 = Div by 2 + +#define TXIO_MASK (HB_PA_OFF_TXIO | LB_PA_OFF_TXIO | ANTSEL_TX1_RX2_TXIO | ANTSEL_TX2_RX1_TXIO | TX_EN_TXIO | AD9515DIV_TXIO) + +// TX IO Functions +#define HB_PA_TXIO               LB_PA_OFF_TXIO +#define LB_PA_TXIO               HB_PA_OFF_TXIO +#define TX_ENB_TXIO              TX_EN_TXIO +#define TX_DIS_TXIO              0 +#define AD9515DIV_3_TXIO         AD9515DIV_TXIO +#define AD9515DIV_2_TXIO         0 + +// RX IO Pins +#define LOCKDET_RXIO (1 << 15)           // This is an INPUT!!! +#define POWER_RXIO   (1 << 14)           // 1 = power on, 0 = shutdown +#define RX_EN_RXIO   (1 << 13)           // 1 = RX on, 0 = RX off +#define RX_HP_RXIO   (1 << 12)           // 0 = Fc set by rx_hpf, 1 = 600 KHz + +#define RXIO_MASK (POWER_RXIO | RX_EN_RXIO | RX_HP_RXIO) + +// RX IO Functions +#define POWER_UP_RXIO            POWER_RXIO +#define POWER_DOWN_RXIO          0 +#define RX_ENB_RXIO              RX_EN_RXIO +#define RX_DIS_RXIO              0 + +#include "max2829_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The XCVR 2450 constants + **********************************************************************/ +static const freq_range_t xcvr_freq_range = list_of +    (range_t(2.4e9, 2.5e9)) +    (range_t(4.9e9, 6.0e9)) +; + +static const prop_names_t xcvr_antennas = list_of("J1")("J2"); + +static const uhd::dict<std::string, gain_range_t> xcvr_tx_gain_ranges = map_list_of +    ("VGA", gain_range_t(0, 30, 0.5)) +    ("BB", gain_range_t(0, 5, 1.5)) +; +static const uhd::dict<std::string, gain_range_t> xcvr_rx_gain_ranges = map_list_of +    ("LNA", gain_range_t(list_of +        (range_t(0)) +        (range_t(15)) +        (range_t(30.5)) +    )) +    ("VGA", gain_range_t(0, 62, 2.0)) +; + +/*********************************************************************** + * The XCVR 2450 dboard class + **********************************************************************/ +class xcvr2450 : public xcvr_dboard_base{ +public: +    xcvr2450(ctor_args_t args); +    ~xcvr2450(void); + +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +    void tx_get(const wax::obj &key, wax::obj &val); +    void tx_set(const wax::obj &key, const wax::obj &val); + +private: +    double _lo_freq; +    double _rx_bandwidth, _tx_bandwidth; +    uhd::dict<std::string, double> _tx_gains, _rx_gains; +    std::string _tx_ant, _rx_ant; +    int _ad9515div; +    max2829_regs_t _max2829_regs; + +    void set_lo_freq(double target_freq); +    void set_tx_ant(const std::string &ant); +    void set_rx_ant(const std::string &ant); +    void set_tx_gain(double gain, const std::string &name); +    void set_rx_gain(double gain, const std::string &name); +    void set_rx_bandwidth(double bandwidth); +    void set_tx_bandwidth(double bandwidth); + +    void update_atr(void); +    void spi_reset(void); +    void send_reg(boost::uint8_t addr){ +        boost::uint32_t value = _max2829_regs.get_reg(addr); +        UHD_LOGV(often) << boost::format( +            "XCVR2450: send reg 0x%02x, value 0x%05x" +        ) % int(addr) % value << std::endl; +        this->get_iface()->write_spi( +            dboard_iface::UNIT_RX, +            spi_config_t::EDGE_RISE, +            value, 24 +        ); +    } + +    static bool is_highband(double freq){return freq > 3e9;} + +    /*! +     * Is the LO locked? +     * \return true for locked +     */ +    bool get_locked(void){ +        return (this->get_iface()->read_gpio(dboard_iface::UNIT_RX) & LOCKDET_RXIO) != 0; +    } + +    /*! +     * Read the RSSI from the aux adc +     * \return the rssi in dB +     */ +    double get_rssi(void){ +        //*FIXME* RSSI depends on LNA Gain Setting (datasheet pg 16 top middle chart) +        double max_power = 0.0; +        switch(_max2829_regs.rx_lna_gain){ +        case 0: +        case 1: max_power = 0;    break; +        case 2: max_power = -15;   break; +        case 3: max_power = -30.5; break; +        } + +        //constants for the rssi calculation +        static const double min_v = 0.5, max_v = 2.5; +        static const double rssi_dyn_range = 60; +        //calculate the rssi from the voltage +        double voltage = this->get_iface()->read_aux_adc(dboard_iface::UNIT_RX, dboard_iface::AUX_ADC_B); +        return max_power - rssi_dyn_range*(voltage - min_v)/(max_v - min_v); +    } +}; + +/*********************************************************************** + * Register the XCVR 2450 dboard + **********************************************************************/ +static dboard_base::sptr make_xcvr2450(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new xcvr2450(args)); +} + +UHD_STATIC_BLOCK(reg_xcvr2450_dboard){ +    //register the factory function for the rx and tx dbids +    dboard_manager::register_dboard(0x0061, 0x0060, &make_xcvr2450, "XCVR2450"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ +    //enable only the clocks we need +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TXIO_MASK); +    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RXIO_MASK); + +    spi_reset(); //prepare the spi + +    _rx_bandwidth = 9.5e6; +    _tx_bandwidth = 12.0e6; + +    //setup the misc max2829 registers +    _max2829_regs.mimo_select         = max2829_regs_t::MIMO_SELECT_MIMO; +    _max2829_regs.band_sel_mimo       = max2829_regs_t::BAND_SEL_MIMO_MIMO; +    _max2829_regs.pll_cp_select       = max2829_regs_t::PLL_CP_SELECT_4MA; +    _max2829_regs.rssi_high_bw        = max2829_regs_t::RSSI_HIGH_BW_6MHZ; +    _max2829_regs.tx_lpf_coarse_adj   = max2829_regs_t::TX_LPF_COARSE_ADJ_12MHZ; +    _max2829_regs.rx_lpf_coarse_adj   = max2829_regs_t::RX_LPF_COARSE_ADJ_9_5MHZ; +    _max2829_regs.rx_lpf_fine_adj     = max2829_regs_t::RX_LPF_FINE_ADJ_100; +    _max2829_regs.rx_vga_gain_spi     = max2829_regs_t::RX_VGA_GAIN_SPI_SPI; +    _max2829_regs.rssi_output_range   = max2829_regs_t::RSSI_OUTPUT_RANGE_HIGH; +    _max2829_regs.rssi_op_mode        = max2829_regs_t::RSSI_OP_MODE_ENABLED; +    _max2829_regs.rssi_pin_fcn        = max2829_regs_t::RSSI_PIN_FCN_RSSI; +    _max2829_regs.rx_highpass         = max2829_regs_t::RX_HIGHPASS_100HZ; +    _max2829_regs.tx_vga_gain_spi     = max2829_regs_t::TX_VGA_GAIN_SPI_SPI; +    _max2829_regs.pa_driver_linearity = max2829_regs_t::PA_DRIVER_LINEARITY_78; +    _max2829_regs.tx_vga_linearity    = max2829_regs_t::TX_VGA_LINEARITY_78; +    _max2829_regs.tx_upconv_linearity = max2829_regs_t::TX_UPCONV_LINEARITY_78; + +    //send initial register settings +    for(boost::uint8_t reg = 0x2; reg <= 0xC; reg++){ +        this->send_reg(reg); +    } + +    //set defaults for LO, gains, antennas +    set_lo_freq(2.45e9); +    set_rx_ant(xcvr_antennas.at(0)); +    set_tx_ant(xcvr_antennas.at(1)); +    BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){ +        set_tx_gain(xcvr_tx_gain_ranges[name].start(), name); +    } +    BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){ +        set_rx_gain(xcvr_rx_gain_ranges[name].start(), name); +    } +} + +xcvr2450::~xcvr2450(void){ +    UHD_SAFE_CALL(spi_reset();) +} + +void xcvr2450::spi_reset(void){ +    //spi reset mode: global enable = off, tx and rx enable = on +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_ENB_TXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); +    boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + +    //take it back out of spi reset mode and wait a bit +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); +    boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +} + +void xcvr2450::update_atr(void){ +    //calculate tx atr pins +    int band_sel   = (xcvr2450::is_highband(_lo_freq))? HB_PA_TXIO : LB_PA_TXIO; +    int tx_ant_sel = (_tx_ant == "J1")? ANTSEL_TX1_RX2_TXIO : ANTSEL_TX2_RX1_TXIO; +    int rx_ant_sel = (_rx_ant == "J2")? ANTSEL_TX1_RX2_TXIO : ANTSEL_TX2_RX1_TXIO; +    int xx_ant_sel = tx_ant_sel; //Prefer the tx antenna selection for full duplex, +    //due to the issue that USRP1 will take the value of full duplex for its TXATR. +    int ad9515div  = (_ad9515div == 3)? AD9515DIV_3_TXIO : AD9515DIV_2_TXIO; + +    //set the tx registers +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        band_sel | ad9515div | TX_DIS_TXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); + +    //set the rx registers +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE,        POWER_UP_RXIO | RX_DIS_RXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY,     POWER_UP_RXIO | RX_ENB_RXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY,     POWER_UP_RXIO | RX_DIS_RXIO); +    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); +} + +/*********************************************************************** + * Tuning + **********************************************************************/ +void xcvr2450::set_lo_freq(double target_freq){ + +    //clip the input to the range +    target_freq = xcvr_freq_range.clip(target_freq); + +    //variables used in the calculation below +    double scaler = xcvr2450::is_highband(target_freq)? (4.0/5.0) : (4.0/3.0); +    double ref_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_TX); +    int R, intdiv, fracdiv; + +    //loop through values until we get a match +    for(_ad9515div = 2; _ad9515div <= 3; _ad9515div++){ +        for(R = 1; R <= 7; R++){ +            double N = (target_freq*scaler*R*_ad9515div)/ref_freq; +            intdiv = int(std::floor(N)); +            fracdiv = boost::math::iround((N - intdiv)*double(1 << 16)); +            //actual minimum is 128, but most chips seems to require higher to lock +            if (intdiv < 131 or intdiv > 255) continue; +            //constraints met: exit loop +            goto done_loop; +        } +    } done_loop: + +    //calculate the actual freq from the values above +    double N = double(intdiv) + double(fracdiv)/double(1 << 16); +    _lo_freq = (N*ref_freq)/(scaler*R*_ad9515div); + +    UHD_LOGV(often) +        << boost::format("XCVR2450 tune:\n") +        << boost::format("    R=%d, N=%f, ad9515=%d, scaler=%f\n") % R % N % _ad9515div % scaler +        << boost::format("    Ref    Freq=%fMHz\n") % (ref_freq/1e6) +        << boost::format("    Target Freq=%fMHz\n") % (target_freq/1e6) +        << boost::format("    Actual Freq=%fMHz\n") % (_lo_freq/1e6) +        << std::endl; + +    //high-high band or low-high band? +    if(_lo_freq > (5.35e9 + 5.47e9)/2.0){ +        UHD_LOGV(often) << "XCVR2450 tune: Using  high-high band" << std::endl; +        _max2829_regs.band_select_802_11a = max2829_regs_t::BAND_SELECT_802_11A_5_47GHZ_TO_5_875GHZ; +    }else{ +        UHD_LOGV(often) << "XCVR2450 tune: Using  low-high band" << std::endl; +        _max2829_regs.band_select_802_11a = max2829_regs_t::BAND_SELECT_802_11A_4_9GHZ_TO_5_35GHZ; +    } + +    //new band select settings and ad9515 divider +    this->update_atr(); + +    //load new counters into registers +    _max2829_regs.int_div_ratio_word = intdiv; +    _max2829_regs.frac_div_ratio_lsb = fracdiv & 0x3; +    _max2829_regs.frac_div_ratio_msb = fracdiv >> 2; +    this->send_reg(0x3); //integer +    this->send_reg(0x4); //fractional + +    //load the reference divider and band select into registers +    //toggle the bandswitch from off to automatic (which really means start) +    _max2829_regs.ref_divider = R; +    _max2829_regs.band_select = (xcvr2450::is_highband(_lo_freq))? +                                max2829_regs_t::BAND_SELECT_5GHZ   : +                                max2829_regs_t::BAND_SELECT_2_4GHZ ; +    _max2829_regs.vco_bandswitch = max2829_regs_t::VCO_BANDSWITCH_DISABLE; +    this->send_reg(0x5); +    _max2829_regs.vco_bandswitch = max2829_regs_t::VCO_BANDSWITCH_AUTOMATIC;; +    this->send_reg(0x5); +} + +/*********************************************************************** + * Antenna Handling + **********************************************************************/ +void xcvr2450::set_tx_ant(const std::string &ant){ +    assert_has(xcvr_antennas, ant, "xcvr antenna name"); +   _tx_ant = ant; +    this->update_atr(); //sets the atr to the new antenna setting +} + +void xcvr2450::set_rx_ant(const std::string &ant){ +    assert_has(xcvr_antennas, ant, "xcvr antenna name"); +    _rx_ant = ant; +    this->update_atr(); //sets the atr to the new antenna setting +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Convert a requested gain for the tx vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 6 bit the register value + */ +static int gain_to_tx_vga_reg(double &gain){ +    //calculate the register value +    int reg = uhd::clip(boost::math::iround(gain*60/30.0) + 3, 0, 63); + +    //calculate the actual gain value +    if (reg < 4)       gain = 0; +    else if (reg < 48) gain = double(reg/2 - 1); +    else               gain = double(reg/2.0 - 1.5); + +    //return register value +    return reg; +} + +/*! + * Convert a requested gain for the tx bb into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return gain enum value + */ +static max2829_regs_t::tx_baseband_gain_t gain_to_tx_bb_reg(double &gain){ +    int reg = uhd::clip(boost::math::iround(gain*3/5.0), 0, 3); +    switch(reg){ +    case 0: +        gain = 0; +        return max2829_regs_t::TX_BASEBAND_GAIN_0DB; +    case 1: +        gain = 2; +        return max2829_regs_t::TX_BASEBAND_GAIN_2DB; +    case 2: +        gain = 3.5; +        return max2829_regs_t::TX_BASEBAND_GAIN_3_5DB; +    case 3: +        gain = 5; +        return max2829_regs_t::TX_BASEBAND_GAIN_5DB; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +/*! + * Convert a requested gain for the rx vga into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 5 bit the register value + */ +static int gain_to_rx_vga_reg(double &gain){ +    int reg = uhd::clip(boost::math::iround(gain/2.0), 0, 31); +    gain = double(reg*2); +    return reg; +} + +/*! + * Convert a requested gain for the rx lna into the integer register value. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return 2 bit the register value + */ +static int gain_to_rx_lna_reg(double &gain){ +    int reg = uhd::clip(boost::math::iround(gain*2/30.5) + 1, 0, 3); +    switch(reg){ +    case 0: +    case 1: gain = 0;    break; +    case 2: gain = 15;   break; +    case 3: gain = 30.5; break; +    } +    return reg; +} + +void xcvr2450::set_tx_gain(double gain, const std::string &name){ +    assert_has(xcvr_tx_gain_ranges.keys(), name, "xcvr tx gain name"); +    if (name == "VGA"){ +        _max2829_regs.tx_vga_gain = gain_to_tx_vga_reg(gain); +        send_reg(0xC); +    } +    else if(name == "BB"){ +        _max2829_regs.tx_baseband_gain = gain_to_tx_bb_reg(gain); +        send_reg(0x9); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _tx_gains[name] = gain; +} + +void xcvr2450::set_rx_gain(double gain, const std::string &name){ +    assert_has(xcvr_rx_gain_ranges.keys(), name, "xcvr rx gain name"); +    if (name == "VGA"){ +        _max2829_regs.rx_vga_gain = gain_to_rx_vga_reg(gain); +        send_reg(0xB); +    } +    else if(name == "LNA"){ +        _max2829_regs.rx_lna_gain = gain_to_rx_lna_reg(gain); +        send_reg(0xB); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _rx_gains[name] = gain; +} + + +/*********************************************************************** + * Bandwidth Handling + **********************************************************************/ +static max2829_regs_t::tx_lpf_coarse_adj_t bandwidth_to_tx_lpf_coarse_reg(double &bandwidth){ +    int reg = uhd::clip(boost::math::iround((bandwidth-6.0e6)/6.0e6), 1, 3); + +    switch(reg){ +    case 1: // bandwidth < 15MHz +        bandwidth = 12e6; +        return max2829_regs_t::TX_LPF_COARSE_ADJ_12MHZ; +    case 2: // 15MHz < bandwidth < 21MHz +        bandwidth = 18e6; +        return max2829_regs_t::TX_LPF_COARSE_ADJ_18MHZ; +    case 3: // bandwidth > 21MHz +        bandwidth = 24e6; +        return max2829_regs_t::TX_LPF_COARSE_ADJ_24MHZ; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +static max2829_regs_t::rx_lpf_fine_adj_t bandwidth_to_rx_lpf_fine_reg(double &bandwidth, double requested_bandwidth){ +    int reg = uhd::clip(boost::math::iround((requested_bandwidth/bandwidth)/0.05), 18, 22); + +    switch(reg){ +    case 18: // requested_bandwidth < 92.5% +        bandwidth = 0.9 * bandwidth; +        return max2829_regs_t::RX_LPF_FINE_ADJ_90; +    case 19: // 92.5% < requested_bandwidth < 97.5% +        bandwidth = 0.95 * bandwidth; +        return max2829_regs_t::RX_LPF_FINE_ADJ_95; +    case 20: // 97.5% < requested_bandwidth < 102.5% +        bandwidth = 1.0 * bandwidth; +        return max2829_regs_t::RX_LPF_FINE_ADJ_100; +    case 21: // 102.5% < requested_bandwidth < 107.5% +        bandwidth = 1.05 * bandwidth; +        return max2829_regs_t::RX_LPF_FINE_ADJ_105; +    case 22: // 107.5% < requested_bandwidth +        bandwidth = 1.1 * bandwidth; +        return max2829_regs_t::RX_LPF_FINE_ADJ_110; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +static max2829_regs_t::rx_lpf_coarse_adj_t bandwidth_to_rx_lpf_coarse_reg(double &bandwidth){ +    int reg = uhd::clip(boost::math::iround((bandwidth-7.0e6)/1.0e6), 0, 11); + +    switch(reg){ +    case 0: // bandwidth < 7.5MHz +    case 1: // 7.5MHz < bandwidth < 8.5MHz +        bandwidth = 7.5e6; +        return max2829_regs_t::RX_LPF_COARSE_ADJ_7_5MHZ; +    case 2: // 8.5MHz < bandwidth < 9.5MHz +    case 3: // 9.5MHz < bandwidth < 10.5MHz +    case 4: // 10.5MHz < bandwidth < 11.5MHz +        bandwidth = 9.5e6; +        return max2829_regs_t::RX_LPF_COARSE_ADJ_9_5MHZ; +    case 5: // 11.5MHz < bandwidth < 12.5MHz +    case 6: // 12.5MHz < bandwidth < 13.5MHz +    case 7: // 13.5MHz < bandwidth < 14.5MHz +    case 8: // 14.5MHz < bandwidth < 15.5MHz +        bandwidth = 14e6; +        return max2829_regs_t::RX_LPF_COARSE_ADJ_14MHZ; +    case 9: // 15.5MHz < bandwidth < 16.5MHz +    case 10: // 16.5MHz < bandwidth < 17.5MHz +    case 11: // 17.5MHz < bandwidth +        bandwidth = 18e6; +        return max2829_regs_t::RX_LPF_COARSE_ADJ_18MHZ; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +void xcvr2450::set_rx_bandwidth(double bandwidth){ +    double requested_bandwidth = bandwidth; + +    //compute coarse low pass cutoff frequency setting +    _max2829_regs.rx_lpf_coarse_adj = bandwidth_to_rx_lpf_coarse_reg(bandwidth); + +    //compute fine low pass cutoff frequency setting +    _max2829_regs.rx_lpf_fine_adj = bandwidth_to_rx_lpf_fine_reg(bandwidth, requested_bandwidth); + +    //shadow bandwidth setting +    _rx_bandwidth = bandwidth; + +    //update register +    send_reg(0x7); + +    UHD_LOGV(often) << boost::format( +        "XCVR2450 RX Bandwidth (lp_fc): %f Hz, coarse reg: %d, fine reg: %d" +    ) % _rx_bandwidth % (int(_max2829_regs.rx_lpf_coarse_adj)) % (int(_max2829_regs.rx_lpf_fine_adj)) << std::endl; +} + +void xcvr2450::set_tx_bandwidth(double bandwidth){ +    //compute coarse low pass cutoff frequency setting +    _max2829_regs.tx_lpf_coarse_adj = bandwidth_to_tx_lpf_coarse_reg(bandwidth); + +    //shadow bandwidth setting +    _tx_bandwidth = bandwidth; + +    //update register +    send_reg(0x7); + +    UHD_LOGV(often) << boost::format( +        "XCVR2450 TX Bandwidth (lp_fc): %f Hz, coarse reg: %d" +    ) % _tx_bandwidth % (int(_max2829_regs.tx_lpf_coarse_adj)) << std::endl; +} + + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void xcvr2450::rx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_rx_gains.keys(), key.name, "xcvr rx gain name"); +        val = _rx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(xcvr_rx_gain_ranges.keys(), key.name, "xcvr rx gain name"); +        val = xcvr_rx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(xcvr_rx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = xcvr_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _rx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = xcvr_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        if (key.name == "lo_locked") +            val = sensor_value_t("LO", this->get_locked(), "locked", "unlocked"); +        else if (key.name == "rssi") +            val = sensor_value_t("RSSI", this->get_rssi(), "dBm"); +        else +            UHD_THROW_INVALID_CODE_PATH(); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES:{ +            prop_names_t names = list_of("lo_locked")("rssi"); +            val = names; +        } +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*_rx_bandwidth; //_tx_bandwidth is low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void xcvr2450::rx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        this->set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_rx_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_rx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_rx_bandwidth(val.as<double>()/2.0); //complex double-sided, we want low-pass +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void xcvr2450::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_tx_gains.keys(), key.name, "xcvr tx gain name"); +        val = _tx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(xcvr_tx_gain_ranges.keys(), key.name, "xcvr tx gain name"); +        val = xcvr_tx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(xcvr_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = _lo_freq; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = xcvr_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = _tx_ant; +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = xcvr_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_QI; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = true; //always enabled +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", this->get_locked(), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*_tx_bandwidth; //_tx_bandwidth is low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void xcvr2450::tx_set(const wax::obj &key_, const wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ + +    case SUBDEV_PROP_FREQ: +        set_lo_freq(val.as<double>()); +        return; + +    case SUBDEV_PROP_GAIN: +        this->set_tx_gain(val.as<double>(), key.name); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        this->set_tx_bandwidth(val.as<double>()/2.0); //complex double-sided, we want low-pass +        return; + +    case SUBDEV_PROP_ANTENNA: +        this->set_tx_ant(val.as<std::string>()); +        return; + +    case SUBDEV_PROP_ENABLED: +        return; //always enabled + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} diff --git a/host/lib/usrp/dboard_base.cpp b/host/lib/usrp/dboard_base.cpp new file mode 100644 index 000000000..e14c9d144 --- /dev/null +++ b/host/lib/usrp/dboard_base.cpp @@ -0,0 +1,123 @@ +// +// 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 "dboard_ctor_args.hpp" +#include <uhd/usrp/dboard_base.hpp> +#include <boost/format.hpp> +#include <stdexcept> + +using namespace uhd::usrp; + +/*********************************************************************** + * dboard_base dboard dboard_base class + **********************************************************************/ +struct dboard_base::impl{ +    dboard_ctor_args_t args; +}; + +dboard_base::dboard_base(ctor_args_t args){ +    _impl = UHD_PIMPL_MAKE(impl, ()); +    _impl->args = *static_cast<dboard_ctor_args_t *>(args); +} + +dboard_base::~dboard_base(void){ +   /* NOP */ +} + +std::string dboard_base::get_subdev_name(void){ +    return _impl->args.sd_name; +} + +dboard_iface::sptr dboard_base::get_iface(void){ +    return _impl->args.db_iface; +} + +dboard_id_t dboard_base::get_rx_id(void){ +    return _impl->args.rx_id; +} + +dboard_id_t dboard_base::get_tx_id(void){ +    return _impl->args.tx_id; +} + +/*********************************************************************** + * xcvr dboard dboard_base class + **********************************************************************/ +xcvr_dboard_base::xcvr_dboard_base(ctor_args_t args) : dboard_base(args){ +    if (get_rx_id() == dboard_id_t::none()){ +        throw uhd::runtime_error(str(boost::format( +            "cannot create xcvr board when the rx id is \"%s\"" +        ) % dboard_id_t::none().to_pp_string())); +    } +    if (get_tx_id() == dboard_id_t::none()){ +        throw uhd::runtime_error(str(boost::format( +            "cannot create xcvr board when the tx id is \"%s\"" +        ) % dboard_id_t::none().to_pp_string())); +    } +} + +xcvr_dboard_base::~xcvr_dboard_base(void){ +    /* NOP */ +} + +/*********************************************************************** + * rx dboard dboard_base class + **********************************************************************/ +rx_dboard_base::rx_dboard_base(ctor_args_t args) : dboard_base(args){ +    if (get_tx_id() != dboard_id_t::none()){ +        throw uhd::runtime_error(str(boost::format( +            "cannot create rx board when the tx id is \"%s\"" +            " -> expected a tx id of \"%s\"" +        ) % get_tx_id().to_pp_string() % dboard_id_t::none().to_pp_string())); +    } +} + +rx_dboard_base::~rx_dboard_base(void){ +    /* NOP */ +} + +void rx_dboard_base::tx_get(const wax::obj &, wax::obj &){ +    throw uhd::runtime_error("cannot call tx_get on a rx dboard"); +} + +void rx_dboard_base::tx_set(const wax::obj &, const wax::obj &){ +    throw uhd::runtime_error("cannot call tx_set on a rx dboard"); +} + +/*********************************************************************** + * tx dboard dboard_base class + **********************************************************************/ +tx_dboard_base::tx_dboard_base(ctor_args_t args) : dboard_base(args){ +    if (get_rx_id() != dboard_id_t::none()){ +        throw uhd::runtime_error(str(boost::format( +            "cannot create tx board when the rx id is \"%s\"" +            " -> expected a rx id of \"%s\"" +        ) % get_rx_id().to_pp_string() % dboard_id_t::none().to_pp_string())); +    } +} + +tx_dboard_base::~tx_dboard_base(void){ +    /* NOP */ +} + +void tx_dboard_base::rx_get(const wax::obj &, wax::obj &){ +    throw uhd::runtime_error("cannot call rx_get on a tx dboard"); +} + +void tx_dboard_base::rx_set(const wax::obj &, const wax::obj &){ +    throw uhd::runtime_error("cannot call rx_set on a tx dboard"); +} diff --git a/host/lib/usrp/dboard_ctor_args.hpp b/host/lib/usrp/dboard_ctor_args.hpp new file mode 100644 index 000000000..708f2ea08 --- /dev/null +++ b/host/lib/usrp/dboard_ctor_args.hpp @@ -0,0 +1,36 @@ +// +// 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_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP + +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <string> + +namespace uhd{ namespace usrp{ + +    struct dboard_ctor_args_t{ +        std::string               sd_name; +        dboard_iface::sptr        db_iface; +        dboard_id_t               rx_id, tx_id; +    }; + +}} //namespace + +#endif /* INCLUDED_LIBUHD_USRP_DBOARD_CTOR_ARGS_HPP */ diff --git a/host/lib/usrp/dboard_eeprom.cpp b/host/lib/usrp/dboard_eeprom.cpp new file mode 100644 index 000000000..b3cb54c1c --- /dev/null +++ b/host/lib/usrp/dboard_eeprom.cpp @@ -0,0 +1,152 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <algorithm> +#include <sstream> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Utility functions + **********************************************************************/ + +//! create a string from a byte vector, return empty if invalid ascii +static const std::string bytes_to_string(const byte_vector_t &bytes){ +    std::string out; +    BOOST_FOREACH(boost::uint8_t byte, bytes){ +        if (byte < 32 or byte > 127) return out; +        out += byte; +    } +    return out; +} + +//! create a byte vector from a string, null terminate unless max length +static const byte_vector_t string_to_bytes(const std::string &string, size_t max_length){ +    byte_vector_t bytes; +    for (size_t i = 0; i < std::min(string.size(), max_length); i++){ +        bytes.push_back(string[i]); +    } +    if (bytes.size() < max_length - 1) bytes.push_back('\0'); +    return bytes; +} + +//////////////////////////////////////////////////////////////////////// +// format of daughterboard EEPROM +// 00: 0xDB code for ``I'm a daughterboard'' +// 01:   .. Daughterboard ID (LSB) +// 02:   .. Daughterboard ID (MSB) +// 03:   .. io bits  7-0 direction (bit set if it's an output from m'board) +// 04:   .. io bits 15-8 direction (bit set if it's an output from m'board) +// 05:   .. ADC0 DC offset correction (LSB) +// 06:   .. ADC0 DC offset correction (MSB) +// 07:   .. ADC1 DC offset correction (LSB) +// 08:   .. ADC1 DC offset correction (MSB) +//  ... +// 1f:   .. negative of the sum of bytes [0x00, 0x1e] + +#define DB_EEPROM_MAGIC         0x00 +#define DB_EEPROM_MAGIC_VALUE   0xDB +#define DB_EEPROM_ID_LSB        0x01 +#define DB_EEPROM_ID_MSB        0x02 +#define DB_EEPROM_OE_LSB        0x03 +#define DB_EEPROM_OE_MSB        0x04 +#define DB_EEPROM_OFFSET_0_LSB  0x05 // offset correction for ADC or DAC 0 +#define DB_EEPROM_OFFSET_0_MSB  0x06 +#define DB_EEPROM_OFFSET_1_LSB  0x07 // offset correction for ADC or DAC 1 +#define DB_EEPROM_OFFSET_1_MSB  0x08 +#define DB_EEPROM_SERIAL        0x09 +#define DB_EEPROM_SERIAL_LEN    0x09 //9 ASCII characters +#define DB_EEPROM_CHKSUM        0x1f + +#define DB_EEPROM_CLEN          0x20 // length of common portion of eeprom + +#define DB_EEPROM_CUSTOM_BASE   DB_EEPROM_CLEN // first avail offset for +                                               //   daughterboard specific use +//////////////////////////////////////////////////////////////////////// + +//negative sum of bytes excluding checksum byte +static boost::uint8_t checksum(const byte_vector_t &bytes){ +    int sum = 0; +    for (size_t i = 0; i < std::min(bytes.size(), size_t(DB_EEPROM_CHKSUM)); i++){ +        sum -= int(bytes.at(i)); +    } +    UHD_LOGV(often) << boost::format("sum: 0x%02x") % sum << std::endl; +    return boost::uint8_t(sum); +} + +dboard_eeprom_t::dboard_eeprom_t(void){ +    id = dboard_id_t::none(); +    serial = ""; +} + +void dboard_eeprom_t::load(i2c_iface &iface, boost::uint8_t addr){ +    byte_vector_t bytes = iface.read_eeprom(addr, 0, DB_EEPROM_CLEN); + +    std::ostringstream ss; +    for (size_t i = 0; i < bytes.size(); i++){ +        ss << boost::format( +            "eeprom byte[0x%02x] = 0x%02x") % i % int(bytes.at(i) +        ) << std::endl; +    } +    UHD_LOGV(often) << ss.str() << std::endl; + +    try{ +        UHD_ASSERT_THROW(bytes.size() >= DB_EEPROM_CLEN); +        UHD_ASSERT_THROW(bytes[DB_EEPROM_MAGIC] == DB_EEPROM_MAGIC_VALUE); +        UHD_ASSERT_THROW(bytes[DB_EEPROM_CHKSUM] == checksum(bytes)); + +        //parse the ids +        id = dboard_id_t::from_uint16(0 +            | (boost::uint16_t(bytes[DB_EEPROM_ID_LSB]) << 0) +            | (boost::uint16_t(bytes[DB_EEPROM_ID_MSB]) << 8) +        ); + +        //parse the serial +        serial = bytes_to_string( +            byte_vector_t(&bytes.at(DB_EEPROM_SERIAL), +            &bytes.at(DB_EEPROM_SERIAL+DB_EEPROM_SERIAL_LEN)) +        ); + +    }catch(const uhd::assertion_error &){ +        id = dboard_id_t::none(); +        serial = ""; +    } +} + +void dboard_eeprom_t::store(i2c_iface &iface, boost::uint8_t addr) const{ +    byte_vector_t bytes(DB_EEPROM_CLEN, 0); //defaults to all zeros +    bytes[DB_EEPROM_MAGIC] = DB_EEPROM_MAGIC_VALUE; + +    //load the id bytes +    bytes[DB_EEPROM_ID_LSB] = boost::uint8_t(id.to_uint16() >> 0); +    bytes[DB_EEPROM_ID_MSB] = boost::uint8_t(id.to_uint16() >> 8); + +    //load the serial bytes +    byte_vector_t ser_bytes = string_to_bytes(serial, DB_EEPROM_SERIAL_LEN); +    std::copy(ser_bytes.begin(), ser_bytes.end(), &bytes.at(DB_EEPROM_SERIAL)); + +    //load the checksum +    bytes[DB_EEPROM_CHKSUM] = checksum(bytes); + +    iface.write_eeprom(addr, 0, bytes); +} diff --git a/host/lib/usrp/dboard_id.cpp b/host/lib/usrp/dboard_id.cpp new file mode 100644 index 000000000..3028d2a3b --- /dev/null +++ b/host/lib/usrp/dboard_id.cpp @@ -0,0 +1,68 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_id.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <sstream> +#include <iostream> + +using namespace uhd::usrp; + +dboard_id_t::dboard_id_t(boost::uint16_t id){ +    _id = id; +} + +dboard_id_t dboard_id_t::none(void){ +    return dboard_id_t(); +} + +dboard_id_t dboard_id_t::from_uint16(boost::uint16_t uint16){ +    return dboard_id_t(uint16); +} + +boost::uint16_t dboard_id_t::to_uint16(void) const{ +    return _id; +} + +//used with lexical cast to parse a hex string +template <class T> struct to_hex{ +    T value; +    operator T() const {return value;} +    friend std::istream& operator>>(std::istream& in, to_hex& out){ +        in >> std::hex >> out.value; +        return in; +    } +}; + +dboard_id_t dboard_id_t::from_string(const std::string &string){ +    if (string.substr(0, 2) == "0x"){ +        return dboard_id_t::from_uint16(boost::lexical_cast<to_hex<boost::uint16_t> >(string)); +    } +    return dboard_id_t::from_uint16(boost::lexical_cast<boost::uint16_t>(string)); +} + +std::string dboard_id_t::to_string(void) const{ +    return str(boost::format("0x%04x") % this->to_uint16()); +} + +//Note: to_pp_string is implemented in the dboard manager +//because it needs access to the dboard registration table + +bool uhd::usrp::operator==(const dboard_id_t &lhs, const dboard_id_t &rhs){ +    return lhs.to_uint16() == rhs.to_uint16(); +} diff --git a/host/lib/usrp/dboard_iface.cpp b/host/lib/usrp/dboard_iface.cpp new file mode 100644 index 000000000..5cc5ea470 --- /dev/null +++ b/host/lib/usrp/dboard_iface.cpp @@ -0,0 +1,78 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> + +using namespace uhd::usrp; + +struct dboard_iface::impl{ +    uhd::dict<unit_t, boost::uint16_t> pin_ctrl_shadow; +    uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > atr_reg_shadow; +    uhd::dict<unit_t, boost::uint16_t> gpio_ddr_shadow; +    uhd::dict<unit_t, boost::uint16_t> gpio_out_shadow; +}; + +dboard_iface::dboard_iface(void){ +    _impl = UHD_PIMPL_MAKE(impl, ()); +} + +template <typename T> +static T shadow_it(T &shadow, const T &value, const T &mask){ +    shadow = (shadow & ~mask) | (value & mask); +    return shadow; +} + +void dboard_iface::set_pin_ctrl( +    unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ +    _set_pin_ctrl(unit, shadow_it(_impl->pin_ctrl_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_pin_ctrl(unit_t unit){ +    return _impl->pin_ctrl_shadow[unit]; +} + +void dboard_iface::set_atr_reg( +    unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask +){ +    _set_atr_reg(unit, reg, shadow_it(_impl->atr_reg_shadow[unit][reg], value, mask)); +} + +boost::uint16_t dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ +    return _impl->atr_reg_shadow[unit][reg]; +} + +void dboard_iface::set_gpio_ddr( +    unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ +    _set_gpio_ddr(unit, shadow_it(_impl->gpio_ddr_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_gpio_ddr(unit_t unit){ +    return _impl->gpio_ddr_shadow[unit]; +} + +void dboard_iface::set_gpio_out( +    unit_t unit, boost::uint16_t value, boost::uint16_t mask +){ +    _set_gpio_out(unit, shadow_it(_impl->gpio_out_shadow[unit], value, mask)); +} + +boost::uint16_t dboard_iface::get_gpio_out(unit_t unit){ +    return _impl->gpio_out_shadow[unit]; +} diff --git a/host/lib/usrp/dboard_manager.cpp b/host/lib/usrp/dboard_manager.cpp new file mode 100644 index 000000000..3f4b5a511 --- /dev/null +++ b/host/lib/usrp/dboard_manager.cpp @@ -0,0 +1,527 @@ +// +// 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 "dboard_ctor_args.hpp" +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/assign/list_of.hpp> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * dboard key class to use for look-up + **********************************************************************/ +class dboard_key_t{ +public: +    dboard_key_t(const dboard_id_t &id = dboard_id_t::none()): +        _rx_id(id), _tx_id(id), _xcvr(false){} + +    dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id): +        _rx_id(rx_id), _tx_id(tx_id), _xcvr(true){} + +    dboard_id_t xx_id(void) const{ +        UHD_ASSERT_THROW(not this->is_xcvr()); +        return this->_rx_id; +    } + +    dboard_id_t rx_id(void) const{ +        UHD_ASSERT_THROW(this->is_xcvr()); +        return this->_rx_id; +    } + +    dboard_id_t tx_id(void) const{ +        UHD_ASSERT_THROW(this->is_xcvr()); +        return this->_tx_id; +    } + +    bool is_xcvr(void) const{ +        return this->_xcvr; +    } + +private: +    dboard_id_t _rx_id, _tx_id; +    bool _xcvr; +}; + +bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){ +    if (lhs.is_xcvr() and rhs.is_xcvr()) +        return lhs.rx_id() == rhs.rx_id() and lhs.tx_id() == rhs.tx_id(); +    if (not lhs.is_xcvr() and not rhs.is_xcvr()) +        return lhs.xx_id() == rhs.xx_id(); +    return false; +} + +/*********************************************************************** + * storage and registering for dboards + **********************************************************************/ +//dboard registry tuple: dboard constructor, canonical name, subdev names +typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, prop_names_t> args_t; + +//map a dboard id to a dboard constructor +typedef uhd::dict<dboard_key_t, args_t> id_to_args_map_t; +UHD_SINGLETON_FCN(id_to_args_map_t, get_id_to_args_map) + +static void register_dboard_key( +    const dboard_key_t &dboard_key, +    dboard_manager::dboard_ctor_t dboard_ctor, +    const std::string &name, +    const prop_names_t &subdev_names +){ +    UHD_LOGV(always) << "registering: " << name << std::endl; +    if (get_id_to_args_map().has_key(dboard_key)){ + +        if (dboard_key.is_xcvr()) throw uhd::key_error(str(boost::format( +            "The dboard id pair [%s, %s] is already registered to %s." +        ) % dboard_key.rx_id().to_string() % dboard_key.tx_id().to_string() % get_id_to_args_map()[dboard_key].get<1>())); + +        else throw uhd::key_error(str(boost::format( +            "The dboard id %s is already registered to %s." +        ) % dboard_key.xx_id().to_string() % get_id_to_args_map()[dboard_key].get<1>())); + +    } +    get_id_to_args_map()[dboard_key] = args_t(dboard_ctor, name, subdev_names); +} + +void dboard_manager::register_dboard( +    const dboard_id_t &dboard_id, +    dboard_ctor_t dboard_ctor, +    const std::string &name, +    const prop_names_t &subdev_names +){ +    register_dboard_key(dboard_key_t(dboard_id), dboard_ctor, name, subdev_names); +} + +void dboard_manager::register_dboard( +    const dboard_id_t &rx_dboard_id, +    const dboard_id_t &tx_dboard_id, +    dboard_ctor_t dboard_ctor, +    const std::string &name, +    const prop_names_t &subdev_names +){ +    register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), dboard_ctor, name, subdev_names); +} + +std::string dboard_id_t::to_cname(void) const{ +    std::string cname; +    BOOST_FOREACH(const dboard_key_t &key, get_id_to_args_map().keys()){ +        if ( +            (not key.is_xcvr() and *this == key.xx_id()) or +            (key.is_xcvr() and (*this == key.rx_id() or *this == key.tx_id())) +        ){ +            if (not cname.empty()) cname += ", "; +            cname += get_id_to_args_map()[key].get<1>(); +        } +    } +    return (cname.empty())? "Unknown" : cname; +} + +std::string dboard_id_t::to_pp_string(void) const{ +    return str(boost::format("%s (%s)") % this->to_cname() % this->to_string()); +} + +/*********************************************************************** + * internal helper classes + **********************************************************************/ +/*! + * A special wax proxy object that forwards calls to a subdev. + * A sptr to an instance will be used in the properties structure.  + */ +class subdev_proxy : boost::noncopyable, public wax::obj{ +public: +    typedef boost::shared_ptr<subdev_proxy> sptr; +    enum type_t{RX_TYPE, TX_TYPE}; + +    //structors +    subdev_proxy(dboard_base::sptr subdev, type_t type): +        _subdev(subdev), _type(type) +    { +        /* NOP */ +    } + +private: +    dboard_base::sptr   _subdev; +    type_t              _type; + +    //forward the get calls to the rx or tx +    void get(const wax::obj &key, wax::obj &val){ +        switch(_type){ +        case RX_TYPE: return _subdev->rx_get(key, val); +        case TX_TYPE: return _subdev->tx_get(key, val); +        } +    } + +    //forward the set calls to the rx or tx +    void set(const wax::obj &key, const wax::obj &val){ +        switch(_type){ +        case RX_TYPE: return _subdev->rx_set(key, val); +        case TX_TYPE: return _subdev->tx_set(key, val); +        } +    } +}; + +/*********************************************************************** + * dboard manager implementation class + **********************************************************************/ +class dboard_manager_impl : public dboard_manager{ + +public: +    dboard_manager_impl( +        dboard_id_t rx_dboard_id, +        dboard_id_t tx_dboard_id, +        dboard_iface::sptr iface +    ); +    ~dboard_manager_impl(void); + +    //dboard_iface +    prop_names_t get_rx_subdev_names(void); +    prop_names_t get_tx_subdev_names(void); +    wax::obj get_rx_subdev(const std::string &subdev_name); +    wax::obj get_tx_subdev(const std::string &subdev_name); + +private: +    void init(dboard_id_t, dboard_id_t); +    //list of rx and tx dboards in this dboard_manager +    //each dboard here is actually a subdevice proxy +    //the subdevice proxy is internal to the cpp file +    uhd::dict<std::string, subdev_proxy::sptr> _rx_dboards; +    uhd::dict<std::string, subdev_proxy::sptr> _tx_dboards; +    dboard_iface::sptr _iface; +    void set_nice_dboard_if(void); +}; + +/*********************************************************************** + * make routine for dboard manager + **********************************************************************/ +dboard_manager::sptr dboard_manager::make( +    dboard_id_t rx_dboard_id, +    dboard_id_t tx_dboard_id, +    dboard_iface::sptr iface +){ +    return dboard_manager::sptr( +        new dboard_manager_impl(rx_dboard_id, tx_dboard_id, iface) +    ); +} + +/*********************************************************************** + * implementation class methods + **********************************************************************/ +dboard_manager_impl::dboard_manager_impl( +    dboard_id_t rx_dboard_id, +    dboard_id_t tx_dboard_id, +    dboard_iface::sptr iface +): +    _iface(iface) +{ +    try{ +        this->init(rx_dboard_id, tx_dboard_id); +    } +    catch(const std::exception &e){ +        UHD_MSG(error) << "The daughterboard manager encountered a recoverable error in init" << std::endl << e.what(); +        this->init(dboard_id_t::none(), dboard_id_t::none()); +    } +} + +void dboard_manager_impl::init( +    dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id +){ +    //find the dboard key matches for the dboard ids +    dboard_key_t rx_dboard_key, tx_dboard_key, xcvr_dboard_key; +    BOOST_FOREACH(const dboard_key_t &key, get_id_to_args_map().keys()){ +        if (key.is_xcvr()){ +            if (rx_dboard_id == key.rx_id() and tx_dboard_id == key.tx_id()) xcvr_dboard_key = key; +            if (rx_dboard_id == key.rx_id()) rx_dboard_key = key; //kept to handle warning +            if (tx_dboard_id == key.tx_id()) tx_dboard_key = key; //kept to handle warning +        } +        else{ +            if (rx_dboard_id == key.xx_id()) rx_dboard_key = key; +            if (tx_dboard_id == key.xx_id()) tx_dboard_key = key; +        } +    } + +    //warn for invalid dboard id xcvr combinations +    if (not xcvr_dboard_key.is_xcvr() and (rx_dboard_key.is_xcvr() or tx_dboard_key.is_xcvr())){ +        UHD_MSG(warning) << boost::format( +            "Unknown transceiver board ID combination.\n" +            "Is your daughter-board mounted properly?\n" +            "RX dboard ID: %s\n" +            "TX dboard ID: %s\n" +        ) % rx_dboard_id.to_pp_string() % tx_dboard_id.to_pp_string(); +    } + +    //initialize the gpio pins before creating subdevs +    set_nice_dboard_if(); + +    //dboard constructor args +    dboard_ctor_args_t db_ctor_args; +    db_ctor_args.db_iface = _iface; + +    //make xcvr subdevs +    if (xcvr_dboard_key.is_xcvr()){ + +        //extract data for the xcvr dboard key +        dboard_ctor_t dboard_ctor; std::string name; prop_names_t subdevs; +        boost::tie(dboard_ctor, name, subdevs) = get_id_to_args_map()[xcvr_dboard_key]; + +        //create the xcvr object for each subdevice +        BOOST_FOREACH(const std::string &subdev, subdevs){ +            db_ctor_args.sd_name = subdev; +            db_ctor_args.rx_id = rx_dboard_id; +            db_ctor_args.tx_id = tx_dboard_id; +            dboard_base::sptr xcvr_dboard = dboard_ctor(&db_ctor_args); +            //create a rx proxy for this xcvr board +            _rx_dboards[subdev] = subdev_proxy::sptr( +                new subdev_proxy(xcvr_dboard, subdev_proxy::RX_TYPE) +            ); +            //create a tx proxy for this xcvr board +            _tx_dboards[subdev] = subdev_proxy::sptr( +                new subdev_proxy(xcvr_dboard, subdev_proxy::TX_TYPE) +            ); +        } +    } + +    //make tx and rx subdevs (separate subdevs for rx and tx dboards) +    else{ + +        //force the rx key to the unknown board for bad combinations +        if (rx_dboard_key.is_xcvr() or rx_dboard_key.xx_id() == dboard_id_t::none()){ +            rx_dboard_key = dboard_key_t(0xfff1); +        } + +        //extract data for the rx dboard key +        dboard_ctor_t rx_dboard_ctor; std::string rx_name; prop_names_t rx_subdevs; +        boost::tie(rx_dboard_ctor, rx_name, rx_subdevs) = get_id_to_args_map()[rx_dboard_key]; + +        //make the rx subdevs +        BOOST_FOREACH(const std::string &subdev, rx_subdevs){ +            db_ctor_args.sd_name = subdev; +            db_ctor_args.rx_id = rx_dboard_id; +            db_ctor_args.tx_id = dboard_id_t::none(); +            dboard_base::sptr rx_dboard = rx_dboard_ctor(&db_ctor_args); +            //create a rx proxy for this rx board +            _rx_dboards[subdev] = subdev_proxy::sptr( +                new subdev_proxy(rx_dboard, subdev_proxy::RX_TYPE) +            ); +        } + +        //force the tx key to the unknown board for bad combinations +        if (tx_dboard_key.is_xcvr() or tx_dboard_key.xx_id() == dboard_id_t::none()){ +            tx_dboard_key = dboard_key_t(0xfff0); +        } + +        //extract data for the tx dboard key +        dboard_ctor_t tx_dboard_ctor; std::string tx_name; prop_names_t tx_subdevs; +        boost::tie(tx_dboard_ctor, tx_name, tx_subdevs) = get_id_to_args_map()[tx_dboard_key]; + +        //make the tx subdevs +        BOOST_FOREACH(const std::string &subdev, tx_subdevs){ +            db_ctor_args.sd_name = subdev; +            db_ctor_args.rx_id = dboard_id_t::none(); +            db_ctor_args.tx_id = tx_dboard_id; +            dboard_base::sptr tx_dboard = tx_dboard_ctor(&db_ctor_args); +            //create a tx proxy for this tx board +            _tx_dboards[subdev] = subdev_proxy::sptr( +                new subdev_proxy(tx_dboard, subdev_proxy::TX_TYPE) +            ); +        } +    } +} + +dboard_manager_impl::~dboard_manager_impl(void){UHD_SAFE_CALL( +    set_nice_dboard_if(); +)} + +prop_names_t dboard_manager_impl::get_rx_subdev_names(void){ +    return _rx_dboards.keys(); +} + +prop_names_t dboard_manager_impl::get_tx_subdev_names(void){ +    return _tx_dboards.keys(); +} + +wax::obj dboard_manager_impl::get_rx_subdev(const std::string &subdev_name){ +    if (not _rx_dboards.has_key(subdev_name)) throw uhd::key_error( +        str(boost::format("Unknown rx subdev name %s") % subdev_name) +    ); +    //get a link to the rx subdev proxy +    return _rx_dboards[subdev_name]->get_link(); +} + +wax::obj dboard_manager_impl::get_tx_subdev(const std::string &subdev_name){ +    if (not _tx_dboards.has_key(subdev_name)) throw uhd::key_error( +        str(boost::format("Unknown tx subdev name %s") % subdev_name) +    ); +    //get a link to the tx subdev proxy +    return _tx_dboards[subdev_name]->get_link(); +} + +void dboard_manager_impl::set_nice_dboard_if(void){ +    //make a list of possible unit types +    std::vector<dboard_iface::unit_t> units = boost::assign::list_of +        (dboard_iface::UNIT_RX) +        (dboard_iface::UNIT_TX) +    ; + +    //set nice settings on each unit +    BOOST_FOREACH(dboard_iface::unit_t unit, units){ +        _iface->set_gpio_ddr(unit, 0x0000); //all inputs +        _iface->set_gpio_out(unit, 0x0000); //all low +        _iface->set_pin_ctrl(unit, 0x0000); //all gpio +        _iface->set_clock_enabled(unit, false); //clock off +    } + +    //disable all rx subdevices +    BOOST_FOREACH(const std::string &sd_name, this->get_rx_subdev_names()){ +        this->get_rx_subdev(sd_name)[SUBDEV_PROP_ENABLED] = false; +    } + +    //disable all tx subdevices +    BOOST_FOREACH(const std::string &sd_name, this->get_tx_subdev_names()){ +        this->get_tx_subdev(sd_name)[SUBDEV_PROP_ENABLED] = false; +    } +} + +/*********************************************************************** + * Populate a properties tree from a subdev waxy object + **********************************************************************/ +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> + +static sensor_value_t get_sensor(wax::obj subdev, const std::string &name){ +    return subdev[named_prop_t(SUBDEV_PROP_SENSOR, name)].as<sensor_value_t>(); +} + +static void set_gain(wax::obj subdev, const std::string &name, const double gain){ +    subdev[named_prop_t(SUBDEV_PROP_GAIN, name)] = gain; +} + +static double get_gain(wax::obj subdev, const std::string &name){ +    return subdev[named_prop_t(SUBDEV_PROP_GAIN, name)].as<double>(); +} + +static meta_range_t get_gain_range(wax::obj subdev, const std::string &name){ +    return subdev[named_prop_t(SUBDEV_PROP_GAIN_RANGE, name)].as<meta_range_t>(); +} + +static void set_freq(wax::obj subdev, const double freq){ +    subdev[SUBDEV_PROP_FREQ] = freq; +} + +static double get_freq(wax::obj subdev){ +    return subdev[SUBDEV_PROP_FREQ].as<double>(); +} + +static meta_range_t get_freq_range(wax::obj subdev){ +    return subdev[SUBDEV_PROP_FREQ_RANGE].as<meta_range_t>(); +} + +static void set_ant(wax::obj subdev, const std::string &ant){ +    subdev[SUBDEV_PROP_ANTENNA] = ant; +} + +static std::string get_ant(wax::obj subdev){ +    return subdev[SUBDEV_PROP_ANTENNA].as<std::string>(); +} + +static std::vector<std::string> get_ants(wax::obj subdev){ +    return subdev[SUBDEV_PROP_ANTENNA_NAMES].as<std::vector<std::string> >(); +} + +static std::string get_conn(wax::obj subdev){ +    switch(subdev[SUBDEV_PROP_CONNECTION].as<subdev_conn_t>()){ +    case SUBDEV_CONN_COMPLEX_IQ: return "IQ"; +    case SUBDEV_CONN_COMPLEX_QI: return "QI"; +    case SUBDEV_CONN_REAL_I: return "I"; +    case SUBDEV_CONN_REAL_Q: return "Q"; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +static bool get_use_lo_off(wax::obj subdev){ +    return subdev[SUBDEV_PROP_USE_LO_OFFSET].as<bool>(); +} + +static bool get_set_enb(wax::obj subdev, const bool enb){ +    subdev[SUBDEV_PROP_ENABLED] = enb; +    return subdev[SUBDEV_PROP_ENABLED].as<bool>(); +} + +static void set_bw(wax::obj subdev, const double freq){ +    subdev[SUBDEV_PROP_BANDWIDTH] = freq; +} + +static double get_bw(wax::obj subdev){ +    return subdev[SUBDEV_PROP_BANDWIDTH].as<double>(); +} + +void dboard_manager::populate_prop_tree_from_subdev( +    property_tree::sptr subtree, wax::obj subdev +){ +    subtree->create<std::string>("name").set(subdev[SUBDEV_PROP_NAME].as<std::string>()); + +    const prop_names_t sensor_names = subdev[SUBDEV_PROP_SENSOR_NAMES].as<prop_names_t>(); +    BOOST_FOREACH(const std::string &name, sensor_names){ +        subtree->create<sensor_value_t>("sensors/" + name) +            .publish(boost::bind(&get_sensor, subdev, name)); +    } + +    const prop_names_t gain_names = subdev[SUBDEV_PROP_GAIN_NAMES].as<prop_names_t>(); +    subtree->create<int>("gains"); //phony property so this dir exists +    BOOST_FOREACH(const std::string &name, gain_names){ +        subtree->create<double>("gains/" + name + "/value") +            .publish(boost::bind(&get_gain, subdev, name)) +            .subscribe(boost::bind(&set_gain, subdev, name, _1)); +        subtree->create<meta_range_t>("gains/" + name + "/range") +            .publish(boost::bind(&get_gain_range, subdev, name)); +    } + +    subtree->create<double>("freq/value") +        .publish(boost::bind(&get_freq, subdev)) +        .subscribe(boost::bind(&set_freq, subdev, _1)); + +    subtree->create<meta_range_t>("freq/range") +        .publish(boost::bind(&get_freq_range, subdev)); + +    subtree->create<std::string>("antenna/value") +        .publish(boost::bind(&get_ant, subdev)) +        .subscribe(boost::bind(&set_ant, subdev, _1)); + +    subtree->create<std::vector<std::string> >("antenna/options") +        .publish(boost::bind(&get_ants, subdev)); + +    subtree->create<std::string>("connection") +        .publish(boost::bind(&get_conn, subdev)); + +    subtree->create<bool>("enabled") +        .coerce(boost::bind(&get_set_enb, subdev, _1)); + +    subtree->create<bool>("use_lo_offset") +        .publish(boost::bind(&get_use_lo_off, subdev)); + +    subtree->create<double>("bandwidth/value") +        .publish(boost::bind(&get_bw, subdev)) +        .subscribe(boost::bind(&set_bw, subdev, _1)); +} diff --git a/host/lib/usrp/e100/CMakeLists.txt b/host/lib/usrp/e100/CMakeLists.txt new file mode 100644 index 000000000..ac9d8c655 --- /dev/null +++ b/host/lib/usrp/e100/CMakeLists.txt @@ -0,0 +1,40 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP-E100 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF) + +IF(ENABLE_E100) +    INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include) + +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e100_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e100_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/e100_mmap_zero_copy.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/fpga_downloader.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp +    ) +ENDIF(ENABLE_E100) diff --git a/host/lib/usrp/e100/clock_ctrl.cpp b/host/lib/usrp/e100/clock_ctrl.cpp new file mode 100644 index 000000000..6acb13528 --- /dev/null +++ b/host/lib/usrp/e100/clock_ctrl.cpp @@ -0,0 +1,510 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9522_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/cstdint.hpp> +#include "e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <algorithm> +#include <utility> + +using namespace uhd; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const bool ENABLE_THE_TEST_OUT = true; +static const double REFERENCE_INPUT_RATE = 10e6; + +/*********************************************************************** + * Helpers + **********************************************************************/ +template <typename div_type, typename bypass_type> static void set_clock_divider( +    size_t divider, div_type &low, div_type &high, bypass_type &bypass +){ +    high = divider/2 - 1; +    low = divider - high - 2; +    bypass = (divider == 1)? 1 : 0; +} + +/*********************************************************************** + * Clock rate calculation stuff: + *   Using the internal VCO between 1400 and 1800 MHz + **********************************************************************/ +struct clock_settings_type{ +    size_t ref_clock_doubler, r_counter, a_counter, b_counter, prescaler, vco_divider, chan_divider; +    size_t get_n_counter(void) const{return prescaler * b_counter + a_counter;} +    double get_ref_rate(void) const{return REFERENCE_INPUT_RATE * ref_clock_doubler;} +    double get_vco_rate(void) const{return get_ref_rate()/r_counter * get_n_counter();} +    double get_chan_rate(void) const{return get_vco_rate()/vco_divider;} +    double get_out_rate(void) const{return get_chan_rate()/chan_divider;} +    std::string to_pp_string(void) const{ +        return str(boost::format( +            "  r_counter: %d\n" +            "  a_counter: %d\n" +            "  b_counter: %d\n" +            "  prescaler: %d\n" +            "  vco_divider: %d\n" +            "  chan_divider: %d\n" +            "  vco_rate: %fMHz\n" +            "  chan_rate: %fMHz\n" +            "  out_rate: %fMHz\n" +            ) +            % r_counter +            % a_counter +            % b_counter +            % prescaler +            % vco_divider +            % chan_divider +            % (get_vco_rate()/1e6) +            % (get_chan_rate()/1e6) +            % (get_out_rate()/1e6) +        ); +    } +}; + +//! gives the greatest divisor of num between 1 and max inclusive +template<typename T> static inline T greatest_divisor(T num, T max){ +    for (T i = max; i > 1; i--) if (num%i == 0) return i; return 1; +} + +//! gives the least divisor of num between min and num exclusive +template<typename T> static inline T least_divisor(T num, T min){ +    for (T i = min; i < num; i++) if (num%i == 0) return i; return 1; +} + +static clock_settings_type get_clock_settings(double rate){ +    clock_settings_type cs; +    cs.ref_clock_doubler = 2; //always doubling +    cs.prescaler = 8; //set to 8 when input is under 2400 MHz + +    //basic formulas used below: +    //out_rate*X = ref_rate*Y +    //X = i*ref_rate/gcd +    //Y = i*out_rate/gcd +    //X = chan_div * vco_div * R +    //Y = P*B + A + +    const boost::uint64_t out_rate = boost::uint64_t(rate); +    const boost::uint64_t ref_rate = boost::uint64_t(cs.get_ref_rate()); +    const size_t gcd = size_t(boost::math::gcd(ref_rate, out_rate)); + +    for (size_t i = 1; i <= 100; i++){ +        const size_t X = i*ref_rate/gcd; +        const size_t Y = i*out_rate/gcd; + +        //determine A and B (P is fixed) +        cs.b_counter = Y/cs.prescaler; +        cs.a_counter = Y - cs.b_counter*cs.prescaler; + +        static const double vco_bound_pad = 100e6; +        for ( //calculate an r divider that fits into the bounds of the vco +            cs.r_counter  = size_t(cs.get_n_counter()*cs.get_ref_rate()/(1800e6 - vco_bound_pad)); +            cs.r_counter <= size_t(cs.get_n_counter()*cs.get_ref_rate()/(1400e6 + vco_bound_pad)) +            and cs.r_counter > 0; cs.r_counter++ +        ){ + +            //determine chan_div and vco_div +            //and fill in that order of preference +            cs.chan_divider = greatest_divisor<size_t>(X/cs.r_counter, 32); +            cs.vco_divider = greatest_divisor<size_t>(X/cs.chan_divider/cs.r_counter, 6); + +            //avoid a vco divider of 1 (if possible) +            if (cs.vco_divider == 1){ +                cs.vco_divider = least_divisor<size_t>(cs.chan_divider, 2); +                cs.chan_divider /= cs.vco_divider; +            } + +            UHD_LOGV(always) +                << "gcd " << gcd << std::endl +                << "X " << X << std::endl +                << "Y " << Y << std::endl +                << cs.to_pp_string() << std::endl +            ; + +            //filter limits on the counters +            if (cs.vco_divider == 1) continue; +            if (cs.r_counter >= (1<<14)) continue; +            if (cs.b_counter == 2) continue; +            if (cs.b_counter == 1 and cs.a_counter != 0) continue; +            if (cs.b_counter >= (1<<13)) continue; +            if (cs.a_counter >= (1<<6)) continue; + +            UHD_MSG(status) << "USRP-E100 clock control: " << i << std::endl << cs.to_pp_string() << std::endl; +            return cs; +        } +    } + +    throw uhd::value_error(str(boost::format( +        "USRP-E100 clock control: could not calculate settings for clock rate %fMHz" +    ) % (rate/1e6))); +} + +/*********************************************************************** + * Clock Control Implementation + **********************************************************************/ +class e100_clock_ctrl_impl : public e100_clock_ctrl{ +public: +    e100_clock_ctrl_impl(spi_iface::sptr iface, double master_clock_rate){ +        _iface = iface; +        _chan_rate = 0.0; +        _out_rate = 0.0; + +        //perform soft-reset +        _ad9522_regs.soft_reset = 1; +        this->send_reg(0x000); +        this->latch_regs(); +        _ad9522_regs.soft_reset = 0; + +        //init the clock gen registers +        //Note: out0 should already be clocking the FPGA or this isnt going to work +        _ad9522_regs.sdo_active = ad9522_regs_t::SDO_ACTIVE_SDO_SDIO; +        _ad9522_regs.enb_stat_eeprom_at_stat_pin = 0; //use status pin +        _ad9522_regs.status_pin_control = 0x1; //n divider +        _ad9522_regs.ld_pin_control = 0x00; //dld +        _ad9522_regs.refmon_pin_control = 0x12; //show ref2 +        _ad9522_regs.lock_detect_counter = ad9522_regs_t::LOCK_DETECT_COUNTER_16CYC; + +        this->use_internal_ref(); + +        //initialize the FPGA clock rate +        UHD_MSG(status) << boost::format("Initializing FPGA clock to %fMHz...") % (master_clock_rate/1e6) << std::endl; +        this->set_fpga_clock_rate(master_clock_rate); + +        this->enable_test_clock(ENABLE_THE_TEST_OUT); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +    } + +    ~e100_clock_ctrl_impl(void){ +        this->enable_test_clock(ENABLE_THE_TEST_OUT); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +    } + +    /*********************************************************************** +     * Clock rate control: +     *  - set clock rate w/ internal VCO +     *  - set clock rate w/ external VCXO +     **********************************************************************/ +    void set_clock_settings_with_internal_vco(double rate){ +        const clock_settings_type cs = get_clock_settings(rate); + +        //set the rates to private variables so the implementation knows! +        _chan_rate = cs.get_chan_rate(); +        _out_rate = cs.get_out_rate(); + +        _ad9522_regs.enable_clock_doubler = (cs.ref_clock_doubler == 2)? 1 : 0; + +        _ad9522_regs.set_r_counter(cs.r_counter); +        _ad9522_regs.a_counter = cs.a_counter; +        _ad9522_regs.set_b_counter(cs.b_counter); +        UHD_ASSERT_THROW(cs.prescaler == 8); //assumes this below: +        _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV8_9; + +        _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; + +        _ad9522_regs.bypass_vco_divider = 0; +        switch(cs.vco_divider){ +        case 1: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV1; break; +        case 2: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV2; break; +        case 3: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV3; break; +        case 4: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV4; break; +        case 5: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV5; break; +        case 6: _ad9522_regs.vco_divider = ad9522_regs_t::VCO_DIVIDER_DIV6; break; +        } +        _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_VCO; + +        //setup fpga master clock +        _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; +        set_clock_divider(cs.chan_divider, +            _ad9522_regs.divider0_low_cycles, +            _ad9522_regs.divider0_high_cycles, +            _ad9522_regs.divider0_bypass +        ); + +        //setup codec clock +        _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; +        set_clock_divider(cs.chan_divider, +            _ad9522_regs.divider1_low_cycles, +            _ad9522_regs.divider1_high_cycles, +            _ad9522_regs.divider1_bypass +        ); + +        this->send_all_regs(); +        calibrate_now(); +    } + +    void set_clock_settings_with_external_vcxo(double rate){ +        //set the rates to private variables so the implementation knows! +        _chan_rate = rate; +        _out_rate = rate; + +        _ad9522_regs.enable_clock_doubler = 1; //doubler always on +        const double ref_rate = REFERENCE_INPUT_RATE*2; + +        //bypass prescaler such that N = B +        long gcd = boost::math::gcd(long(ref_rate), long(rate)); +        _ad9522_regs.set_r_counter(int(ref_rate/gcd)); +        _ad9522_regs.a_counter = 0; +        _ad9522_regs.set_b_counter(int(rate/gcd)); +        _ad9522_regs.prescaler_p = ad9522_regs_t::PRESCALER_P_DIV1; + +        //setup external vcxo +        _ad9522_regs.pll_power_down = ad9522_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9522_regs.cp_current = ad9522_regs_t::CP_CURRENT_1_2MA; +        _ad9522_regs.bypass_vco_divider = 1; +        _ad9522_regs.select_vco_or_clock = ad9522_regs_t::SELECT_VCO_OR_CLOCK_EXTERNAL; + +        //setup fpga master clock +        _ad9522_regs.out0_format = ad9522_regs_t::OUT0_FORMAT_LVDS; +        _ad9522_regs.divider0_bypass = 1; + +        //setup codec clock +        _ad9522_regs.out3_format = ad9522_regs_t::OUT3_FORMAT_LVDS; +        _ad9522_regs.divider1_bypass = 1; + +        this->send_all_regs(); +    } + +    void set_fpga_clock_rate(double rate){ +        if (_out_rate == rate) return; +        if (rate == 61.44e6) set_clock_settings_with_external_vcxo(rate); +        else                 set_clock_settings_with_internal_vco(rate); +        set_rx_dboard_clock_rate(rate); +        set_tx_dboard_clock_rate(rate); +    } + +    double get_fpga_clock_rate(void){ +        return this->_out_rate; +    } + +    /*********************************************************************** +     * Special test clock output +     **********************************************************************/ +    void enable_test_clock(bool enb){ +        //setup test clock (same divider as codec clock) +        _ad9522_regs.out4_format = ad9522_regs_t::OUT4_FORMAT_CMOS; +        _ad9522_regs.out4_cmos_configuration = (enb)? +            ad9522_regs_t::OUT4_CMOS_CONFIGURATION_A_ON : +            ad9522_regs_t::OUT4_CMOS_CONFIGURATION_OFF; +        this->send_reg(0x0F4); +        this->latch_regs(); +    } + +    /*********************************************************************** +     * RX Dboard Clock Control (output 9, divider 3) +     **********************************************************************/ +    void enable_rx_dboard_clock(bool enb){ +        _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: +    spi_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; +        _iface->write_spi( +            UE_SPI_SS_AD9522, +            spi_config_t::EDGE_RISE, +            reg, 24 +        ); +    } + +    void calibrate_now(void){ +        //vco calibration routine: +        _ad9522_regs.vco_calibration_now = 0; +        this->send_reg(0x18); +        this->latch_regs(); +        _ad9522_regs.vco_calibration_now = 1; +        this->send_reg(0x18); +        this->latch_regs(); +        //wait for calibration done: +        static const boost::uint8_t addr = 0x01F; +        for (size_t ms10 = 0; ms10 < 100; ms10++){ +            boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +            boost::uint32_t reg = _iface->read_spi( +                UE_SPI_SS_AD9522, spi_config_t::EDGE_RISE, +                _ad9522_regs.get_read_reg(addr), 24 +            ); +            _ad9522_regs.set_reg(addr, reg); +            if (_ad9522_regs.vco_calibration_finished) goto wait_for_ld; +        } +        UHD_MSG(error) << "USRP-E100 clock control: VCO calibration timeout" << std::endl; +        wait_for_ld: +        //wait for digital lock detect: +        for (size_t ms10 = 0; ms10 < 100; ms10++){ +            boost::this_thread::sleep(boost::posix_time::milliseconds(10)); +            boost::uint32_t reg = _iface->read_spi( +                UE_SPI_SS_AD9522, spi_config_t::EDGE_RISE, +                _ad9522_regs.get_read_reg(addr), 24 +            ); +            _ad9522_regs.set_reg(addr, reg); +            if (_ad9522_regs.digital_lock_detect) return; +        } +        UHD_MSG(error) << "USRP-E100 clock control: lock detection timeout" << std::endl; +    } + +    void soft_sync(void){ +        _ad9522_regs.soft_sync = 1; +        this->send_reg(0x230); +        this->latch_regs(); +        _ad9522_regs.soft_sync = 0; +        this->send_reg(0x230); +        this->latch_regs(); +    } + +    void send_all_regs(void){ +        //setup a list of register ranges to write +        typedef std::pair<boost::uint16_t, boost::uint16_t> range_t; +        static const std::vector<range_t> ranges = boost::assign::list_of +            (range_t(0x000, 0x000)) (range_t(0x010, 0x01F)) +            (range_t(0x0F0, 0x0FD)) (range_t(0x190, 0x19B)) +            (range_t(0x1E0, 0x1E1)) (range_t(0x230, 0x230)) +        ; + +        //write initial register values and latch/update +        BOOST_FOREACH(const range_t &range, ranges){ +            for(boost::uint16_t addr = range.first; addr <= range.second; addr++){ +                this->send_reg(addr); +            } +        } +        this->latch_regs(); +    } +}; + +/*********************************************************************** + * Clock Control Make + **********************************************************************/ +e100_clock_ctrl::sptr e100_clock_ctrl::make(spi_iface::sptr iface, double master_clock_rate){ +    return sptr(new e100_clock_ctrl_impl(iface, master_clock_rate)); +} diff --git a/host/lib/usrp/e100/clock_ctrl.hpp b/host/lib/usrp/e100/clock_ctrl.hpp new file mode 100644 index 000000000..7c16649d3 --- /dev/null +++ b/host/lib/usrp/e100/clock_ctrl.hpp @@ -0,0 +1,123 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_CLOCK_CTRL_HPP +#define INCLUDED_USRP_E100_CLOCK_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +/*! + * The usrp-e clock control: + * - Setup system clocks. + * - Disable/enable clock lines. + */ +class e100_clock_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<e100_clock_ctrl> sptr; + +    /*! +     * Make a new clock control object. +     * \param iface the spi iface object +     * \param master clock rate the FPGA rate +     * \return the clock control object +     */ +    static sptr make(uhd::spi_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; + +    /*! +     * Get the current rx dboard clock rate. +     * \return the clock rate in Hz +     */ +    virtual double get_rx_clock_rate(void) = 0; + +    /*! +     * Get the current tx dboard clock rate. +     * \return the clock rate in Hz +     */ +    virtual double get_tx_clock_rate(void) = 0; + +    /*! +     * Enable/disable the rx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_rx_dboard_clock(bool enb) = 0; + +    /*! +     * Enable/disable the tx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_tx_dboard_clock(bool enb) = 0; +     +    /*! +     * Use the internal TCXO reference +     */ +    virtual void use_internal_ref(void) = 0; +     +    /*! +     * Use the external SMA reference +     */ +    virtual void use_external_ref(void) = 0; +     +    /*! +     * Use external if available, internal otherwise +     */ +    virtual void use_auto_ref(void) = 0; + +}; + +#endif /* INCLUDED_USRP_E100_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/e100/codec_ctrl.cpp b/host/lib/usrp/e100/codec_ctrl.cpp new file mode 100644 index 000000000..6efeb10e8 --- /dev/null +++ b/host/lib/usrp/e100/codec_ctrl.cpp @@ -0,0 +1,280 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/cstdint.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include "e100_regs.hpp" //spi slave constants +#include <boost/assign/list_of.hpp> + +using namespace uhd; + +const gain_range_t e100_codec_ctrl::tx_pga_gain_range(-20, 0, double(0.1)); +const gain_range_t e100_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class e100_codec_ctrl_impl : public e100_codec_ctrl{ +public: +    //structors +    e100_codec_ctrl_impl(spi_iface::sptr iface); +    ~e100_codec_ctrl_impl(void); + +    //aux adc and dac control +    double read_aux_adc(aux_adc_t which); +    void write_aux_dac(aux_dac_t which, double volts); + +    //pga gain control +    void set_tx_pga_gain(double); +    double get_tx_pga_gain(void); +    void set_rx_pga_gain(double, char); +    double get_rx_pga_gain(char); + +private: +    spi_iface::sptr _iface; +    ad9862_regs_t _ad9862_regs; +    void send_reg(boost::uint8_t addr); +    void recv_reg(boost::uint8_t addr); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +e100_codec_ctrl_impl::e100_codec_ctrl_impl(spi_iface::sptr iface){ +    _iface = iface; + +    //soft reset +    _ad9862_regs.soft_reset = 1; +    this->send_reg(0); + +    //initialize the codec register settings +    _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; +    _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; +    _ad9862_regs.soft_reset = 0; + +    //setup rx side of codec +    _ad9862_regs.byp_buffer_a = 1; +    _ad9862_regs.byp_buffer_b = 1; +    _ad9862_regs.buffer_a_pd = 1; +    _ad9862_regs.buffer_b_pd = 1; +    _ad9862_regs.rx_pga_a = 0;//0x1f;  //TODO bring under api control +    _ad9862_regs.rx_pga_b = 0;//0x1f;  //TODO bring under api control +    _ad9862_regs.rx_twos_comp = 1; +    _ad9862_regs.rx_hilbert = ad9862_regs_t::RX_HILBERT_DIS; + +    //setup tx side of codec +    _ad9862_regs.two_data_paths = ad9862_regs_t::TWO_DATA_PATHS_BOTH; +    _ad9862_regs.interleaved = ad9862_regs_t::INTERLEAVED_INTERLEAVED; +    _ad9862_regs.tx_retime = ad9862_regs_t::TX_RETIME_CLKOUT2; +    _ad9862_regs.tx_pga_gain = 199; //TODO bring under api control +    _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; +    _ad9862_regs.interp = ad9862_regs_t::INTERP_2; +    _ad9862_regs.tx_twos_comp = 1; +    _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_BYPASS; +    _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; +    _ad9862_regs.dac_a_coarse_gain = 0x3; +    _ad9862_regs.dac_b_coarse_gain = 0x3; +    _ad9862_regs.edges = ad9862_regs_t::EDGES_NORMAL; + +    //setup the dll +    _ad9862_regs.input_clk_ctrl = ad9862_regs_t::INPUT_CLK_CTRL_EXTERNAL; +    _ad9862_regs.dll_mult = ad9862_regs_t::DLL_MULT_2; +    _ad9862_regs.dll_mode = ad9862_regs_t::DLL_MODE_FAST; + +    //write the register settings to the codec +    for (uint8_t addr = 0; addr <= 25; addr++){ +        this->send_reg(addr); +    } + +    //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); +} + +e100_codec_ctrl_impl::~e100_codec_ctrl_impl(void){ +    //set aux dacs to zero +    this->write_aux_dac(AUX_DAC_A, 0); +    this->write_aux_dac(AUX_DAC_B, 0); +    this->write_aux_dac(AUX_DAC_C, 0); +    this->write_aux_dac(AUX_DAC_D, 0); + +    //power down +    _ad9862_regs.all_rx_pd = 1; +    this->send_reg(1); +    _ad9862_regs.tx_digital_pd = 1; +    _ad9862_regs.tx_analog_pd = ad9862_regs_t::TX_ANALOG_PD_BOTH; +    this->send_reg(8); +} + +/*********************************************************************** + * Codec Control Gain Control Methods + **********************************************************************/ +static const int mtpgw = 255; //maximum tx pga gain word + +void e100_codec_ctrl_impl::set_tx_pga_gain(double gain){ +    int gain_word = int(mtpgw*(gain - tx_pga_gain_range.start())/(tx_pga_gain_range.stop() - tx_pga_gain_range.start())); +    _ad9862_regs.tx_pga_gain = uhd::clip(gain_word, 0, mtpgw); +    this->send_reg(16); +} + +double e100_codec_ctrl_impl::get_tx_pga_gain(void){ +    return (_ad9862_regs.tx_pga_gain*(tx_pga_gain_range.stop() - tx_pga_gain_range.start())/mtpgw) + tx_pga_gain_range.start(); +} + +static const int mrpgw = 0x14; //maximum rx pga gain word + +void e100_codec_ctrl_impl::set_rx_pga_gain(double gain, char which){ +    int gain_word = int(mrpgw*(gain - rx_pga_gain_range.start())/(rx_pga_gain_range.stop() - rx_pga_gain_range.start())); +    gain_word = uhd::clip(gain_word, 0, mrpgw); +    switch(which){ +    case 'A': +        _ad9862_regs.rx_pga_a = gain_word; +        this->send_reg(2); +        return; +    case 'B': +        _ad9862_regs.rx_pga_b = gain_word; +        this->send_reg(3); +        return; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +double e100_codec_ctrl_impl::get_rx_pga_gain(char which){ +    int gain_word; +    switch(which){ +    case 'A': gain_word = _ad9862_regs.rx_pga_a; break; +    case 'B': gain_word = _ad9862_regs.rx_pga_b; break; +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +    return (gain_word*(rx_pga_gain_range.stop() - rx_pga_gain_range.start())/mrpgw) + rx_pga_gain_range.start(); +} + +/*********************************************************************** + * Codec Control AUX ADC Methods + **********************************************************************/ +static double aux_adc_to_volts(boost::uint8_t high, boost::uint8_t low){ +    return double((boost::uint16_t(high) << 2) | low)*3.3/0x3ff; +} + +double e100_codec_ctrl_impl::read_aux_adc(aux_adc_t which){ +    switch(which){ +    case AUX_ADC_A1: +        _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC1; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(28); //read the value (2 bytes, 2 reads) +        this->recv_reg(29); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_a1_9_2, _ad9862_regs.aux_adc_a1_1_0); + +    case AUX_ADC_A2: +        _ad9862_regs.select_a = ad9862_regs_t::SELECT_A_AUX_ADC2; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(26); //read the value (2 bytes, 2 reads) +        this->recv_reg(27); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_a2_9_2, _ad9862_regs.aux_adc_a2_1_0); + +    case AUX_ADC_B1: +        _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC1; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(32); //read the value (2 bytes, 2 reads) +        this->recv_reg(33); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_b1_9_2, _ad9862_regs.aux_adc_b1_1_0); + +    case AUX_ADC_B2: +        _ad9862_regs.select_b = ad9862_regs_t::SELECT_B_AUX_ADC2; +        this->send_reg(34); //start conversion and select mux +        this->recv_reg(30); //read the value (2 bytes, 2 reads) +        this->recv_reg(31); +        return aux_adc_to_volts(_ad9862_regs.aux_adc_b2_9_2, _ad9862_regs.aux_adc_b2_1_0); +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Codec Control AUX DAC Methods + **********************************************************************/ +void e100_codec_ctrl_impl::write_aux_dac(aux_dac_t which, double volts){ +    //special case for aux dac d (aka sigma delta word) +    if (which == AUX_DAC_D){ +        boost::uint16_t dac_word = uhd::clip(boost::math::iround(volts*0xfff/3.3), 0, 0xfff); +        _ad9862_regs.sig_delt_11_4 = boost::uint8_t(dac_word >> 4); +        _ad9862_regs.sig_delt_3_0 = boost::uint8_t(dac_word & 0xf); +        this->send_reg(42); +        this->send_reg(43); +        return; +    } + +    //calculate the dac word for aux dac a, b, c +    boost::uint8_t dac_word = uhd::clip(boost::math::iround(volts*0xff/3.3), 0, 0xff); + +    //setup a lookup table for the aux dac params (reg ref, reg addr) +    typedef boost::tuple<boost::uint8_t*, boost::uint8_t> dac_params_t; +    uhd::dict<aux_dac_t, dac_params_t> aux_dac_to_params = boost::assign::map_list_of +        (AUX_DAC_A, dac_params_t(&_ad9862_regs.aux_dac_a, 36)) +        (AUX_DAC_B, dac_params_t(&_ad9862_regs.aux_dac_b, 37)) +        (AUX_DAC_C, dac_params_t(&_ad9862_regs.aux_dac_c, 38)) +    ; + +    //set the aux dac register +    UHD_ASSERT_THROW(aux_dac_to_params.has_key(which)); +    boost::uint8_t *reg_ref, reg_addr; +    boost::tie(reg_ref, reg_addr) = aux_dac_to_params[which]; +    *reg_ref = dac_word; +    this->send_reg(reg_addr); +} + +/*********************************************************************** + * Codec Control SPI Methods + **********************************************************************/ +void e100_codec_ctrl_impl::send_reg(boost::uint8_t addr){ +    boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); +    UHD_LOGV(often) << "codec control write reg: " << std::hex << reg << std::endl; +    _iface->write_spi( +        UE_SPI_SS_AD9862, +        spi_config_t::EDGE_RISE, +        reg, 16 +    ); +} + +void e100_codec_ctrl_impl::recv_reg(boost::uint8_t addr){ +    boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); +    UHD_LOGV(often) << "codec control read reg: " << std::hex << reg << std::endl; +    boost::uint32_t ret = _iface->read_spi( +        UE_SPI_SS_AD9862, +        spi_config_t::EDGE_RISE, +        reg, 16 +    ); +    UHD_LOGV(often) << "codec control read ret: " << std::hex << ret << std::endl; +    _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +e100_codec_ctrl::sptr e100_codec_ctrl::make(spi_iface::sptr iface){ +    return sptr(new e100_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/e100/codec_ctrl.hpp b/host/lib/usrp/e100/codec_ctrl.hpp new file mode 100644 index 000000000..707f6f521 --- /dev/null +++ b/host/lib/usrp/e100/codec_ctrl.hpp @@ -0,0 +1,90 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP_E100_CODEC_CTRL_HPP +#define INCLUDED_USRP_E100_CODEC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp-e codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class e100_codec_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<e100_codec_ctrl> sptr; + +    static const uhd::gain_range_t tx_pga_gain_range; +    static const uhd::gain_range_t rx_pga_gain_range; + +    /*! +     * Make a new codec control object. +     * \param iface the spi iface object +     * \return the codec control object +     */ +    static sptr make(uhd::spi_iface::sptr iface); + +    //! aux adc identifier constants +    enum aux_adc_t{ +        AUX_ADC_A2 = 0xA2, +        AUX_ADC_A1 = 0xA1, +        AUX_ADC_B2 = 0xB2, +        AUX_ADC_B1 = 0xB1 +    }; + +    /*! +     * Read an auxiliary adc: +     * The internals remember which aux adc was read last. +     * Therefore, the aux adc switch is only changed as needed. +     * \param which which of the 4 adcs +     * \return a value in volts +     */ +    virtual double read_aux_adc(aux_adc_t which) = 0; + +    //! aux dac identifier constants +    enum aux_dac_t{ +        AUX_DAC_A = 0xA, +        AUX_DAC_B = 0xB, +        AUX_DAC_C = 0xC, +        AUX_DAC_D = 0xD //really the sigma delta output +    }; + +    /*! +     * Write an auxiliary dac. +     * \param which which of the 4 dacs +     * \param volts the level in in volts +     */ +    virtual void write_aux_dac(aux_dac_t which, double volts) = 0; + +    //! Set the TX PGA gain +    virtual void set_tx_pga_gain(double gain) = 0; + +    //! Get the TX PGA gain +    virtual double get_tx_pga_gain(void) = 0; + +    //! Set the RX PGA gain ('A' or 'B') +    virtual void set_rx_pga_gain(double gain, char which) = 0; + +    //! Get the RX PGA gain ('A' or 'B') +    virtual double get_rx_pga_gain(char which) = 0; +}; + +#endif /* INCLUDED_USRP_E100_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/e100/dboard_iface.cpp b/host/lib/usrp/e100/dboard_iface.cpp new file mode 100644 index 000000000..d45577bd9 --- /dev/null +++ b/host/lib/usrp/e100/dboard_iface.cpp @@ -0,0 +1,309 @@ +// +// 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 "wb_iface.hpp" +#include <uhd/types/serial.hpp> +#include "e100_regs.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> +#include <linux/usrp_e.h> //i2c and spi constants + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class e100_dboard_iface : public dboard_iface{ +public: + +    e100_dboard_iface( +        wb_iface::sptr wb_iface, +        i2c_iface::sptr i2c_iface, +        spi_iface::sptr spi_iface, +        e100_clock_ctrl::sptr clock, +        e100_codec_ctrl::sptr codec +    ){ +        _wb_iface = wb_iface; +        _i2c_iface = i2c_iface; +        _spi_iface = spi_iface; +        _clock = clock; +        _codec = codec; + +        //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()); + +        _wb_iface->poke16(E100_REG_GPIO_RX_DBG, 0); +        _wb_iface->poke16(E100_REG_GPIO_TX_DBG, 0); +    } + +    ~e100_dboard_iface(void){ +        /* NOP */ +    } + +    special_props_t get_special_props(void){ +        special_props_t props; +        props.soft_clock_divider = false; +        props.mangle_i2c_addrs = false; +        return props; +    } + +    void write_aux_dac(unit_t, aux_dac_t, double); +    double read_aux_adc(unit_t, aux_adc_t); + +    void _set_pin_ctrl(unit_t, boost::uint16_t); +    void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); +    void _set_gpio_ddr(unit_t, boost::uint16_t); +    void _set_gpio_out(unit_t, boost::uint16_t); +    void set_gpio_debug(unit_t, int); +    boost::uint16_t read_gpio(unit_t); + +    void write_i2c(boost::uint8_t, const byte_vector_t &); +    byte_vector_t read_i2c(boost::uint8_t, size_t); + +    void write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    boost::uint32_t read_write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    void set_clock_rate(unit_t, double); +    std::vector<double> get_clock_rates(unit_t); +    double get_clock_rate(unit_t); +    void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t); + +private: +    wb_iface::sptr _wb_iface; +    i2c_iface::sptr _i2c_iface; +    spi_iface::sptr _spi_iface; +    e100_clock_ctrl::sptr _clock; +    e100_codec_ctrl::sptr _codec; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_e100_dboard_iface( +    wb_iface::sptr wb_iface, +    i2c_iface::sptr i2c_iface, +    spi_iface::sptr spi_iface, +    e100_clock_ctrl::sptr clock, +    e100_codec_ctrl::sptr codec +){ +    return dboard_iface::sptr(new e100_dboard_iface(wb_iface, i2c_iface, spi_iface, clock, codec)); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +void e100_dboard_iface::set_clock_rate(unit_t unit, double rate){ +    switch(unit){ +    case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); +    case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); +    } +} + +std::vector<double> e100_dboard_iface::get_clock_rates(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _clock->get_rx_dboard_clock_rates(); +    case UNIT_TX: return _clock->get_tx_dboard_clock_rates(); +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +double e100_dboard_iface::get_clock_rate(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _clock->get_rx_clock_rate(); +    case UNIT_TX: return _clock->get_tx_clock_rate(); +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +void e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ +    switch(unit){ +    case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); +    case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); +    } +} + +double e100_dboard_iface::get_codec_rate(unit_t){ +    return _clock->get_fpga_clock_rate(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void e100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ +    UHD_ASSERT_THROW(GPIO_SEL_ATR == 1); //make this assumption +    switch(unit){ +    case UNIT_RX: _wb_iface->poke16(E100_REG_GPIO_RX_SEL, value); return; +    case UNIT_TX: _wb_iface->poke16(E100_REG_GPIO_TX_SEL, value); return; +    } +} + +void e100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ +    switch(unit){ +    case UNIT_RX: _wb_iface->poke16(E100_REG_GPIO_RX_DDR, value); return; +    case UNIT_TX: _wb_iface->poke16(E100_REG_GPIO_TX_DDR, value); return; +    } +} + +void e100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ +    switch(unit){ +    case UNIT_RX: _wb_iface->poke16(E100_REG_GPIO_RX_IO, value); return; +    case UNIT_TX: _wb_iface->poke16(E100_REG_GPIO_TX_IO, value); return; +    } +} + +boost::uint16_t e100_dboard_iface::read_gpio(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _wb_iface->peek16(E100_REG_GPIO_RX_IO); +    case UNIT_TX: return _wb_iface->peek16(E100_REG_GPIO_TX_IO); +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +void e100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ +    //define mapping of unit to atr regs to register address +    static const uhd::dict< +        unit_t, uhd::dict<atr_reg_t, boost::uint32_t> +    > unit_to_atr_to_addr = map_list_of +        (UNIT_RX, map_list_of +            (ATR_REG_IDLE,        E100_REG_ATR_IDLE_RXSIDE) +            (ATR_REG_TX_ONLY,     E100_REG_ATR_INTX_RXSIDE) +            (ATR_REG_RX_ONLY,     E100_REG_ATR_INRX_RXSIDE) +            (ATR_REG_FULL_DUPLEX, E100_REG_ATR_FULL_RXSIDE) +        ) +        (UNIT_TX, map_list_of +            (ATR_REG_IDLE,        E100_REG_ATR_IDLE_TXSIDE) +            (ATR_REG_TX_ONLY,     E100_REG_ATR_INTX_TXSIDE) +            (ATR_REG_RX_ONLY,     E100_REG_ATR_INRX_TXSIDE) +            (ATR_REG_FULL_DUPLEX, E100_REG_ATR_FULL_TXSIDE) +        ) +    ; +    _wb_iface->poke16(unit_to_atr_to_addr[unit][atr], value); +} + +void e100_dboard_iface::set_gpio_debug(unit_t unit, int which){ +    //set this unit to all outputs +    this->set_gpio_ddr(unit, 0xffff); + +    //calculate the debug selections +    boost::uint32_t dbg_sels = 0x0; +    int sel = (which == 0)? GPIO_SEL_DEBUG_0 : GPIO_SEL_DEBUG_1; +    for(size_t i = 0; i < 16; i++) dbg_sels |= sel << i; + +    //set the debug on and which debug selection +    switch(unit){ +    case UNIT_RX: +        _wb_iface->poke16(E100_REG_GPIO_RX_DBG, 0xffff); +        _wb_iface->poke16(E100_REG_GPIO_RX_SEL, dbg_sels); +        return; + +    case UNIT_TX: +        _wb_iface->poke16(E100_REG_GPIO_TX_DBG, 0xffff); +        _wb_iface->poke16(E100_REG_GPIO_TX_SEL, dbg_sels); +        return; +    } +} + +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ +    switch(unit){ +    case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB; +    case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +void e100_dboard_iface::write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    _spi_iface->write_spi(unit_to_otw_spi_dev(unit), config, data, num_bits); +} + +boost::uint32_t e100_dboard_iface::read_write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    return _spi_iface->read_spi(unit_to_otw_spi_dev(unit), config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void e100_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +    return _i2c_iface->write_i2c(addr, bytes); +} + +byte_vector_t e100_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ +    return _i2c_iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void e100_dboard_iface::write_aux_dac(dboard_iface::unit_t, aux_dac_t which, double value){ +    //same aux dacs for each unit +    static const uhd::dict<aux_dac_t, e100_codec_ctrl::aux_dac_t> which_to_aux_dac = map_list_of +        (AUX_DAC_A, e100_codec_ctrl::AUX_DAC_A) +        (AUX_DAC_B, e100_codec_ctrl::AUX_DAC_B) +        (AUX_DAC_C, e100_codec_ctrl::AUX_DAC_C) +        (AUX_DAC_D, e100_codec_ctrl::AUX_DAC_D) +    ; +    _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +double e100_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, aux_adc_t which){ +    static const uhd::dict< +        unit_t, uhd::dict<aux_adc_t, e100_codec_ctrl::aux_adc_t> +    > unit_to_which_to_aux_adc = map_list_of +        (UNIT_RX, map_list_of +            (AUX_ADC_A, e100_codec_ctrl::AUX_ADC_A1) +            (AUX_ADC_B, e100_codec_ctrl::AUX_ADC_B1) +        ) +        (UNIT_TX, map_list_of +            (AUX_ADC_A, e100_codec_ctrl::AUX_ADC_A2) +            (AUX_ADC_B, e100_codec_ctrl::AUX_ADC_B2) +        ) +    ; +    return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/e100/e100_ctrl.cpp b/host/lib/usrp/e100/e100_ctrl.cpp new file mode 100644 index 000000000..87f7855d3 --- /dev/null +++ b/host/lib/usrp/e100/e100_ctrl.cpp @@ -0,0 +1,286 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include "e100_regs.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <sys/ioctl.h> //ioctl +#include <fcntl.h> //open, close +#include <linux/usrp_e.h> //ioctl structures and constants +#include <boost/thread/thread.hpp> //sleep +#include <boost/thread/mutex.hpp> +#include <boost/format.hpp> +#include <fstream> + +using namespace uhd; + +/*********************************************************************** + * Sysfs GPIO wrapper class + **********************************************************************/ +class gpio{ +public: +    gpio(const int num, const std::string &dir) : _num(num){ +        this->set_xport("export"); +        this->set_dir(dir); +        _value_file.open(str(boost::format("/sys/class/gpio/gpio%d/value") % num).c_str(), std::ios_base::in | std::ios_base::out); +    } +    ~gpio(void){ +        _value_file.close(); +        this->set_dir("in"); +        this->set_xport("unexport"); +    } +    void operator()(const int val){ +        _value_file << val << std::endl << std::flush; +    } +    int operator()(void){ +        std::string val; +        std::getline(_value_file, val); +        _value_file.seekg(0); +        return int(val.at(0) - '0') & 0x1; +    } +private: +    void set_xport(const std::string &xport){ +        std::ofstream export_file(("/sys/class/gpio/" + xport).c_str()); +        export_file << _num << std::endl << std::flush; +        export_file.close(); +    } +    void set_dir(const std::string &dir){ +        std::ofstream dir_file(str(boost::format("/sys/class/gpio/gpio%d/direction") % _num).c_str()); +        dir_file << dir << std::endl << std::flush; +        dir_file.close(); +    } +    const int _num; +    std::fstream _value_file; +}; + +/*********************************************************************** + * Aux spi implementation + **********************************************************************/ +class aux_spi_iface_impl : public spi_iface{ +public: +    aux_spi_iface_impl(void): +        spi_sclk_gpio(65, "out"), +        spi_sen_gpio(186, "out"), +        spi_mosi_gpio(145, "out"), +        spi_miso_gpio(147, "in"){} + +    boost::uint32_t transact_spi( +        int, const spi_config_t &, //not used params +        boost::uint32_t bits, +        size_t num_bits, +        bool readback +    ){ +        boost::uint32_t rb_bits = 0; +        this->spi_sen_gpio(0); + +        for (size_t i = 0; i < num_bits; i++){ +            this->spi_sclk_gpio(0); +            this->spi_mosi_gpio((bits >> (num_bits-i-1)) & 0x1); +            boost::this_thread::sleep(boost::posix_time::microseconds(10)); +            if (readback) rb_bits = (rb_bits << 1) | this->spi_miso_gpio(); +            this->spi_sclk_gpio(1); +            boost::this_thread::sleep(boost::posix_time::microseconds(10)); +        } + +        this->spi_sen_gpio(1); +        boost::this_thread::sleep(boost::posix_time::microseconds(100)); +        return rb_bits; +    } + +private: +    gpio spi_sclk_gpio, spi_sen_gpio, spi_mosi_gpio, spi_miso_gpio; +}; + +uhd::spi_iface::sptr e100_ctrl::make_aux_spi_iface(void){ +    return uhd::spi_iface::sptr(new aux_spi_iface_impl()); +} + +/*********************************************************************** + * I2C device node implementation wrapper + **********************************************************************/ +#include <linux/i2c-dev.h> +#include <linux/i2c.h> +class i2c_dev_iface : public i2c_iface{ +public: +    i2c_dev_iface(const std::string &node){ +        if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ +            throw uhd::io_error("Failed to open " + node); +        } +    } + +    ~i2c_dev_iface(void){ +        ::close(_node_fd); +    } + +    void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +        byte_vector_t rw_bytes(bytes); + +        //setup the message +        i2c_msg msg; +        msg.addr = addr; +        msg.flags = 0; +        msg.len = bytes.size(); +        msg.buf = &rw_bytes.front(); + +        //setup the data +        i2c_rdwr_ioctl_data data; +        data.msgs = &msg; +        data.nmsgs = 1; + +        //call the ioctl +        UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ +        byte_vector_t bytes(num_bytes); + +        //setup the message +        i2c_msg msg; +        msg.addr = addr; +        msg.flags = I2C_M_RD; +        msg.len = bytes.size(); +        msg.buf = &bytes.front(); + +        //setup the data +        i2c_rdwr_ioctl_data data; +        data.msgs = &msg; +        data.nmsgs = 1; + +        //call the ioctl +        UHD_ASSERT_THROW(::ioctl(_node_fd, I2C_RDWR, &data) >= 0); + +        return bytes; +    } + +private: int _node_fd; +}; + +uhd::i2c_iface::sptr e100_ctrl::make_dev_i2c_iface(const std::string &node){ +    return uhd::i2c_iface::sptr(new i2c_dev_iface(node)); +} + +/*********************************************************************** + * USRP-E100 control implementation + **********************************************************************/ +class e100_ctrl_impl : public e100_ctrl{ +public: + +    int get_file_descriptor(void){ +        return _node_fd; +    } + +    /******************************************************************* +     * Structors +     ******************************************************************/ +    e100_ctrl_impl(const std::string &node){ +        UHD_MSG(status) << "Opening device node " << node << "..." << std::endl; + +        //open the device node and check file descriptor +        if ((_node_fd = ::open(node.c_str(), O_RDWR)) < 0){ +            throw uhd::io_error("Failed to open " + node); +        } + +        //check the module compatibility number +        int module_compat_num = ::ioctl(_node_fd, USRP_E_GET_COMPAT_NUMBER, NULL); +        if (module_compat_num != USRP_E_COMPAT_NUMBER){ +            throw uhd::runtime_error(str(boost::format( +                "Expected module compatibility number 0x%x, but got 0x%x:\n" +                "The module build is not compatible with the host code build." +            ) % USRP_E_COMPAT_NUMBER % module_compat_num)); +        } + +        //perform a global reset after opening +        this->poke32(E100_REG_GLOBAL_RESET, 0); +    } + +    ~e100_ctrl_impl(void){ +        ::close(_node_fd); +    } + +    /******************************************************************* +     * IOCTL: provides the communication base for all other calls +     ******************************************************************/ +    void ioctl(int request, void *mem){ +        boost::mutex::scoped_lock lock(_ioctl_mutex); + +        if (::ioctl(_node_fd, request, mem) < 0){ +            throw uhd::os_error(str( +                boost::format("ioctl failed with request %d") % request +            )); +        } +    } +    /******************************************************************* +     * Peek and Poke +     ******************************************************************/ +    void poke32(wb_addr_type addr, boost::uint32_t value){ +        //load the data struct +        usrp_e_ctl32 data; +        data.offset = addr; +        data.count = 1; +        data.buf[0] = value; + +        //call the ioctl +        this->ioctl(USRP_E_WRITE_CTL32, &data); +    } + +    void poke16(wb_addr_type addr, boost::uint16_t value){ +        //load the data struct +        usrp_e_ctl16 data; +        data.offset = addr; +        data.count = 1; +        data.buf[0] = value; + +        //call the ioctl +        this->ioctl(USRP_E_WRITE_CTL16, &data); +    } + +    boost::uint32_t peek32(wb_addr_type addr){ +        //load the data struct +        usrp_e_ctl32 data; +        data.offset = addr; +        data.count = 1; + +        //call the ioctl +        this->ioctl(USRP_E_READ_CTL32, &data); + +        return data.buf[0]; +    } + +    boost::uint16_t peek16(wb_addr_type addr){ +        //load the data struct +        usrp_e_ctl16 data; +        data.offset = addr; +        data.count = 1; + +        //call the ioctl +        this->ioctl(USRP_E_READ_CTL16, &data); + +        return data.buf[0]; +    } + +private: +    int _node_fd; +    boost::mutex _ioctl_mutex; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +e100_ctrl::sptr e100_ctrl::make(const std::string &node){ +    return sptr(new e100_ctrl_impl(node)); +} diff --git a/host/lib/usrp/e100/e100_ctrl.hpp b/host/lib/usrp/e100/e100_ctrl.hpp new file mode 100644 index 000000000..8520ea595 --- /dev/null +++ b/host/lib/usrp/e100/e100_ctrl.hpp @@ -0,0 +1,45 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_B100_CTRL_HPP +#define INCLUDED_B100_CTRL_HPP + +#include "wb_iface.hpp" +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class e100_ctrl : boost::noncopyable, public wb_iface{ +public: +    typedef boost::shared_ptr<e100_ctrl> sptr; + +    //! Make a new controller for E100 +    static sptr make(const std::string &node); + +    //! Make an i2c iface for the i2c device node +    static uhd::i2c_iface::sptr make_dev_i2c_iface(const std::string &node); + +    //! Make an i2c iface for the i2c device node +    static uhd::spi_iface::sptr make_aux_spi_iface(void); + +    virtual void ioctl(int request, void *mem) = 0; + +    virtual int get_file_descriptor(void) = 0; + +}; + +#endif /* INCLUDED_B100_CTRL_HPP */ diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp new file mode 100644 index 000000000..0defb2973 --- /dev/null +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -0,0 +1,390 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_impl.hpp" +#include "e100_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/functional/hash.hpp> +#include <boost/assign/list_of.hpp> +#include <fstream> + +using namespace uhd; +using namespace uhd::usrp; +namespace fs = boost::filesystem; + +//////////////////////////////////////////////////////////////////////// +// I2C addresses +//////////////////////////////////////////////////////////////////////// +#define I2C_DEV_EEPROM  0x50 // 24LC02[45]:  7-bits 1010xxx +#define	I2C_ADDR_MBOARD (I2C_DEV_EEPROM | 0x0) +#define	I2C_ADDR_TX_DB  (I2C_DEV_EEPROM | 0x4) +#define	I2C_ADDR_RX_DB  (I2C_DEV_EEPROM | 0x5) + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t e100_find(const device_addr_t &hint){ +    device_addrs_t e100_addrs; + +    //return an empty list of addresses when type is set to non-usrp-e +    if (hint.has_key("type") and hint["type"] != "e100") return e100_addrs; + +    //device node not provided, assume its 0 +    if (not hint.has_key("node")){ +        device_addr_t new_addr = hint; +        new_addr["node"] = "/dev/usrp_e0"; +        return e100_find(new_addr); +    } + +    //use the given device node name +    if (fs::exists(hint["node"])){ +        device_addr_t new_addr; +        new_addr["type"] = "e100"; +        new_addr["node"] = fs::system_complete(fs::path(hint["node"])).string(); +        try{ +            i2c_iface::sptr i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); +            const mboard_eeprom_t mb_eeprom(*i2c_iface, mboard_eeprom_t::MAP_E100); +            new_addr["name"] = mb_eeprom["name"]; +            new_addr["serial"] = mb_eeprom["serial"]; +        } +        catch(const std::exception &e){ +            new_addr["name"] = ""; +            new_addr["serial"] = ""; +        } +        if ( +            (not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +            (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) +        ){ +            e100_addrs.push_back(new_addr); +        } +    } + +    return e100_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static size_t hash_fpga_file(const std::string &file_path){ +    size_t hash = 0; +    std::ifstream file(file_path.c_str()); +    if (not file.good()) throw uhd::io_error("cannot open fpga file for read: " + file_path); +    while (file.good()) boost::hash_combine(hash, file.get()); +    file.close(); +    return hash; +} + +static device::sptr e100_make(const device_addr_t &device_addr){ +    return device::sptr(new e100_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_e100_device){ +    device::register_device(&e100_find, &e100_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ + +    //setup the main interface into fpga +    const std::string node = device_addr["node"]; +    _fpga_ctrl = e100_ctrl::make(node); + +    //extract the fpga path and compute hash +    const std::string e100_fpga_image = find_image_path(device_addr.get("fpga", E100_FPGA_FILE_NAME)); +    const boost::uint32_t file_hash = boost::uint32_t(hash_fpga_file(e100_fpga_image)); + +    //When the hash does not match: +    // - close the device node +    // - load the fpga bin file +    // - re-open the device node +    if (_fpga_ctrl->peek32(E100_REG_RB_MISC_TEST32) != file_hash){ +        _fpga_ctrl.reset(); +        e100_load_fpga(e100_fpga_image); +        _fpga_ctrl = e100_ctrl::make(node); +    } + +    //setup clock control here to ensure that the FPGA has a good clock before we continue +    const double master_clock_rate = device_addr.cast<double>("master_clock_rate", E100_DEFAULT_CLOCK_RATE); +    _aux_spi_iface = e100_ctrl::make_aux_spi_iface(); +    _clock_ctrl = e100_clock_ctrl::make(_aux_spi_iface, master_clock_rate); + +    //Perform wishbone readback tests, these tests also write the hash +    bool test_fail = false; +    UHD_MSG(status) << "Performing wishbone readback test... " << std::flush; +    for (size_t i = 0; i < 100; i++){ +        _fpga_ctrl->poke32(E100_REG_SR_MISC_TEST32, file_hash); +        test_fail = _fpga_ctrl->peek32(E100_REG_RB_MISC_TEST32) != file_hash; +        if (test_fail) break; //exit loop on any failure +    } +    UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; + +    if (test_fail) UHD_MSG(error) << boost::format( +        "The FPGA is either clocked improperly\n" +        "or the FPGA build is not compatible.\n" +        "Subsequent errors may follow...\n" +    ); + +    //check that the compatibility is correct +    const boost::uint16_t fpga_compat_num = _fpga_ctrl->peek16(E100_REG_MISC_COMPAT); +    if (fpga_compat_num != E100_FPGA_COMPAT_NUM){ +        throw uhd::runtime_error(str(boost::format( +            "\nPlease update the FPGA image for your device.\n" +            "See the application notes for USRP E-Series for instructions.\n" +            "Expected FPGA compatibility number 0x%x, but got 0x%x:\n" +            "The FPGA build is not compatible with the host code build." +        ) % E100_FPGA_COMPAT_NUM % fpga_compat_num)); +    } + +    //////////////////////////////////////////////////////////////////// +    // Create controller objects +    //////////////////////////////////////////////////////////////////// +    _fpga_i2c_ctrl = i2c_core_100::make(_fpga_ctrl, E100_REG_SLAVE(3)); +    _fpga_spi_ctrl = spi_core_100::make(_fpga_ctrl, E100_REG_SLAVE(2)); +    _dev_i2c_iface = e100_ctrl::make_dev_i2c_iface(E100_I2C_DEV_NODE); +    _data_transport = e100_make_mmap_zero_copy(_fpga_ctrl); + +    //////////////////////////////////////////////////////////////////// +    // Initialize the properties tree +    //////////////////////////////////////////////////////////////////// +    _tree = property_tree::make(); +    _tree->create<std::string>("/name").set("E-Series Device"); +    const fs_path mb_path = "/mboards/0"; +    _tree->create<std::string>(mb_path / "name").set("E100 (euewanee)"); + +    //////////////////////////////////////////////////////////////////// +    // setup the mboard eeprom +    //////////////////////////////////////////////////////////////////// +    const mboard_eeprom_t mb_eeprom(*_dev_i2c_iface, mboard_eeprom_t::MAP_E100); +    _tree->create<mboard_eeprom_t>(mb_path / "eeprom") +        .set(mb_eeprom) +        .subscribe(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create clock control objects +    //////////////////////////////////////////////////////////////////// +    //^^^ clock created up top, just reg props here... ^^^ +    _tree->create<double>(mb_path / "tick_rate") +        .publish(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) +        .subscribe(boost::bind(&e100_impl::update_tick_rate, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create codec control objects +    //////////////////////////////////////////////////////////////////// +    _codec_ctrl = e100_codec_ctrl::make(_fpga_spi_ctrl); +    const fs_path rx_codec_path = mb_path / "rx_codecs/A"; +    const fs_path tx_codec_path = mb_path / "tx_codecs/A"; +    _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); +    _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(e100_codec_ctrl::rx_pga_gain_range); +    _tree->create<double>(rx_codec_path / "gains/pga/value") +        .coerce(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); +    _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); +    _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(e100_codec_ctrl::tx_pga_gain_range); +    _tree->create<double>(tx_codec_path / "gains/pga/value") +        .subscribe(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) +        .publish(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); + +    //////////////////////////////////////////////////////////////////// +    // and do the misc mboard sensors +    //////////////////////////////////////////////////////////////////// +    //none for now... +    _tree->create<int>(mb_path / "sensors"); //phony property so this dir exists + +    //////////////////////////////////////////////////////////////////// +    // create frontend control objects +    //////////////////////////////////////////////////////////////////// +    _rx_fe = rx_frontend_core_200::make(_fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_FRONT)); +    _tx_fe = tx_frontend_core_200::make(_fpga_ctrl, E100_REG_SR_ADDR(UE_SR_TX_FRONT)); +    //TODO lots of properties to expose here for frontends +    _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") +        .subscribe(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); +    _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") +        .subscribe(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create rx dsp control objects +    //////////////////////////////////////////////////////////////////// +    _rx_dsps.push_back(rx_dsp_core_200::make( +        _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_DSP0), E100_REG_SR_ADDR(UE_SR_RX_CTRL0), E100_RX_SID_BASE + 0 +    )); +    _rx_dsps.push_back(rx_dsp_core_200::make( +        _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_RX_DSP1), E100_REG_SR_ADDR(UE_SR_RX_CTRL1), E100_RX_SID_BASE + 1 +    )); +    for (size_t dspno = 0; dspno < _rx_dsps.size(); dspno++){ +        _rx_dsps[dspno]->set_link_rate(E100_LINK_RATE_BPS); +        _tree->access<double>(mb_path / "tick_rate") +            .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); +        fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); +        _tree->create<double>(rx_dsp_path / "rate/value") +            .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) +            .subscribe(boost::bind(&e100_impl::update_rx_samp_rate, this, _1)); +        _tree->create<double>(rx_dsp_path / "freq/value") +            .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); +        _tree->create<meta_range_t>(rx_dsp_path / "freq/range") +            .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); +        _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") +            .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); +    } + +    //////////////////////////////////////////////////////////////////// +    // create tx dsp control objects +    //////////////////////////////////////////////////////////////////// +    _tx_dsp = tx_dsp_core_200::make( +        _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_TX_DSP), E100_REG_SR_ADDR(UE_SR_TX_CTRL), E100_TX_ASYNC_SID +    ); +    _tx_dsp->set_link_rate(E100_LINK_RATE_BPS); +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); +    _tree->create<double>(mb_path / "tx_dsps/0/rate/value") +        .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) +        .subscribe(boost::bind(&e100_impl::update_tx_samp_rate, this, _1)); +    _tree->create<double>(mb_path / "tx_dsps/0/freq/value") +        .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); +    _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") +        .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + +    //////////////////////////////////////////////////////////////////// +    // create time control objects +    //////////////////////////////////////////////////////////////////// +    time64_core_200::readback_bases_type time64_rb_bases; +    time64_rb_bases.rb_secs_now = E100_REG_RB_TIME_NOW_SECS; +    time64_rb_bases.rb_ticks_now = E100_REG_RB_TIME_NOW_TICKS; +    time64_rb_bases.rb_secs_pps = E100_REG_RB_TIME_PPS_SECS; +    time64_rb_bases.rb_ticks_pps = E100_REG_RB_TIME_PPS_TICKS; +    _time64 = time64_core_200::make( +        _fpga_ctrl, E100_REG_SR_ADDR(UE_SR_TIME64), time64_rb_bases +    ); +    _tree->access<double>(mb_path / "tick_rate") +        .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); +    _tree->create<time_spec_t>(mb_path / "time/now") +        .publish(boost::bind(&time64_core_200::get_time_now, _time64)) +        .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); +    _tree->create<time_spec_t>(mb_path / "time/pps") +        .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) +        .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); +    //setup time source props +    _tree->create<std::string>(mb_path / "time_source/value") +        .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); +    _tree->create<std::vector<std::string> >(mb_path / "time_source/options") +        .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); +    //setup reference source props +    _tree->create<std::string>(mb_path / "clock_source/value") +        .subscribe(boost::bind(&e100_impl::update_clock_source, this, _1)); +    static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); +    _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); + +    //////////////////////////////////////////////////////////////////// +    // create dboard control objects +    //////////////////////////////////////////////////////////////////// + +    //read the dboard eeprom to extract the dboard ids +    dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; +    rx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_RX_DB); +    tx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB); +    gdb_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB ^ 5); + +    //create the properties and register subscribers +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") +        .set(rx_db_eeprom) +        .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") +        .set(tx_db_eeprom) +        .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); +    _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") +        .set(gdb_eeprom) +        .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); + +    //create a new dboard interface and manager +    _dboard_iface = make_e100_dboard_iface(_fpga_ctrl, _fpga_i2c_ctrl, _fpga_spi_ctrl, _clock_ctrl, _codec_ctrl); +    _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); +    _dboard_manager = dboard_manager::make( +        rx_db_eeprom.id, +        ((gdb_eeprom.id == dboard_id_t::none())? tx_db_eeprom : gdb_eeprom).id, +        _dboard_iface +    ); +    BOOST_FOREACH(const std::string &name, _dboard_manager->get_rx_subdev_names()){ +        dboard_manager::populate_prop_tree_from_subdev( +            _tree->subtree(mb_path / "dboards/A/rx_frontends" / name), +            _dboard_manager->get_rx_subdev(name) +        ); +    } +    BOOST_FOREACH(const std::string &name, _dboard_manager->get_tx_subdev_names()){ +        dboard_manager::populate_prop_tree_from_subdev( +            _tree->subtree(mb_path / "dboards/A/tx_frontends" / name), +            _dboard_manager->get_tx_subdev(name) +        ); +    } + +    //initialize io handling +    this->io_init(); + +    //////////////////////////////////////////////////////////////////// +    // do some post-init tasks +    //////////////////////////////////////////////////////////////////// +    _tree->access<double>(mb_path / "tick_rate").update() //update and then subscribe the clock callback +        .subscribe(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + +    //and now that the tick rate is set, init the host rates to something +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ +        _tree->access<double>(mb_path / "rx_dsps" / name / "rate" / "value").set(1e6); +    } +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ +        _tree->access<double>(mb_path / "tx_dsps" / name / "rate" / "value").set(1e6); +    } + +    _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(subdev_spec_t("A:"+_dboard_manager->get_rx_subdev_names()[0])); +    _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(subdev_spec_t("A:"+_dboard_manager->get_tx_subdev_names()[0])); +    _tree->access<std::string>(mb_path / "clock_source/value").set("internal"); +    _tree->access<std::string>(mb_path / "time_source/value").set("none"); + +} + +e100_impl::~e100_impl(void){ +    /* NOP */ +} + +double e100_impl::update_rx_codec_gain(const double gain){ +    //set gain on both I and Q, readback on one +    //TODO in the future, gains should have individual control +    _codec_ctrl->set_rx_pga_gain(gain, 'A'); +    _codec_ctrl->set_rx_pga_gain(gain, 'B'); +    return _codec_ctrl->get_rx_pga_gain('A'); +} + +void e100_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom){ +    mb_eeprom.commit(*_dev_i2c_iface, mboard_eeprom_t::MAP_E100); +} + +void e100_impl::set_db_eeprom(const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ +    if (type == "rx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_RX_DB); +    if (type == "tx") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB); +    if (type == "gdb") db_eeprom.store(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB ^ 5); +} + +void e100_impl::update_clock_source(const std::string &source){ +    if      (source == "auto")     _clock_ctrl->use_auto_ref(); +    else if (source == "internal") _clock_ctrl->use_internal_ref(); +    else if (source == "external") _clock_ctrl->use_external_ref(); +    else throw uhd::runtime_error("unhandled clock configuration reference source: " + source); +} diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp new file mode 100644 index 000000000..d60d6a63c --- /dev/null +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -0,0 +1,132 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "spi_core_100.hpp" +#include "i2c_core_100.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "rx_dsp_core_200.hpp" +#include "tx_dsp_core_200.hpp" +#include "time64_core_200.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/transport/zero_copy.hpp> + +#ifndef INCLUDED_E100_IMPL_HPP +#define INCLUDED_E100_IMPL_HPP + +uhd::transport::zero_copy_if::sptr e100_make_mmap_zero_copy(e100_ctrl::sptr iface); + +static const double          E100_LINK_RATE_BPS = 256e6/8; +static const std::string     E100_I2C_DEV_NODE = "/dev/i2c-3"; +static const std::string     E100_FPGA_FILE_NAME = "usrp_e100_fpga_v2.bin"; +static const boost::uint16_t E100_FPGA_COMPAT_NUM = 0x05; +static const boost::uint32_t E100_RX_SID_BASE = 2; +static const boost::uint32_t E100_TX_ASYNC_SID = 1; +static const double          E100_DEFAULT_CLOCK_RATE = 64e6; + +//! load an fpga image from a bin file into the usrp-e fpga +extern void e100_load_fpga(const std::string &bin_file); + +//! Make an e100 dboard interface +uhd::usrp::dboard_iface::sptr make_e100_dboard_iface( +    wb_iface::sptr wb_iface, +    uhd::i2c_iface::sptr i2c_iface, +    uhd::spi_iface::sptr spi_iface, +    e100_clock_ctrl::sptr clock, +    e100_codec_ctrl::sptr codec +); + +/*! + * USRP-E100 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class e100_impl : public uhd::device{ +public: +    //structors +    e100_impl(const uhd::device_addr_t &); +    ~e100_impl(void); + +    //the io interface +    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); +    bool recv_async_msg(uhd::async_metadata_t &, double); +    size_t get_max_send_samps_per_packet(void) const; +    size_t get_max_recv_samps_per_packet(void) const; + +private: +    uhd::property_tree::sptr _tree; + +    //controllers +    spi_core_100::sptr _fpga_spi_ctrl; +    i2c_core_100::sptr _fpga_i2c_ctrl; +    rx_frontend_core_200::sptr _rx_fe; +    tx_frontend_core_200::sptr _tx_fe; +    std::vector<rx_dsp_core_200::sptr> _rx_dsps; +    tx_dsp_core_200::sptr _tx_dsp; +    time64_core_200::sptr _time64; +    e100_clock_ctrl::sptr _clock_ctrl; +    e100_codec_ctrl::sptr _codec_ctrl; +    e100_ctrl::sptr _fpga_ctrl; +    uhd::i2c_iface::sptr _dev_i2c_iface; +    uhd::spi_iface::sptr _aux_spi_iface; + +    //transports +    uhd::transport::zero_copy_if::sptr _data_transport; + +    //dboard stuff +    uhd::usrp::dboard_manager::sptr _dboard_manager; +    uhd::usrp::dboard_iface::sptr _dboard_iface; + +    //handle io stuff +    uhd::otw_type_t _rx_otw_type, _tx_otw_type; +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void io_init(void); + +    //device properties interface +    void get(const wax::obj &, wax::obj &val){ +        val = _tree; //entry point into property tree +    } +    uhd::property_tree::sptr get_tree(void) const{ +        return _tree; +    } + +    double update_rx_codec_gain(const double); //sets A and B at once +    void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); +    void set_db_eeprom(const std::string &, const uhd::usrp::dboard_eeprom_t &); +    void update_tick_rate(const double rate); +    void update_rx_samp_rate(const double rate); +    void update_tx_samp_rate(const double rate); +    void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void update_clock_source(const std::string &); + +}; + +#endif /* INCLUDED_E100_IMPL_HPP */ diff --git a/host/lib/usrp/e100/e100_mmap_zero_copy.cpp b/host/lib/usrp/e100/e100_mmap_zero_copy.cpp new file mode 100644 index 000000000..cdb7094f4 --- /dev/null +++ b/host/lib/usrp/e100/e100_mmap_zero_copy.cpp @@ -0,0 +1,269 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "e100_ctrl.hpp" +#include <uhd/transport/zero_copy.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/exception.hpp> +#include <linux/usrp_e.h> +#include <sys/mman.h> //mmap +#include <unistd.h> //getpagesize +#include <poll.h> //poll +#include <vector> + +using namespace uhd; +using namespace uhd::transport; + +#define fp_verbose false //fast-path verbose +static const size_t poll_breakout = 10; //how many poll timeouts constitute a full timeout + +/*********************************************************************** + * Reusable managed receiver buffer: + *  - The buffer knows how to claim and release a frame. + **********************************************************************/ +class e100_mmap_zero_copy_mrb : public managed_recv_buffer{ +public: +    e100_mmap_zero_copy_mrb(void *mem, ring_buffer_info *info): +        _mem(mem), _info(info) { /* NOP */ } + +    void release(void){ +        if (_info->flags != RB_USER_PROCESS) return; +        if (fp_verbose) UHD_LOGV(always) << "recv buff: release" << std::endl; +        _info->flags = RB_KERNEL; //release the frame +    } + +    bool ready(void){return _info->flags & RB_USER;} + +    sptr get_new(void){ +        if (fp_verbose) UHD_LOGV(always) << "  make_recv_buff: " << get_size() << std::endl; +        _info->flags = RB_USER_PROCESS; //claim the frame +        return make_managed_buffer(this); +    } + +private: +    const void *get_buff(void) const{return _mem;} +    size_t get_size(void) const{return _info->len;} + +    void *_mem; +    ring_buffer_info *_info; +}; + +/*********************************************************************** + * Reusable managed send buffer: + *  - The buffer knows how to claim and release a frame. + **********************************************************************/ +class e100_mmap_zero_copy_msb : public managed_send_buffer{ +public: +    e100_mmap_zero_copy_msb(void *mem, ring_buffer_info *info, size_t len, int fd): +        _mem(mem), _info(info), _len(len), _fd(fd) { /* NOP */ } + +    void commit(size_t len){ +        if (_info->flags != RB_USER_PROCESS) return; +        if (fp_verbose) UHD_LOGV(always) << "send buff: commit " << len << std::endl; +        _info->len = len; +        _info->flags = RB_USER; //release the frame +        if (::write(_fd, NULL, 0) < 0){ //notifies the kernel +            UHD_LOGV(rarely) << UHD_THROW_SITE_INFO("write error") << std::endl; +        } +    } + +    bool ready(void){return _info->flags & RB_KERNEL;} + +    sptr get_new(void){ +        if (fp_verbose) UHD_LOGV(always) << "  make_send_buff: " << get_size() << std::endl; +        _info->flags = RB_USER_PROCESS; //claim the frame +        return make_managed_buffer(this); +    } + +private: +    void *get_buff(void) const{return _mem;} +    size_t get_size(void) const{return _len;} + +    void *_mem; +    ring_buffer_info *_info; +    size_t _len; +    int _fd; +}; + +/*********************************************************************** + * The zero copy interface implementation + **********************************************************************/ +class e100_mmap_zero_copy_impl : public zero_copy_if{ +public: +    e100_mmap_zero_copy_impl(e100_ctrl::sptr iface): +        _fd(iface->get_file_descriptor()), _recv_index(0), _send_index(0) +    { +        //get system sizes +        iface->ioctl(USRP_E_GET_RB_INFO, &_rb_size); +        size_t page_size = getpagesize(); +        _frame_size = page_size/2; + +        //calculate the memory size +        _map_size = +            (_rb_size.num_pages_rx_flags + _rb_size.num_pages_tx_flags) * page_size + +            (_rb_size.num_rx_frames + _rb_size.num_tx_frames) * _frame_size; + +        //print sizes summary +        UHD_LOG +            << "page_size:          " << page_size                   << std::endl +            << "frame_size:         " << _frame_size                 << std::endl +            << "num_pages_rx_flags: " << _rb_size.num_pages_rx_flags << std::endl +            << "num_rx_frames:      " << _rb_size.num_rx_frames      << std::endl +            << "num_pages_tx_flags: " << _rb_size.num_pages_tx_flags << std::endl +            << "num_tx_frames:      " << _rb_size.num_tx_frames      << std::endl +            << "map_size:           " << _map_size                   << std::endl +        ; + +        //call mmap to get the memory +        _mapped_mem = ::mmap( +            NULL, _map_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0 +        ); +        UHD_ASSERT_THROW(_mapped_mem != MAP_FAILED); + +        //calculate the memory offsets for info and buffers +        size_t recv_info_off = 0; +        size_t recv_buff_off = recv_info_off + (_rb_size.num_pages_rx_flags * page_size); +        size_t send_info_off = recv_buff_off + (_rb_size.num_rx_frames * _frame_size); +        size_t send_buff_off = send_info_off + (_rb_size.num_pages_tx_flags * page_size); + +        //print offset summary +        UHD_LOG +            << "recv_info_off: " << recv_info_off << std::endl +            << "recv_buff_off: " << recv_buff_off << std::endl +            << "send_info_off: " << send_info_off << std::endl +            << "send_buff_off: " << send_buff_off << std::endl +        ; + +        //pointers to sections in the mapped memory +        ring_buffer_info (*recv_info)[], (*send_info)[]; +        char *recv_buff, *send_buff; + +        //set the internal pointers for info and buffers +        typedef ring_buffer_info (*rbi_pta)[]; +        char *rb_ptr = reinterpret_cast<char *>(_mapped_mem); +        recv_info = reinterpret_cast<rbi_pta>(rb_ptr + recv_info_off); +        recv_buff = rb_ptr + recv_buff_off; +        send_info = reinterpret_cast<rbi_pta>(rb_ptr + send_info_off); +        send_buff = rb_ptr + send_buff_off; + +        //initialize the managed receive buffers +        for (size_t i = 0; i < get_num_recv_frames(); i++){ +            _mrb_pool.push_back(e100_mmap_zero_copy_mrb( +                recv_buff + get_recv_frame_size()*i, (*recv_info) + i +            )); +        } + +        //initialize the managed send buffers +        for (size_t i = 0; i < get_num_recv_frames(); i++){ +            _msb_pool.push_back(e100_mmap_zero_copy_msb( +                send_buff + get_send_frame_size()*i, (*send_info) + i, +                get_send_frame_size(), _fd +            )); +        } +    } + +    ~e100_mmap_zero_copy_impl(void){ +        UHD_LOG << "cleanup: munmap" << std::endl; +        ::munmap(_mapped_mem, _map_size); +    } + +    managed_recv_buffer::sptr get_recv_buff(double timeout){ +        if (fp_verbose) UHD_LOGV(always) << "get_recv_buff: " << _recv_index << std::endl; +        e100_mmap_zero_copy_mrb &mrb = _mrb_pool[_recv_index]; + +        //poll/wait for a ready frame +        if (not mrb.ready()){ +            for (size_t i = 0; i < poll_breakout; i++){ +                pollfd pfd; +                pfd.fd = _fd; +                pfd.events = POLLIN; +                ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3/poll_breakout)); +                if (fp_verbose) UHD_LOGV(always) << "  POLLIN: " << poll_ret << std::endl; +                if (poll_ret > 0) goto found_user_frame; //good poll, continue on +            } +            return managed_recv_buffer::sptr(); //timed-out for real +        } found_user_frame: + +        //increment the index for the next call +        if (++_recv_index == get_num_recv_frames()) _recv_index = 0; + +        //return the managed buffer for this frame +        return mrb.get_new(); +    } + +    size_t get_num_recv_frames(void) const{ +        return _rb_size.num_rx_frames; +    } + +    size_t get_recv_frame_size(void) const{ +        return _frame_size; +    } + +    managed_send_buffer::sptr get_send_buff(double timeout){ +        if (fp_verbose) UHD_LOGV(always) << "get_send_buff: " << _send_index << std::endl; +        e100_mmap_zero_copy_msb &msb = _msb_pool[_send_index]; + +        //poll/wait for a ready frame +        if (not msb.ready()){ +            pollfd pfd; +            pfd.fd = _fd; +            pfd.events = POLLOUT; +            ssize_t poll_ret = ::poll(&pfd, 1, size_t(timeout*1e3)); +            if (fp_verbose) UHD_LOGV(always) << "  POLLOUT: " << poll_ret << std::endl; +            if (poll_ret <= 0) return managed_send_buffer::sptr(); +        } + +        //increment the index for the next call +        if (++_send_index == get_num_send_frames()) _send_index = 0; + +        //return the managed buffer for this frame +        return msb.get_new(); +    } + +    size_t get_num_send_frames(void) const{ +        return _rb_size.num_tx_frames; +    } + +    size_t get_send_frame_size(void) const{ +        return _frame_size; +    } + +private: +    //file descriptor for mmap +    int _fd; + +    //the mapped memory itself +    void *_mapped_mem; + +    //mapped memory sizes +    usrp_e_ring_buffer_size_t _rb_size; +    size_t _frame_size, _map_size; + +    //re-usable managed buffers +    std::vector<e100_mmap_zero_copy_mrb> _mrb_pool; +    std::vector<e100_mmap_zero_copy_msb> _msb_pool; + +    //indexes into sub-sections of mapped memory +    size_t _recv_index, _send_index; +}; + +/*********************************************************************** + * The zero copy interface make function + **********************************************************************/ +zero_copy_if::sptr e100_make_mmap_zero_copy(e100_ctrl::sptr iface){ +    return zero_copy_if::sptr(new e100_mmap_zero_copy_impl(iface)); +} diff --git a/host/lib/usrp/e100/e100_regs.hpp b/host/lib/usrp/e100/e100_regs.hpp new file mode 100644 index 000000000..28ef707dc --- /dev/null +++ b/host/lib/usrp/e100/e100_regs.hpp @@ -0,0 +1,157 @@ + + +//////////////////////////////////////////////////////////////// +// +//         Memory map for embedded wishbone bus +// +//////////////////////////////////////////////////////////////// + +// All addresses are byte addresses.  All accesses are word (16-bit) accesses. +//  This means that address bit 0 is usually 0. +//  There are 11 bits of address for the control. + +#ifndef INCLUDED_E100_REGS_HPP +#define INCLUDED_E100_REGS_HPP + +///////////////////////////////////////////////////// +// Slave pointers + +#define E100_REG_SLAVE(n) ((n)<<7) + +///////////////////////////////////////////////////// +// Slave 0 -- Misc Regs + +#define E100_REG_MISC_BASE E100_REG_SLAVE(0) + +#define E100_REG_MISC_LED        E100_REG_MISC_BASE + 0 +#define E100_REG_MISC_SW         E100_REG_MISC_BASE + 2 +#define E100_REG_MISC_CGEN_CTRL  E100_REG_MISC_BASE + 4 +#define E100_REG_MISC_CGEN_ST    E100_REG_MISC_BASE + 6 +#define E100_REG_MISC_TEST       E100_REG_MISC_BASE + 8 +#define E100_REG_MISC_RX_LEN     E100_REG_MISC_BASE + 10 +#define E100_REG_MISC_TX_LEN     E100_REG_MISC_BASE + 12 +#define E100_REG_MISC_XFER_RATE  E100_REG_MISC_BASE + 14 +#define E100_REG_MISC_COMPAT     E100_REG_MISC_BASE + 16 + +///////////////////////////////////////////////////// +// Slave 1 -- UART +//   CLKDIV is 16 bits, others are only 8 + +#define E100_REG_UART_BASE E100_REG_SLAVE(1) + +#define E100_REG_UART_CLKDIV  E100_REG_UART_BASE + 0 +#define E100_REG_UART_TXLEVEL E100_REG_UART_BASE + 2 +#define E100_REG_UART_RXLEVEL E100_REG_UART_BASE + 4 +#define E100_REG_UART_TXCHAR  E100_REG_UART_BASE + 6 +#define E100_REG_UART_RXCHAR  E100_REG_UART_BASE + 8 + +///////////////////////////////////////////////////// +// Slave 2 -- SPI Core +//these are 32-bit registers mapped onto the 16-bit Wishbone bus. +//Using peek32/poke32 should allow transparent use of these registers. +#define E100_REG_SPI_BASE E100_REG_SLAVE(2) + +//spi slave constants +#define UE_SPI_SS_AD9522    (1 << 3) +#define UE_SPI_SS_AD9862    (1 << 2) +#define UE_SPI_SS_TX_DB     (1 << 1) +#define UE_SPI_SS_RX_DB     (1 << 0) + +//////////////////////////////////////////////// +// Slave 3 -- I2C Core + +#define E100_REG_I2C_BASE E100_REG_SLAVE(3) + +//////////////////////////////////////////////// +// Slave 5 -- Error messages buffer + +#define E100_REG_ERR_BUFF E100_REG_SLAVE(5) + +//////////////////////////////////////////////// +// Slave 4 -- GPIO + +#define E100_REG_GPIO_BASE E100_REG_SLAVE(4) + +#define E100_REG_GPIO_RX_IO      E100_REG_GPIO_BASE + 0 +#define E100_REG_GPIO_TX_IO      E100_REG_GPIO_BASE + 2 +#define E100_REG_GPIO_RX_DDR     E100_REG_GPIO_BASE + 4 +#define E100_REG_GPIO_TX_DDR     E100_REG_GPIO_BASE + 6 +#define E100_REG_GPIO_RX_SEL     E100_REG_GPIO_BASE + 8 +#define E100_REG_GPIO_TX_SEL     E100_REG_GPIO_BASE + 10 +#define E100_REG_GPIO_RX_DBG     E100_REG_GPIO_BASE + 12 +#define E100_REG_GPIO_TX_DBG     E100_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 E100_REG_ATR_BASE  E100_REG_SLAVE(6) + +#define	E100_REG_ATR_IDLE_RXSIDE  E100_REG_ATR_BASE + 0 +#define	E100_REG_ATR_IDLE_TXSIDE  E100_REG_ATR_BASE + 2 +#define E100_REG_ATR_INTX_RXSIDE  E100_REG_ATR_BASE + 4 +#define E100_REG_ATR_INTX_TXSIDE  E100_REG_ATR_BASE + 6 +#define	E100_REG_ATR_INRX_RXSIDE  E100_REG_ATR_BASE + 8 +#define	E100_REG_ATR_INRX_TXSIDE  E100_REG_ATR_BASE + 10 +#define	E100_REG_ATR_FULL_RXSIDE  E100_REG_ATR_BASE + 12 +#define	E100_REG_ATR_FULL_TXSIDE  E100_REG_ATR_BASE + 14 + +/////////////////////////////////////////////////// +// Slave 7 -- Readback Mux 32 + +#define E100_REG_RB_MUX_32_BASE  E100_REG_SLAVE(7) + +#define E100_REG_RB_TIME_NOW_SECS   E100_REG_RB_MUX_32_BASE + 0 +#define E100_REG_RB_TIME_NOW_TICKS  E100_REG_RB_MUX_32_BASE + 4 +#define E100_REG_RB_TIME_PPS_SECS   E100_REG_RB_MUX_32_BASE + 8 +#define E100_REG_RB_TIME_PPS_TICKS  E100_REG_RB_MUX_32_BASE + 12 +#define E100_REG_RB_MISC_TEST32     E100_REG_RB_MUX_32_BASE + 16 +#define E100_REG_RB_ERR_STATUS      E100_REG_RB_MUX_32_BASE + 20 + +//////////////////////////////////////////////////// +// Slave 8 -- Settings Bus +// +// Output-only, no readback, 64 registers total +//  Each register must be written 64 bits at a time +//  First the address xxx_xx00 and then xxx_xx10 + +// 64 total regs in address space +#define UE_SR_RX_CTRL0 0       // 9 regs (+0 to +8) +#define UE_SR_RX_DSP0 10       // 4 regs (+0 to +3) +#define UE_SR_RX_CTRL1 16      // 9 regs (+0 to +8) +#define UE_SR_RX_DSP1 26       // 4 regs (+0 to +3) +#define UE_SR_ERR_CTRL 30      // 1 reg +#define UE_SR_TX_CTRL 32       // 4 regs (+0 to +3) +#define UE_SR_TX_DSP 38        // 3 regs (+0 to +2) + +#define UE_SR_TIME64 42        // 6 regs (+0 to +5) +#define UE_SR_RX_FRONT 48      // 5 regs (+0 to +4) +#define UE_SR_TX_FRONT 54      // 5 regs (+0 to +4) + +#define UE_SR_REG_TEST32 60    // 1 reg +#define UE_SR_CLEAR_RX_FIFO 61 // 1 reg +#define UE_SR_CLEAR_TX_FIFO 62 // 1 reg +#define UE_SR_GLOBAL_RESET 63  // 1 reg + +#define E100_REG_SR_ADDR(n) (E100_REG_SLAVE(8) + (4*(n))) + +#define E100_REG_SR_MISC_TEST32        E100_REG_SR_ADDR(UE_SR_REG_TEST32) +#define E100_REG_SR_ERR_CTRL           E100_REG_SR_ADDR(UE_SR_ERR_CTRL) + +///////////////////////////////////////////////// +// Magic reset regs +//////////////////////////////////////////////// +#define E100_REG_CLEAR_RX           E100_REG_SR_ADDR(UE_SR_CLEAR_RX_FIFO) +#define E100_REG_CLEAR_TX           E100_REG_SR_ADDR(UE_SR_CLEAR_RX_FIFO) +#define E100_REG_GLOBAL_RESET       E100_REG_SR_ADDR(UE_SR_GLOBAL_RESET) + +#endif + diff --git a/host/lib/usrp/e100/fpga_downloader.cpp b/host/lib/usrp/e100/fpga_downloader.cpp new file mode 100644 index 000000000..7074c8299 --- /dev/null +++ b/host/lib/usrp/e100/fpga_downloader.cpp @@ -0,0 +1,272 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#ifdef UHD_DLL_EXPORTS +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#else //special case when this file is externally included +#include <stdexcept> +#include <iostream> +#define UHD_MSG(type) std::cout +namespace uhd{ +    typedef std::runtime_error os_error; +    typedef std::runtime_error io_error; +} +#endif + +#include <sstream> +#include <fstream> +#include <string> +#include <cstdlib> + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <linux/spi/spidev.h> + +/* + * Configuration connections + * + * CCK    - MCSPI1_CLK + * DIN    - MCSPI1_MOSI + * PROG_B - GPIO_175     - output (change mux) + * DONE   - GPIO_173     - input  (change mux) + * INIT_B - GPIO_114     - input  (change mux) + * +*/ + +namespace usrp_e_fpga_downloader_utility{ + +const unsigned int PROG_B = 175; +const unsigned int DONE   = 173; +const unsigned int INIT_B = 114; + +//static std::string bit_file = "safe_u1e.bin"; + +const int BUF_SIZE = 4096; + +enum gpio_direction {IN, OUT}; + +class gpio { +	public: + +	gpio(unsigned int gpio_num, gpio_direction pin_direction); + +	bool get_value(); +	void set_value(bool state); + +	private: + +	std::stringstream base_path; +	std::fstream value_file;	 +}; + +class spidev { +	public: + +	spidev(std::string dev_name); +	~spidev(); + +	void send(char *wbuf, char *rbuf, unsigned int nbytes); + +	private: + +	int fd; + +}; + +gpio::gpio(unsigned int gpio_num, gpio_direction pin_direction) +{ +	std::fstream export_file; + +	export_file.open("/sys/class/gpio/export", std::ios::out); +	if (not export_file.is_open()) throw uhd::os_error( +		"Failed to open gpio export file." +	); + +	export_file << gpio_num << std::endl; + +	base_path << "/sys/class/gpio/gpio" << gpio_num << std::flush; + +	std::fstream direction_file; +	std::string direction_file_name; + +	if (gpio_num != 114) { +		direction_file_name = base_path.str() + "/direction"; + +		direction_file.open(direction_file_name.c_str()); +		if (!direction_file.is_open()) throw uhd::os_error( +			"Failed to open direction file." +		); +		if (pin_direction == OUT) +			direction_file << "out" << std::endl; +		else +			direction_file << "in" << std::endl; +	} + +	std::string value_file_name; + +	value_file_name = base_path.str() + "/value"; + +	value_file.open(value_file_name.c_str(), std::ios_base::in | std::ios_base::out); +	if (!value_file.is_open()) throw uhd::os_error( +		"Failed to open value file." +	); +} + +bool gpio::get_value() +{ + +	std::string val; + +	std::getline(value_file, val); +	value_file.seekg(0); + +	if (val == "0") +		return false; +	else if (val == "1") +		return true; +	else +		throw uhd::os_error("Data read from value file|" + val + "|"); + +	return false; +} + +void gpio::set_value(bool state) +{ + +	if (state) +		value_file << "1" << std::endl; +	else +		value_file << "0" << std::endl; +} + +static void prepare_fpga_for_configuration(gpio &prog, gpio &)//init) +{ + +	prog.set_value(true); +	prog.set_value(false); +	prog.set_value(true); + +#if 0 +	bool ready_to_program(false); +	unsigned int count(0); +	do { +		ready_to_program = init.get_value(); +		count++; + +		sleep(1); +	} while (count < 10 && !ready_to_program); + +	if (count == 10) { +		throw uhd::os_error("FPGA not ready for programming."); +	} +#endif +} + +spidev::spidev(std::string fname) +{ +	int ret; +	int mode = 0; +	int speed = 12000000; +	int bits = 8; + +	fd = open(fname.c_str(), O_RDWR); + +	ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); +	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); +	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); +} +	 + +spidev::~spidev() +{ +	close(fd); +} + +void spidev::send(char *buf, char *rbuf, unsigned int nbytes) +{ +	int ret; + +	struct spi_ioc_transfer tr; +	tr.tx_buf = (unsigned long) buf; +	tr.rx_buf = (unsigned long) rbuf; +	tr.len = nbytes; +	tr.delay_usecs = 0; +	tr.speed_hz = 48000000; +	tr.bits_per_word = 8; + +	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);	 + +} + +static void send_file_to_fpga(const std::string &file_name, gpio &error, gpio &done) +{ +	std::ifstream bitstream; + +	bitstream.open(file_name.c_str(), std::ios::binary); +	if (!bitstream.is_open()) throw uhd::os_error( +		"Coult not open the file: " + file_name +	); + +	spidev spi("/dev/spidev1.0"); +	char buf[BUF_SIZE]; +	char rbuf[BUF_SIZE]; + +	do { +		bitstream.read(buf, BUF_SIZE); +		spi.send(buf, rbuf, bitstream.gcount()); + +		if (error.get_value()) +			throw uhd::os_error("INIT_B went high, error occured."); + +		if (!done.get_value()) +			UHD_MSG(status) << "Configuration complete." << std::endl; + +	} while (bitstream.gcount() == BUF_SIZE); +} + +}//namespace usrp_e_fpga_downloader_utility + +void e100_load_fpga(const std::string &bin_file){ +	using namespace usrp_e_fpga_downloader_utility; + +	gpio gpio_prog_b(PROG_B, OUT); +	gpio gpio_init_b(INIT_B, IN); +	gpio gpio_done  (DONE,   IN); + +	UHD_MSG(status) << "Loading FPGA image: " << bin_file << "... " << std::flush; + +//	if(std::system("/sbin/rmmod usrp_e") != 0){ +//		UHD_MSG(warning) << "USRP-E100 FPGA downloader: could not unload usrp_e module" << std::endl; +//	} + +	prepare_fpga_for_configuration(gpio_prog_b, gpio_init_b); + +	UHD_MSG(status) << "done = " << gpio_done.get_value() << std::endl; + +	send_file_to_fpga(bin_file, gpio_init_b, gpio_done); + +//	if(std::system("/sbin/modprobe usrp_e") != 0){ +//		UHD_MSG(warning) << "USRP-E100 FPGA downloader: could not load usrp_e module" << std::endl; +//	} + +} + diff --git a/host/lib/usrp/e100/include/linux/usrp_e.h b/host/lib/usrp/e100/include/linux/usrp_e.h new file mode 100644 index 000000000..2c4aa2ac1 --- /dev/null +++ b/host/lib/usrp/e100/include/linux/usrp_e.h @@ -0,0 +1,60 @@ + +/* + *  Copyright (C) 2010 Ettus Research, LLC + * + *  Written by Philip Balister <philip@opensdr.com> + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + */ + +#ifndef __USRP_E_H +#define __USRP_E_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +struct usrp_e_ctl16 { +	__u32 offset; +	__u32 count; +	__u16 buf[20]; +}; + +struct usrp_e_ctl32 { +	__u32 offset; +	__u32 count; +	__u32 buf[10]; +}; + +#define USRP_E_IOC_MAGIC	'u' +#define USRP_E_WRITE_CTL16	_IOW(USRP_E_IOC_MAGIC, 0x20, struct usrp_e_ctl16) +#define USRP_E_READ_CTL16	_IOWR(USRP_E_IOC_MAGIC, 0x21, struct usrp_e_ctl16) +#define USRP_E_WRITE_CTL32	_IOW(USRP_E_IOC_MAGIC, 0x22, struct usrp_e_ctl32) +#define USRP_E_READ_CTL32	_IOWR(USRP_E_IOC_MAGIC, 0x23, struct usrp_e_ctl32) +#define USRP_E_GET_RB_INFO      _IOR(USRP_E_IOC_MAGIC, 0x27, struct usrp_e_ring_buffer_size_t) +#define USRP_E_GET_COMPAT_NUMBER _IO(USRP_E_IOC_MAGIC, 0x28) + +#define USRP_E_COMPAT_NUMBER 2 + +/* Flag defines */ +#define RB_USER (1<<0) +#define RB_KERNEL (1<<1) +#define RB_OVERRUN (1<<2) +#define RB_DMA_ACTIVE (1<<3) +#define RB_USER_PROCESS (1<<4) + +struct ring_buffer_info { +	int flags; +	int len; +}; + +struct usrp_e_ring_buffer_size_t { +	int num_pages_rx_flags; +	int num_rx_frames; +	int num_pages_tx_flags; +	int num_tx_frames; +}; + +#endif diff --git a/host/lib/usrp/e100/io_impl.cpp b/host/lib/usrp/e100/io_impl.cpp new file mode 100644 index 000000000..0b81c1a86 --- /dev/null +++ b/host/lib/usrp/e100/io_impl.cpp @@ -0,0 +1,327 @@ +// +// 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 "recv_packet_demuxer.hpp" +#include "validate_subdev_spec.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include <linux/usrp_e.h> //ioctl structures and constants +#include "e100_impl.hpp" +#include "e100_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread/thread.hpp> +#include <poll.h> //poll +#include <fcntl.h> //open, close +#include <sstream> +#include <fstream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew of 1 + * - bounded buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct e100_impl::io_impl{ +    io_impl(void): +        false_alarm(0), async_msg_fifo(100/*messages deep*/) +    { /* NOP */ } + +    double tick_rate; //set by update tick rate method +    e100_ctrl::sptr iface; //so handle irq can peek and poke +    void handle_irq(void); +    size_t false_alarm; +    //The data transport is listed first so that it is deconstructed last, +    //which is after the states and booty which may hold managed buffers. +    recv_packet_demuxer::sptr demuxer; + +    //state management for the vrt packet handler code +    sph::recv_packet_handler recv_handler; +    sph::send_packet_handler send_handler; + +    //a pirate's life is the life for me! +    void recv_pirate_loop( +        spi_iface::sptr //keep a sptr to iface which shares gpio147 +    ){ +        //open the GPIO and set it up for an IRQ +        std::ofstream edge_file("/sys/class/gpio/gpio147/edge"); +        edge_file << "rising" << std::endl << std::flush; +        edge_file.close(); +        int fd = ::open("/sys/class/gpio/gpio147/value", O_RDONLY); +        if (fd < 0) UHD_MSG(error) << "Unable to open GPIO for IRQ\n"; + +        while (not boost::this_thread::interruption_requested()){ +            pollfd pfd; +            pfd.fd = fd; +            pfd.events = POLLPRI | POLLERR; +            ssize_t ret = ::poll(&pfd, 1, 100/*ms*/); +            if (ret > 0) this->handle_irq(); +        } + +        //cleanup before thread exit +        ::close(fd); +    } +    bounded_buffer<async_metadata_t> async_msg_fifo; +    task::sptr pirate_task; +}; + +void e100_impl::io_impl::handle_irq(void){ +    //check the status of the async msg buffer +    const boost::uint32_t status = iface->peek32(E100_REG_RB_ERR_STATUS); +    if ((status & 0x3) == 0){ //not done or error +        //This could be a false-alarm because spi readback is mixed in. +        //So we just sleep for a bit rather than interrupt continuously. +        if (false_alarm++ > 3) boost::this_thread::sleep(boost::posix_time::milliseconds(1)); +        return; +    } +    false_alarm = 0; //its a real message, reset the count... +    //std::cout << boost::format("status: 0x%x") % status << std::endl; + +    //load the data struct and call the ioctl +    usrp_e_ctl32 data; +    data.offset = E100_REG_ERR_BUFF; +    data.count = status >> 16; +    iface->ioctl(USRP_E_READ_CTL32, &data); +    //for (size_t i = 0; i < data.count; i++){ +        //data.buf[i] = iface->peek32(E100_REG_ERR_BUFF + i*sizeof(boost::uint32_t)); +        //std::cout << boost::format("    buff[%u] = 0x%08x\n") % i % data.buf[i]; +    //} + +    //unpack the vrt header and process below... +    vrt::if_packet_info_t if_packet_info; +    if_packet_info.num_packet_words32 = data.count; +    try{vrt::if_hdr_unpack_le(data.buf, if_packet_info);} +    catch(const std::exception &e){ +        UHD_MSG(error) << "Error unpacking vrt header:\n" << e.what() << std::endl; +        goto prepare; +    } + +    //handle a tx async report message +    if (if_packet_info.sid == E100_TX_ASYNC_SID and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + +        //fill in the async metadata +        async_metadata_t metadata; +        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), long(if_packet_info.tsf), tick_rate +        ); +        metadata.event_code = async_metadata_t::event_code_t(sph::get_context_code(data.buf, if_packet_info)); + +        //push the message onto the queue +        async_msg_fifo.push_with_pop_on_full(metadata); + +        //print some fastpath messages +        if (metadata.event_code & +            ( async_metadata_t::EVENT_CODE_UNDERFLOW +            | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET) +        ) UHD_MSG(fastpath) << "U"; +        else if (metadata.event_code & +            ( async_metadata_t::EVENT_CODE_SEQ_ERROR +            | async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST) +        ) UHD_MSG(fastpath) << "S"; +    } + +    //prepare for the next round +    prepare: +    iface->poke32(E100_REG_SR_ERR_CTRL, 1 << 0); //clear +    while ((iface->peek32(E100_REG_RB_ERR_STATUS) & (1 << 2)) == 0){} //wait for idle +    iface->poke32(E100_REG_SR_ERR_CTRL, 1 << 1); //start +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void e100_impl::io_init(void){ + +    //setup rx otw type +    _rx_otw_type.width = 16; +    _rx_otw_type.shift = 0; +    _rx_otw_type.byteorder = uhd::otw_type_t::BO_LITTLE_ENDIAN; + +    //setup tx otw type +    _tx_otw_type.width = 16; +    _tx_otw_type.shift = 0; +    _tx_otw_type.byteorder = uhd::otw_type_t::BO_LITTLE_ENDIAN; + +    //create new io impl +    _io_impl = UHD_PIMPL_MAKE(io_impl, ()); +    _io_impl->demuxer = recv_packet_demuxer::make(_data_transport, _rx_dsps.size(), E100_RX_SID_BASE); +    _io_impl->iface = _fpga_ctrl; + +    //clear state machines +    _fpga_ctrl->poke32(E100_REG_CLEAR_RX, 0); +    _fpga_ctrl->poke32(E100_REG_CLEAR_TX, 0); + +    //prepare the async msg buffer for incoming messages +    _fpga_ctrl->poke32(E100_REG_SR_ERR_CTRL, 1 << 0); //clear +    while ((_fpga_ctrl->peek32(E100_REG_RB_ERR_STATUS) & (1 << 2)) == 0){} //wait for idle +    _fpga_ctrl->poke32(E100_REG_SR_ERR_CTRL, 1 << 1); //start + +    //spawn a pirate, yarrr! +    _io_impl->pirate_task = task::make(boost::bind( +        &e100_impl::io_impl::recv_pirate_loop, _io_impl.get(), _aux_spi_iface +    )); + +    //init some handler stuff +    _io_impl->recv_handler.set_vrt_unpacker(&vrt::if_hdr_unpack_le); +    _io_impl->recv_handler.set_converter(_rx_otw_type); +    _io_impl->send_handler.set_vrt_packer(&vrt::if_hdr_pack_le); +    _io_impl->send_handler.set_converter(_tx_otw_type); +    _io_impl->send_handler.set_max_samples_per_packet(get_max_send_samps_per_packet()); +} + +void e100_impl::update_tick_rate(const double rate){ +    _io_impl->tick_rate = rate; +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.set_tick_rate(rate); +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.set_tick_rate(rate); +} + +void e100_impl::update_rx_samp_rate(const double rate){ +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.set_samp_rate(rate); +    const double adj = _rx_dsps.front()->get_scaling_adjustment(); +    _io_impl->recv_handler.set_scale_factor(adj/32767.); +} + +void e100_impl::update_tx_samp_rate(const double rate){ +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.set_samp_rate(rate); +} + +void e100_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    fs_path root = "/mboards/0/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "rx"); + +    //setup mux for this spec +    bool fe_swapped = false; +    for (size_t i = 0; i < spec.size(); i++){ +        const std::string conn = _tree->access<std::string>(root / spec[i].db_name / "rx_frontends" / spec[i].sd_name / "connection").get(); +        if (i == 0 and (conn == "QI" or conn == "Q")) fe_swapped = true; +        _rx_dsps[i]->set_mux(conn, fe_swapped); +    } +    _rx_fe->set_mux(fe_swapped); + +    //resize for the new occupancy +    _io_impl->recv_handler.resize(spec.size()); + +    //bind new callbacks for the handler +    for (size_t i = 0; i < _io_impl->recv_handler.size(); i++){ +        _rx_dsps[i]->set_nsamps_per_packet(get_max_recv_samps_per_packet()); //seems to be a good place to set this +        _io_impl->recv_handler.set_xport_chan_get_buff(i, boost::bind( +            &recv_packet_demuxer::get_recv_buff, _io_impl->demuxer, i, _1 +        )); +        _io_impl->recv_handler.set_overflow_handler(i, boost::bind(&rx_dsp_core_200::handle_overflow, _rx_dsps[i])); +    } +} + +void e100_impl::update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    fs_path root = "/mboards/0/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "tx"); + +    //set the mux for this spec +    const std::string conn = _tree->access<std::string>(root / spec[0].db_name / "tx_frontends" / spec[0].sd_name / "connection").get(); +    _tx_fe->set_mux(conn); + +    //resize for the new occupancy +    _io_impl->send_handler.resize(spec.size()); + +    //bind new callbacks for the handler +    for (size_t i = 0; i < _io_impl->send_handler.size(); i++){ +        _io_impl->send_handler.set_xport_chan_get_buff(i, boost::bind( +            &zero_copy_if::get_send_buff, _data_transport, _1 +        )); +    } +} + +/*********************************************************************** + * Data Send + **********************************************************************/ +size_t e100_impl::get_max_send_samps_per_packet(void) const{ +    static const size_t hdr_size = 0 +        + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +    ; +    size_t bpp = _data_transport->get_send_frame_size() - hdr_size; +    return bpp/_tx_otw_type.get_sample_size(); +} + +size_t e100_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 + **********************************************************************/ +size_t e100_impl::get_max_recv_samps_per_packet(void) const{ +    static const size_t hdr_size = 0 +        + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +        + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +    ; +    size_t bpp = _data_transport->get_recv_frame_size() - hdr_size; +    return bpp/_rx_otw_type.get_sample_size(); +} + +size_t e100_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 +    ); +} + +/*********************************************************************** + * Async Recv + **********************************************************************/ +bool e100_impl::recv_async_msg( +    async_metadata_t &async_metadata, double timeout +){ +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return _io_impl->async_msg_fifo.pop_with_timed_wait(async_metadata, timeout); +} diff --git a/host/lib/usrp/gps_ctrl.cpp b/host/lib/usrp/gps_ctrl.cpp new file mode 100644 index 000000000..3cef1ef19 --- /dev/null +++ b/host/lib/usrp/gps_ctrl.cpp @@ -0,0 +1,238 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/props.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/sensors.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/cstdint.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread/thread.hpp> +#include <boost/tokenizer.hpp> + +using namespace uhd; +using namespace boost::gregorian; +using namespace boost::posix_time; +using namespace boost::algorithm; +using namespace boost::this_thread; + +/*! + * A GPS control for Jackson Labs devices (and other NMEA compatible GPS's) + */ + +class gps_ctrl_impl : public gps_ctrl{ +public: +  gps_ctrl_impl(gps_send_fn_t send, gps_recv_fn_t recv){ +    _send = send; +    _recv = recv; + +    std::string reply; +    bool i_heard_some_nmea = false, i_heard_something_weird = false; +    gps_type = GPS_TYPE_NONE; +     +    //first we look for a Jackson Labs Firefly (since that's what we provide...) +    _recv(); //get whatever junk is in the rx buffer right now, and throw it away +    _send("HAAAY GUYYYYS\n"); //to elicit a response from the Firefly + +    //then we loop until we either timeout, or until we get a response that indicates we're a JL device +    int timeout = GPS_TIMEOUT_TRIES; +    while(timeout--) { +      reply = _recv(); +      if(reply.find("Command Error") != std::string::npos) { +        gps_type = GPS_TYPE_JACKSON_LABS; +        break; +      }  +      else if(reply.substr(0, 3) == "$GP") i_heard_some_nmea = true; //but keep looking for that "Command Error" response +      else if(reply.length() != 0) i_heard_something_weird = true; //probably wrong baud rate +      sleep(milliseconds(200)); +    } + +    if((i_heard_some_nmea) && (gps_type != GPS_TYPE_JACKSON_LABS)) gps_type = GPS_TYPE_GENERIC_NMEA; + +    if((gps_type == GPS_TYPE_NONE) && i_heard_something_weird) { +      UHD_MSG(error) << "GPS invalid reply \"" << reply << "\", assuming none available" << std::endl; +    } + +    switch(gps_type) { +    case GPS_TYPE_JACKSON_LABS: +      UHD_MSG(status) << "Found a Jackson Labs GPS" << std::endl; +      init_firefly(); + +    case GPS_TYPE_GENERIC_NMEA: +      if(gps_type == GPS_TYPE_GENERIC_NMEA) UHD_MSG(status) << "Found a generic NMEA GPS device" << std::endl; +      if(get_time() == ptime()) { +          UHD_MSG(status) << "No valid GPRMC packet found. Ignoring discovered GPS."; +          gps_type = GPS_TYPE_NONE; +      } +      break; + +    case GPS_TYPE_NONE: +    default: +      break; + +    } +  } + +  ~gps_ctrl_impl(void){ +    /* NOP */ +  } + +  //return a list of supported sensors +  std::vector<std::string> get_sensors(void) { +    std::vector<std::string> ret = boost::assign::list_of +        ("gps_gpgga") +        ("gps_gprmc") +        ("gps_gpgsa") +        ("gps_time") +        ("gps_locked"); +    return ret; +  } + +  uhd::sensor_value_t get_sensor(std::string key) { +    if(key == "gps_gpgga" +    or key == "gps_gprmc" +    or key == "gps_gpgsa") { +        return sensor_value_t( +                 boost::to_upper_copy(key), +                 get_nmea(boost::to_upper_copy(key.substr(4,8))), +                 ""); +    } +    else if(key == "gps_time") { +        return sensor_value_t("GPS epoch time", int(get_epoch_time()), "seconds"); +    } +    else if(key == "gps_locked") { +        return sensor_value_t("GPS lock status", locked(), "locked", "unlocked"); +    } +    else { +        UHD_THROW_PROP_GET_ERROR(); +    } +  } + +private: +  void init_firefly(void) { +    //issue some setup stuff so it spits out the appropriate data +    //none of these should issue replies so we don't bother looking for them +    //we have to sleep between commands because the JL device, despite not acking, takes considerable time to process each command. +     sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); +    _send("SYST:COMM:SER:ECHO OFF\n"); +     sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); +    _send("SYST:COMM:SER:PRO OFF\n"); +     sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); +    _send("GPS:GPGGA 1\n"); +     sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); +    _send("GPS:GGAST 0\n"); +     sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); +    _send("GPS:GPRMC 1\n"); +     sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); +    _send("GPS:GPGSA 1\n"); +     sleep(milliseconds(FIREFLY_STUPID_DELAY_MS)); +  } +  +  //retrieve a raw NMEA sentence +  std::string get_nmea(std::string msgtype) { +    msgtype.insert(0, "$"); +    std::string reply; +    if(not gps_detected()) { +        UHD_MSG(error) << "get_nmea(): unsupported GPS or no GPS detected"; +        return std::string(); +    } +    int timeout = GPS_TIMEOUT_TRIES; +    while(timeout--) { +        reply = _recv(); +        if(reply.substr(0, 6) == msgtype) +          return reply; +        boost::this_thread::sleep(milliseconds(GPS_TIMEOUT_DELAY_MS)); +    } +    UHD_MSG(error) << "get_nmea(): no " << msgtype << " message found"; +    return std::string(); +  } + +  //helper function to retrieve a field from an NMEA sentence +  std::string get_token(std::string sentence, size_t offset) { +    boost::tokenizer<boost::escaped_list_separator<char> > tok(sentence); +    std::vector<std::string> toked; + +    tok.assign(sentence); +    toked.assign(tok.begin(), tok.end()); + +    if(toked.size() <= offset) { +      UHD_MSG(error) << "get_token: too few tokens in reply " << sentence; +      return std::string(); +    } +    return toked[offset]; +  } + +  ptime get_time(void) { +    std::string reply = get_nmea("GPRMC"); + +    std::string datestr = get_token(reply, 9); +    std::string timestr = get_token(reply, 1); + +    if(datestr.size() == 0 or timestr.size() == 0) { +        return ptime(); +    } + +    //just trust me on this one +    return ptime( date(  +                     greg_year(boost::lexical_cast<int>(datestr.substr(4, 2)) + 2000), +                     greg_month(boost::lexical_cast<int>(datestr.substr(2, 2))),  +                     greg_day(boost::lexical_cast<int>(datestr.substr(0, 2)))  +                   ), +                  hours(  boost::lexical_cast<int>(timestr.substr(0, 2))) +                + minutes(boost::lexical_cast<int>(timestr.substr(2, 2))) +                + seconds(boost::lexical_cast<int>(timestr.substr(4, 2))) +             ); +  } +   +  time_t get_epoch_time(void) { +      return (get_time() - from_time_t(0)).total_seconds(); +  } + +  bool gps_detected(void) { +    return (gps_type != GPS_TYPE_NONE); +  } + +  bool locked(void) { +      std::string reply = get_nmea("GPGGA"); +      if(reply.size() <= 1) return false; + +      return (get_token(reply, 6) != "0"); +  } + +  gps_send_fn_t _send; +  gps_recv_fn_t _recv; + +  enum { +    GPS_TYPE_JACKSON_LABS, +    GPS_TYPE_GENERIC_NMEA, +    GPS_TYPE_NONE +  } gps_type; + +  static const int GPS_TIMEOUT_TRIES = 10; +  static const int GPS_TIMEOUT_DELAY_MS = 200; +  static const int FIREFLY_STUPID_DELAY_MS = 200; +}; + +/*********************************************************************** + * Public make function for the GPS control + **********************************************************************/ +gps_ctrl::sptr gps_ctrl::make(gps_send_fn_t send, gps_recv_fn_t recv){ +    return sptr(new gps_ctrl_impl(send, recv)); +} diff --git a/host/lib/usrp/mboard_eeprom.cpp b/host/lib/usrp/mboard_eeprom.cpp new file mode 100644 index 000000000..35fba547a --- /dev/null +++ b/host/lib/usrp/mboard_eeprom.cpp @@ -0,0 +1,360 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/types/mac_addr.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/foreach.hpp> +#include <algorithm> +#include <iostream> +#include <cstddef> + +using namespace uhd; +using namespace uhd::usrp; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const size_t SERIAL_LEN = 9; +static const size_t NAME_MAX_LEN = 32 - SERIAL_LEN; + +/*********************************************************************** + * Utility functions + **********************************************************************/ + +//! A wrapper around std::copy that takes ranges instead of iterators. +template<typename RangeSrc, typename RangeDst> inline +void byte_copy(const RangeSrc &src, RangeDst &dst){ +    std::copy(boost::begin(src), boost::end(src), boost::begin(dst)); +} + +//! create a string from a byte vector, return empty if invalid ascii +static const std::string bytes_to_string(const byte_vector_t &bytes){ +    std::string out; +    BOOST_FOREACH(boost::uint8_t byte, bytes){ +        if (byte < 32 or byte > 127) return out; +        out += byte; +    } +    return out; +} + +//! create a byte vector from a string, null terminate unless max length +static const byte_vector_t string_to_bytes(const std::string &string, size_t max_length){ +    byte_vector_t bytes; +    for (size_t i = 0; i < std::min(string.size(), max_length); i++){ +        bytes.push_back(string[i]); +    } +    if (bytes.size() < max_length - 1) bytes.push_back('\0'); +    return bytes; +} + +/*********************************************************************** + * Implementation of N100 load/store + **********************************************************************/ +static const boost::uint8_t N100_EEPROM_ADDR = 0x50; + +static const uhd::dict<std::string, boost::uint8_t> USRP_N100_OFFSETS = boost::assign::map_list_of +    ("rev-lsb-msb", 0x00) +    ("mac-addr", 0x02) +    ("ip-addr", 0x0C) +    //leave space here for other addresses (perhaps) +    ("prod-lsb-msb", 0x14) +    ("gpsdo", 0x17) +    ("serial", 0x18) +    ("name", 0x18 + SERIAL_LEN) +; + +enum n200_gpsdo_type{ +    N200_GPSDO_NONE = 0, +    N200_GPSDO_INTERNAL = 1, +    N200_GPSDO_ONBOARD = 2 +}; + +static void load_n100(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    //extract the revision number +    byte_vector_t rev_lsb_msb = iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["rev-lsb-msb"], 2); +    boost::uint16_t rev = (boost::uint16_t(rev_lsb_msb.at(0)) << 0) | (boost::uint16_t(rev_lsb_msb.at(1)) << 8); +    mb_eeprom["rev"] = boost::lexical_cast<std::string>(rev); + +    //extract the product code +    byte_vector_t prod_lsb_msb = iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["prod-lsb-msb"], 2); +    boost::uint16_t prod = (boost::uint16_t(prod_lsb_msb.at(0)) << 0) | (boost::uint16_t(prod_lsb_msb.at(1)) << 8); +    mb_eeprom["product"] = (prod == 0 or prod == 0xffff)? "" : boost::lexical_cast<std::string>(prod); + +    //extract the addresses +    mb_eeprom["mac-addr"] = mac_addr_t::from_bytes(iface.read_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["mac-addr"], 6 +    )).to_string(); + +    boost::asio::ip::address_v4::bytes_type ip_addr_bytes; +    byte_copy(iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["ip-addr"], 4), ip_addr_bytes); +    mb_eeprom["ip-addr"] = boost::asio::ip::address_v4(ip_addr_bytes).to_string(); + +    //gpsdo capabilities +    boost::uint8_t gpsdo_byte = iface.read_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["gpsdo"], 1).at(0); +    switch(n200_gpsdo_type(gpsdo_byte)){ +    case N200_GPSDO_INTERNAL: mb_eeprom["gpsdo"] = "internal"; break; +    case N200_GPSDO_ONBOARD: mb_eeprom["gpsdo"] = "onboard"; break; +    default: mb_eeprom["gpsdo"] = "none"; +    } + +    //extract the serial +    mb_eeprom["serial"] = bytes_to_string(iface.read_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["serial"], SERIAL_LEN +    )); + +    //extract the name +    mb_eeprom["name"] = bytes_to_string(iface.read_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["name"], NAME_MAX_LEN +    )); + +    //Empty serial correction: use the mac address to determine serial. +    //Older usrp2 models don't have a serial burned into EEPROM. +    //The lower mac address bits will function as the serial number. +    if (mb_eeprom["serial"].empty()){ +        byte_vector_t mac_addr_bytes = mac_addr_t::from_string(mb_eeprom["mac-addr"]).to_bytes(); +        unsigned serial = mac_addr_bytes.at(5) | (unsigned(mac_addr_bytes.at(4) & 0x0f) << 8); +        mb_eeprom["serial"] = boost::lexical_cast<std::string>(serial); +    } +} + +static void store_n100(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    //parse the revision number +    if (mb_eeprom.has_key("rev")){ +        boost::uint16_t rev = boost::lexical_cast<boost::uint16_t>(mb_eeprom["rev"]); +        byte_vector_t rev_lsb_msb = boost::assign::list_of +            (boost::uint8_t(rev >> 0)) +            (boost::uint8_t(rev >> 8)) +        ; +        iface.write_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["rev-lsb-msb"], rev_lsb_msb); +    } + +    //parse the product code +    if (mb_eeprom.has_key("product")){ +        boost::uint16_t prod = boost::lexical_cast<boost::uint16_t>(mb_eeprom["product"]); +        byte_vector_t prod_lsb_msb = boost::assign::list_of +            (boost::uint8_t(prod >> 0)) +            (boost::uint8_t(prod >> 8)) +        ; +        iface.write_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["prod-lsb-msb"], prod_lsb_msb); +    } + +    //store the addresses +    if (mb_eeprom.has_key("mac-addr")) iface.write_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["mac-addr"], +        mac_addr_t::from_string(mb_eeprom["mac-addr"]).to_bytes() +    ); + +    if (mb_eeprom.has_key("ip-addr")){ +        byte_vector_t ip_addr_bytes(4); +        byte_copy(boost::asio::ip::address_v4::from_string(mb_eeprom["ip-addr"]).to_bytes(), ip_addr_bytes); +        iface.write_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["ip-addr"], ip_addr_bytes); +    } + +    //gpsdo capabilities +    if (mb_eeprom.has_key("gpsdo")){ +        boost::uint8_t gpsdo_byte = N200_GPSDO_NONE; +        if (mb_eeprom["gpsdo"] == "internal") gpsdo_byte = N200_GPSDO_INTERNAL; +        if (mb_eeprom["gpsdo"] == "onboard") gpsdo_byte = N200_GPSDO_ONBOARD; +        iface.write_eeprom(N100_EEPROM_ADDR, USRP_N100_OFFSETS["gpsdo"], byte_vector_t(1, gpsdo_byte)); +    } + +    //store the serial +    if (mb_eeprom.has_key("serial")) iface.write_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["serial"], +        string_to_bytes(mb_eeprom["serial"], SERIAL_LEN) +    ); + +    //store the name +    if (mb_eeprom.has_key("name")) iface.write_eeprom( +        N100_EEPROM_ADDR, USRP_N100_OFFSETS["name"], +        string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) +    ); +} + +/*********************************************************************** + * Implementation of B000 load/store + **********************************************************************/ +static const boost::uint8_t B000_EEPROM_ADDR = 0x50; +static const size_t B000_SERIAL_LEN = 8; + +static const uhd::dict<std::string, boost::uint8_t> USRP_B000_OFFSETS = boost::assign::map_list_of +    ("serial", 0xf8) +    ("name", 0xf8 - NAME_MAX_LEN) +    ("mcr", 0xf8 - NAME_MAX_LEN - sizeof(boost::uint32_t)) +; + +static void load_b000(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    //extract the serial +    mb_eeprom["serial"] = bytes_to_string(iface.read_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["serial"], B000_SERIAL_LEN +    )); + +    //extract the name +    mb_eeprom["name"] = bytes_to_string(iface.read_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["name"], NAME_MAX_LEN +    )); + +    //extract master clock rate as a 32-bit uint in Hz +    boost::uint32_t master_clock_rate; +    const byte_vector_t rate_bytes = iface.read_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["mcr"], sizeof(master_clock_rate) +    ); +    std::copy( +        rate_bytes.begin(), rate_bytes.end(), //input +        reinterpret_cast<boost::uint8_t *>(&master_clock_rate) //output +    ); +    master_clock_rate = ntohl(master_clock_rate); +    if (master_clock_rate > 1e6 and master_clock_rate < 1e9){ +        mb_eeprom["mcr"] = boost::lexical_cast<std::string>(master_clock_rate); +    } +    else mb_eeprom["mcr"] = ""; +} + +static void store_b000(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    //store the serial +    if (mb_eeprom.has_key("serial")) iface.write_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["serial"], +        string_to_bytes(mb_eeprom["serial"], B000_SERIAL_LEN) +    ); + +    //store the name +    if (mb_eeprom.has_key("name")) iface.write_eeprom( +        B000_EEPROM_ADDR, USRP_B000_OFFSETS["name"], +        string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) +    ); + +    //store the master clock rate as a 32-bit uint in Hz +    if (mb_eeprom.has_key("mcr")){ +        boost::uint32_t master_clock_rate = boost::uint32_t(boost::lexical_cast<double>(mb_eeprom["mcr"])); +        master_clock_rate = htonl(master_clock_rate); +        const byte_vector_t rate_bytes( +            reinterpret_cast<const boost::uint8_t *>(&master_clock_rate), +            reinterpret_cast<const boost::uint8_t *>(&master_clock_rate) + sizeof(master_clock_rate) +        ); +        iface.write_eeprom( +            B000_EEPROM_ADDR, USRP_B000_OFFSETS["mcr"], rate_bytes +        ); +    } +} +/*********************************************************************** + * Implementation of E100 load/store + **********************************************************************/ +static const boost::uint8_t E100_EEPROM_ADDR = 0x51; + +struct e100_eeprom_map{ +    boost::uint16_t vendor; +    boost::uint16_t device; +    unsigned char revision; +    unsigned char content; +    unsigned char model[8]; +    unsigned char env_var[16]; +    unsigned char env_setting[64]; +    unsigned char serial[10]; +    unsigned char name[NAME_MAX_LEN]; +}; + +template <typename T> static const byte_vector_t to_bytes(const T &item){ +    return byte_vector_t( +        reinterpret_cast<const byte_vector_t::value_type *>(&item), +        reinterpret_cast<const byte_vector_t::value_type *>(&item)+sizeof(item) +    ); +} + +#define sizeof_member(struct_name, member_name) \ +    sizeof(reinterpret_cast<struct_name*>(NULL)->member_name) + +static void load_e100(mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ +    const size_t num_bytes = offsetof(e100_eeprom_map, model); +    byte_vector_t map_bytes = iface.read_eeprom(E100_EEPROM_ADDR, 0, num_bytes); +    e100_eeprom_map map; std::memcpy(&map, &map_bytes[0], map_bytes.size()); + +    mb_eeprom["vendor"] = boost::lexical_cast<std::string>(uhd::ntohx(map.vendor)); +    mb_eeprom["device"] = boost::lexical_cast<std::string>(uhd::ntohx(map.device)); +    mb_eeprom["revision"] = boost::lexical_cast<std::string>(unsigned(map.revision)); +    mb_eeprom["content"] = boost::lexical_cast<std::string>(unsigned(map.content)); + +    #define load_e100_string_xx(key) mb_eeprom[#key] = bytes_to_string(iface.read_eeprom( \ +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, key), sizeof_member(e100_eeprom_map, key) \ +    )); + +    load_e100_string_xx(model); +    load_e100_string_xx(env_var); +    load_e100_string_xx(env_setting); +    load_e100_string_xx(serial); +    load_e100_string_xx(name); +} + +static void store_e100(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ + +    if (mb_eeprom.has_key("vendor")) iface.write_eeprom( +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, vendor), +        to_bytes(uhd::htonx(boost::lexical_cast<boost::uint16_t>(mb_eeprom["vendor"]))) +    ); + +    if (mb_eeprom.has_key("device")) iface.write_eeprom( +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, device), +        to_bytes(uhd::htonx(boost::lexical_cast<boost::uint16_t>(mb_eeprom["device"]))) +    ); + +    if (mb_eeprom.has_key("revision")) iface.write_eeprom( +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, revision), +        byte_vector_t(1, boost::lexical_cast<unsigned>(mb_eeprom["revision"])) +    ); + +    if (mb_eeprom.has_key("content")) iface.write_eeprom( +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, content), +        byte_vector_t(1, boost::lexical_cast<unsigned>(mb_eeprom["content"])) +    ); + +    #define store_e100_string_xx(key) if (mb_eeprom.has_key(#key)) iface.write_eeprom( \ +        E100_EEPROM_ADDR, offsetof(e100_eeprom_map, key), \ +        string_to_bytes(mb_eeprom[#key], sizeof_member(e100_eeprom_map, key)) \ +    ); + +    store_e100_string_xx(model); +    store_e100_string_xx(env_var); +    store_e100_string_xx(env_setting); +    store_e100_string_xx(serial); +    store_e100_string_xx(name); +} + +/*********************************************************************** + * Implementation of mboard eeprom + **********************************************************************/ +mboard_eeprom_t::mboard_eeprom_t(void){ +    /* NOP */ +} + +mboard_eeprom_t::mboard_eeprom_t(i2c_iface &iface, map_type map){ +    switch(map){ +    case MAP_N100: load_n100(*this, iface); break; +    case MAP_B000: load_b000(*this, iface); break; +    case MAP_E100: load_e100(*this, iface); break; +    } +} + +void mboard_eeprom_t::commit(i2c_iface &iface, map_type map) const{ +    switch(map){ +    case MAP_N100: store_n100(*this, iface); break; +    case MAP_B000: store_b000(*this, iface); break; +    case MAP_E100: store_e100(*this, iface); break; +    } +} diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp new file mode 100644 index 000000000..49cbe522f --- /dev/null +++ b/host/lib/usrp/multi_usrp.cpp @@ -0,0 +1,698 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/property_tree.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/usrp/mboard_iface.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/gain_group.hpp> +#include <boost/thread.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <cmath> + +using namespace uhd; +using namespace uhd::usrp; + +const std::string multi_usrp::ALL_GAINS = ""; + +/*********************************************************************** + * Helper methods + **********************************************************************/ +static void do_samp_rate_warning_message( +    double target_rate, +    double actual_rate, +    const std::string &xx +){ +    static const double max_allowed_error = 1.0; //Sps +    if (std::abs(target_rate - actual_rate) > max_allowed_error){ +        UHD_MSG(warning) << boost::format( +            "The hardware does not support the requested %s sample rate:\n" +            "Target sample rate: %f MSps\n" +            "Actual sample rate: %f MSps\n" +        ) % xx % (target_rate/1e6) % (actual_rate/1e6); +    } +} + +static void do_tune_freq_warning_message( +    double target_freq, +    double actual_freq, +    const std::string &xx +){ +    static const double max_allowed_error = 1.0; //Hz +    if (std::abs(target_freq - actual_freq) > max_allowed_error){ +        UHD_MSG(warning) << boost::format( +            "The hardware does not support the requested %s frequency:\n" +            "Target frequency: %f MHz\n" +            "Actual frequency: %f MHz\n" +        ) % xx % (target_freq/1e6) % (actual_freq/1e6); +    } +} + +/*********************************************************************** + * Gain helper functions + **********************************************************************/ +static double get_gain_value(property_tree::sptr subtree){ +    return subtree->access<double>("value").get(); +} + +static void set_gain_value(property_tree::sptr subtree, const double gain){ +    subtree->access<double>("value").set(gain); +} + +static meta_range_t get_gain_range(property_tree::sptr subtree){ +    return subtree->access<meta_range_t>("range").get(); +} + +static gain_fcns_t make_gain_fcns_from_subtree(property_tree::sptr subtree){ +    gain_fcns_t gain_fcns; +    gain_fcns.get_range = boost::bind(&get_gain_range, subtree); +    gain_fcns.get_value = boost::bind(&get_gain_value, subtree); +    gain_fcns.set_value = boost::bind(&set_gain_value, subtree, _1); +    return gain_fcns; +} + +/*********************************************************************** + * Tune Helper Functions + **********************************************************************/ +static const double RX_SIGN = +1.0; +static const double TX_SIGN = -1.0; + +static tune_result_t tune_xx_subdev_and_dsp( +    const double xx_sign, +    property_tree::sptr dsp_subtree, +    property_tree::sptr rf_fe_subtree, +    const tune_request_t &tune_request +){ +    //------------------------------------------------------------------ +    //-- calculate the LO offset, only used with automatic policy +    //------------------------------------------------------------------ +    double lo_offset = 0.0; +    if (rf_fe_subtree->access<bool>("use_lo_offset").get()){ +        //If the local oscillator will be in the passband, use an offset. +        //But constrain the LO offset by the width of the filter bandwidth. +        const double rate = dsp_subtree->access<double>("rate/value").get(); +        const double bw = rf_fe_subtree->access<double>("bandwidth/value").get(); +        if (bw > rate) lo_offset = std::min((bw - rate)/2, rate/2); +    } + +    //------------------------------------------------------------------ +    //-- set the RF frequency depending upon the policy +    //------------------------------------------------------------------ +    double target_rf_freq = 0.0; +    switch (tune_request.rf_freq_policy){ +    case tune_request_t::POLICY_AUTO: +        target_rf_freq = tune_request.target_freq + lo_offset; +        rf_fe_subtree->access<double>("freq/value").set(target_rf_freq); +        break; + +    case tune_request_t::POLICY_MANUAL: +        target_rf_freq = tune_request.rf_freq; +        rf_fe_subtree->access<double>("freq/value").set(target_rf_freq); +        break; + +    case tune_request_t::POLICY_NONE: break; //does not set +    } +    const double actual_rf_freq = rf_fe_subtree->access<double>("freq/value").get(); + +    //------------------------------------------------------------------ +    //-- calculate the dsp freq, only used with automatic policy +    //------------------------------------------------------------------ +    double target_dsp_freq = actual_rf_freq - tune_request.target_freq; + +    //invert the sign on the dsp freq for transmit +    target_dsp_freq *= xx_sign; + +    //------------------------------------------------------------------ +    //-- set the dsp frequency depending upon the dsp frequency policy +    //------------------------------------------------------------------ +    switch (tune_request.dsp_freq_policy){ +    case tune_request_t::POLICY_AUTO: +        dsp_subtree->access<double>("freq/value").set(target_dsp_freq); +        break; + +    case tune_request_t::POLICY_MANUAL: +        target_dsp_freq = tune_request.dsp_freq; +        dsp_subtree->access<double>("freq/value").set(target_dsp_freq); +        break; + +    case tune_request_t::POLICY_NONE: break; //does not set +    } +    const double actual_dsp_freq = dsp_subtree->access<double>("freq/value").get(); + +    //------------------------------------------------------------------ +    //-- load and return the tune result +    //------------------------------------------------------------------ +    tune_result_t tune_result; +    tune_result.target_rf_freq = target_rf_freq; +    tune_result.actual_rf_freq = actual_rf_freq; +    tune_result.target_dsp_freq = target_dsp_freq; +    tune_result.actual_dsp_freq = actual_dsp_freq; +    return tune_result; +} + +static double derive_freq_from_xx_subdev_and_dsp( +    const double xx_sign, +    property_tree::sptr dsp_subtree, +    property_tree::sptr rf_fe_subtree +){ +    //extract actual dsp and IF frequencies +    const double actual_rf_freq = rf_fe_subtree->access<double>("freq/value").get(); +    const double actual_dsp_freq = dsp_subtree->access<double>("freq/value").get(); + +    //invert the sign on the dsp freq for transmit +    return actual_rf_freq - actual_dsp_freq * xx_sign; +} + +/*********************************************************************** + * Multi USRP Implementation + **********************************************************************/ +class multi_usrp_impl : public multi_usrp{ +public: +    multi_usrp_impl(const device_addr_t &addr){ +        _dev = device::make(addr); +        _tree = (*_dev)[0].as<property_tree::sptr>(); +    } + +    device::sptr get_device(void){ +        return _dev; +    } + +    /******************************************************************* +     * Mboard methods +     ******************************************************************/ +    void set_master_clock_rate(double rate, size_t mboard){ +        if (mboard != ALL_MBOARDS){ +            _tree->access<double>(mb_root(mboard) / "tick_rate").set(rate); +            return; +        } +        for (size_t m = 0; m < get_num_mboards(); m++){ +            set_master_clock_rate(rate, m); +        } +    } + +    double get_master_clock_rate(size_t mboard){ +        return _tree->access<double>(mb_root(mboard) / "tick_rate").get(); +    } + +    std::string get_pp_string(void){ +        std::string buff = str(boost::format( +            "%s USRP:\n" +            "  Device: %s\n" +        ) +            % ((get_num_mboards() > 1)? "Multi" : "Single") +            % (_tree->access<std::string>("/name").get()) +        ); +        for (size_t m = 0; m < get_num_mboards(); m++){ +            buff += str(boost::format( +                "  Mboard %d: %s\n" +            ) % m +                % (_tree->access<std::string>(mb_root(m) / "name").get()) +            ); +        } + +        //----------- rx side of life ---------------------------------- +        for (size_t m = 0, chan = 0; m < get_num_mboards(); m++){ +            for (; chan < (m + 1)*get_rx_subdev_spec(m).size(); chan++){ +                buff += str(boost::format( +                    "  RX Channel: %u\n" +                    "    RX DSP: %s\n" +                    "    RX Dboard: %s\n" +                    "    RX Subdev: %s\n" +                ) % chan +                    % rx_dsp_root(chan).leaf() +                    % rx_rf_fe_root(chan).branch_path().branch_path().leaf() +                    % (_tree->access<std::string>(rx_rf_fe_root(chan) / "name").get()) +                ); +            } +        } + +        //----------- tx side of life ---------------------------------- +        for (size_t m = 0, chan = 0; m < get_num_mboards(); m++){ +            for (; chan < (m + 1)*get_tx_subdev_spec(m).size(); chan++){ +                buff += str(boost::format( +                    "  TX Channel: %u\n" +                    "    TX DSP: %s\n" +                    "    TX Dboard: %s\n" +                    "    TX Subdev: %s\n" +                ) % chan +                    % tx_dsp_root(chan).leaf() +                    % tx_rf_fe_root(chan).branch_path().branch_path().leaf() +                    % (_tree->access<std::string>(tx_rf_fe_root(chan) / "name").get()) +                ); +            } +        } + +        return buff; +    } + +    std::string get_mboard_name(size_t mboard){ +        return _tree->access<std::string>(mb_root(mboard) / "name").get(); +    } + +    time_spec_t get_time_now(size_t mboard = 0){ +        return _tree->access<time_spec_t>(mb_root(mboard) / "time/now").get(); +    } + +    time_spec_t get_time_last_pps(size_t mboard = 0){ +        return _tree->access<time_spec_t>(mb_root(mboard) / "time/pps").get(); +    } + +    void set_time_now(const time_spec_t &time_spec, size_t mboard){ +        if (mboard != ALL_MBOARDS){ +            _tree->access<time_spec_t>(mb_root(mboard) / "time/now").set(time_spec); +            return; +        } +        for (size_t m = 0; m < get_num_mboards(); m++){ +            set_time_now(time_spec, m); +        } +    } + +    void set_time_next_pps(const time_spec_t &time_spec){ +        for (size_t m = 0; m < get_num_mboards(); m++){ +            _tree->access<time_spec_t>(mb_root(m) / "time/pps").set(time_spec); +        } +    } + +    void set_time_unknown_pps(const time_spec_t &time_spec){ +        UHD_MSG(status) << "    1) catch time transition at pps edge" << std::endl; +        time_spec_t time_start = get_time_now(); +        time_spec_t time_start_last_pps = get_time_last_pps(); +        while(true){ +            if (get_time_last_pps() != time_start_last_pps) break; +            if ((get_time_now() - time_start) > time_spec_t(1.1)){ +                throw uhd::runtime_error( +                    "Board 0 may not be getting a PPS signal!\n" +                    "No PPS detected within the time interval.\n" +                    "See the application notes for your device.\n" +                ); +            } +        } + +        UHD_MSG(status) << "    2) set times next pps (synchronously)" << std::endl; +        set_time_next_pps(time_spec); +        boost::this_thread::sleep(boost::posix_time::seconds(1)); + +        //verify that the time registers are read to be within a few RTT +        for (size_t m = 1; m < get_num_mboards(); m++){ +            time_spec_t time_0 = this->get_time_now(0); +            time_spec_t time_i = this->get_time_now(m); +            if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)){ //10 ms: greater than RTT but not too big +                UHD_MSG(warning) << boost::format( +                    "Detected time deviation between board %d and board 0.\n" +                    "Board 0 time is %f seconds.\n" +                    "Board %d time is %f seconds.\n" +                ) % m % time_0.get_real_secs() % m % time_i.get_real_secs(); +            } +        } +    } + +    bool get_time_synchronized(void){ +        for (size_t m = 1; m < get_num_mboards(); m++){ +            time_spec_t time_0 = this->get_time_now(0); +            time_spec_t time_i = this->get_time_now(m); +            if (time_i < time_0 or (time_i - time_0) > time_spec_t(0.01)) return false; +        } +        return true; +    } + +    void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t chan){ +        if (chan != ALL_CHANS){ +            _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); +            return; +        } +        for (size_t c = 0; c < get_rx_num_channels(); c++){ +            issue_stream_cmd(stream_cmd, c); +        } +    } + +    void set_clock_config(const clock_config_t &clock_config, size_t mboard){ +        if (mboard != ALL_MBOARDS){ +            //set the reference source... +            std::string clock_source; +            switch(clock_config.ref_source){ +            case clock_config_t::REF_INT: clock_source = "internal"; break; +            case clock_config_t::PPS_SMA: clock_source = "external"; break; +            case clock_config_t::PPS_MIMO: clock_source = "mimo"; break; +            default: clock_source = "unknown"; +            } +            if (clock_source == "external" and clock_config.pps_polarity == clock_config_t::PPS_NEG) clock_source = "_external_"; +            _tree->access<std::string>(mb_root(mboard) / "clock_source" / "value").set(clock_source); + +            //set the time source +            std::string time_source; +            switch(clock_config.pps_source){ +            case clock_config_t::PPS_INT: time_source = "internal"; break; +            case clock_config_t::PPS_SMA: time_source = "external"; break; +            case clock_config_t::PPS_MIMO: time_source = "mimo"; break; +            default: time_source = "unknown"; +            } +            _tree->access<std::string>(mb_root(mboard) / "time_source" / "value").set(time_source); +            return; +        } +        for (size_t m = 0; m < get_num_mboards(); m++){ +            set_clock_config(clock_config, m); +        } +    } + +    size_t get_num_mboards(void){ +        return _tree->list("/mboards").size(); +    } + +    sensor_value_t get_mboard_sensor(const std::string &name, size_t mboard){ +        return _tree->access<sensor_value_t>(mb_root(mboard) / "sensors" / name).get(); +    } + +    std::vector<std::string> get_mboard_sensor_names(size_t mboard){ +        return _tree->list(mb_root(mboard) / "sensors"); +    } + +    mboard_iface::sptr get_mboard_iface(size_t){ +        return mboard_iface::sptr(); //not implemented +    } + +    /******************************************************************* +     * RX methods +     ******************************************************************/ +    void set_rx_subdev_spec(const subdev_spec_t &spec, size_t mboard){ +        if (mboard != ALL_MBOARDS){ +            _tree->access<subdev_spec_t>(mb_root(mboard) / "rx_subdev_spec").set(spec); +            return; +        } +        for (size_t m = 0; m < get_num_mboards(); m++){ +            set_rx_subdev_spec(spec, m); +        } +    } + +    subdev_spec_t get_rx_subdev_spec(size_t mboard){ +        return _tree->access<subdev_spec_t>(mb_root(mboard) / "rx_subdev_spec").get(); +    } + +    size_t get_rx_num_channels(void){ +        size_t sum = 0; +        for (size_t m = 0; m < get_num_mboards(); m++){ +            sum += get_rx_subdev_spec(m).size(); +        } +        return sum; +    } + +    std::string get_rx_subdev_name(size_t chan){ +        return _tree->access<std::string>(rx_rf_fe_root(chan) / "name").get(); +    } + +    void set_rx_rate(double rate, size_t chan){ +        if (chan != ALL_CHANS){ +            _tree->access<double>(rx_dsp_root(chan) / "rate" / "value").set(rate); +            do_samp_rate_warning_message(rate, get_rx_rate(chan), "RX"); +            return; +        } +        for (size_t c = 0; c < get_rx_num_channels(); c++){ +            set_rx_rate(rate, c); +        } +    } + +    double get_rx_rate(size_t chan){ +        return _tree->access<double>(rx_dsp_root(chan) / "rate" / "value").get(); +    } + +    tune_result_t set_rx_freq(const tune_request_t &tune_request, size_t chan){ +        tune_result_t r = tune_xx_subdev_and_dsp(RX_SIGN, _tree->subtree(rx_dsp_root(chan)), _tree->subtree(rx_rf_fe_root(chan)), tune_request); +        do_tune_freq_warning_message(tune_request.target_freq, get_rx_freq(chan), "RX"); +        return r; +    } + +    double get_rx_freq(size_t chan){ +        return derive_freq_from_xx_subdev_and_dsp(RX_SIGN, _tree->subtree(rx_dsp_root(chan)), _tree->subtree(rx_rf_fe_root(chan))); +    } + +    freq_range_t get_rx_freq_range(size_t chan){ +        meta_range_t range = _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "freq" / "range").get(); +        meta_range_t dsp_range = _tree->access<meta_range_t>(rx_dsp_root(chan) / "freq" / "range").get(); +        return meta_range_t(range.start() + dsp_range.start(), range.stop() + dsp_range.stop(), dsp_range.step()); +    } + +    void set_rx_gain(double gain, const std::string &name, size_t chan){ +        return rx_gain_group(chan)->set_value(gain, name); +    } + +    double get_rx_gain(const std::string &name, size_t chan){ +        return rx_gain_group(chan)->get_value(name); +    } + +    gain_range_t get_rx_gain_range(const std::string &name, size_t chan){ +        return rx_gain_group(chan)->get_range(name); +    } + +    std::vector<std::string> get_rx_gain_names(size_t chan){ +        return rx_gain_group(chan)->get_names(); +    } + +    void set_rx_antenna(const std::string &ant, size_t chan){ +        _tree->access<std::string>(rx_rf_fe_root(chan) / "antenna" / "value").set(ant); +    } + +    std::string get_rx_antenna(size_t chan){ +        return _tree->access<std::string>(rx_rf_fe_root(chan) / "antenna" / "value").get(); +    } + +    std::vector<std::string> get_rx_antennas(size_t chan){ +        return _tree->access<std::vector<std::string> >(rx_rf_fe_root(chan) / "antenna" / "options").get(); +    } + +    void set_rx_bandwidth(double bandwidth, size_t chan){ +        _tree->access<double>(rx_rf_fe_root(chan) / "bandwidth" / "value").set(bandwidth); +    } + +    double get_rx_bandwidth(size_t chan){ +        return _tree->access<double>(rx_rf_fe_root(chan) / "bandwidth" / "value").get(); +    } + +    dboard_iface::sptr get_rx_dboard_iface(size_t chan){ +        return _tree->access<dboard_iface::sptr>(rx_rf_fe_root(chan).branch_path().branch_path() / "iface").get(); +    } + +    sensor_value_t get_rx_sensor(const std::string &name, size_t chan){ +        return _tree->access<sensor_value_t>(rx_rf_fe_root(chan) / "sensors" / name).get(); +    } + +    std::vector<std::string> get_rx_sensor_names(size_t chan){ +        return _tree->list(rx_rf_fe_root(chan) / "sensors"); +    } + +    /******************************************************************* +     * TX methods +     ******************************************************************/ +    void set_tx_subdev_spec(const subdev_spec_t &spec, size_t mboard){ +        if (mboard != ALL_MBOARDS){ +            _tree->access<subdev_spec_t>(mb_root(mboard) / "tx_subdev_spec").set(spec); +            return; +        } +        for (size_t m = 0; m < get_num_mboards(); m++){ +            set_tx_subdev_spec(spec, m); +        } +    } + +    subdev_spec_t get_tx_subdev_spec(size_t mboard){ +        return _tree->access<subdev_spec_t>(mb_root(mboard) / "tx_subdev_spec").get(); +    } + +    std::string get_tx_subdev_name(size_t chan){ +        return _tree->access<std::string>(tx_rf_fe_root(chan) / "name").get(); +    } + +    size_t get_tx_num_channels(void){ +        size_t sum = 0; +        for (size_t m = 0; m < get_num_mboards(); m++){ +            sum += get_tx_subdev_spec(m).size(); +        } +        return sum; +    } + +    void set_tx_rate(double rate, size_t chan){ +        if (chan != ALL_CHANS){ +            _tree->access<double>(tx_dsp_root(chan) / "rate" / "value").set(rate); +            do_samp_rate_warning_message(rate, get_tx_rate(chan), "TX"); +            return; +        } +        for (size_t c = 0; c < get_tx_num_channels(); c++){ +            set_tx_rate(rate, c); +        } +    } + +    double get_tx_rate(size_t chan){ +        return _tree->access<double>(tx_dsp_root(chan) / "rate" / "value").get(); +    } + +    tune_result_t set_tx_freq(const tune_request_t &tune_request, size_t chan){ +        tune_result_t r = tune_xx_subdev_and_dsp(TX_SIGN, _tree->subtree(tx_dsp_root(chan)), _tree->subtree(tx_rf_fe_root(chan)), tune_request); +        do_tune_freq_warning_message(tune_request.target_freq, get_tx_freq(chan), "TX"); +        return r; +    } + +    double get_tx_freq(size_t chan){ +        return derive_freq_from_xx_subdev_and_dsp(TX_SIGN, _tree->subtree(tx_dsp_root(chan)), _tree->subtree(tx_rf_fe_root(chan))); +    } + +    freq_range_t get_tx_freq_range(size_t chan){ +        meta_range_t range = _tree->access<meta_range_t>(tx_rf_fe_root(chan) / "freq" / "range").get(); +        meta_range_t dsp_range = _tree->access<meta_range_t>(tx_dsp_root(chan) / "freq" / "range").get(); +        return meta_range_t(range.start() + dsp_range.start(), range.stop() + dsp_range.stop(), dsp_range.step()); +    } + +    void set_tx_gain(double gain, const std::string &name, size_t chan){ +        return tx_gain_group(chan)->set_value(gain, name); +    } + +    double get_tx_gain(const std::string &name, size_t chan){ +        return tx_gain_group(chan)->get_value(name); +    } + +    gain_range_t get_tx_gain_range(const std::string &name, size_t chan){ +        return tx_gain_group(chan)->get_range(name); +    } + +    std::vector<std::string> get_tx_gain_names(size_t chan){ +        return tx_gain_group(chan)->get_names(); +    } + +    void set_tx_antenna(const std::string &ant, size_t chan){ +        _tree->access<std::string>(tx_rf_fe_root(chan) / "antenna" / "value").set(ant); +    } + +    std::string get_tx_antenna(size_t chan){ +        return _tree->access<std::string>(tx_rf_fe_root(chan) / "antenna" / "value").get(); +    } + +    std::vector<std::string> get_tx_antennas(size_t chan){ +        return _tree->access<std::vector<std::string> >(tx_rf_fe_root(chan) / "antenna" / "options").get(); +    } + +    void set_tx_bandwidth(double bandwidth, size_t chan){ +        _tree->access<double>(tx_rf_fe_root(chan) / "bandwidth" / "value").set(bandwidth); +    } + +    double get_tx_bandwidth(size_t chan){ +        return _tree->access<double>(tx_rf_fe_root(chan) / "bandwidth" / "value").get(); +    } + +    dboard_iface::sptr get_tx_dboard_iface(size_t chan){ +        return _tree->access<dboard_iface::sptr>(tx_rf_fe_root(chan).branch_path().branch_path() / "iface").get(); +    } + +    sensor_value_t get_tx_sensor(const std::string &name, size_t chan){ +        return _tree->access<sensor_value_t>(tx_rf_fe_root(chan) / "sensors" / name).get(); +    } + +    std::vector<std::string> get_tx_sensor_names(size_t chan){ +        return _tree->list(tx_rf_fe_root(chan) / "sensors"); +    } + +private: +    device::sptr _dev; +    property_tree::sptr _tree; + +    struct mboard_chan_pair{ +        size_t mboard, chan; +        mboard_chan_pair(void): mboard(0), chan(0){} +    }; + +    mboard_chan_pair rx_chan_to_mcp(size_t chan){ +        mboard_chan_pair mcp; +        mcp.chan = chan; +        for (mcp.mboard = 0; mcp.mboard < get_num_mboards(); mcp.mboard++){ +            size_t sss = get_rx_subdev_spec(mcp.mboard).size(); +            if (mcp.chan < sss) break; +            mcp.chan -= sss; +        } +        return mcp; +    } + +    mboard_chan_pair tx_chan_to_mcp(size_t chan){ +        mboard_chan_pair mcp; +        mcp.chan = chan; +        for (mcp.mboard = 0; mcp.mboard < get_num_mboards(); mcp.mboard++){ +            size_t sss = get_tx_subdev_spec(mcp.mboard).size(); +            if (mcp.chan < sss) break; +            mcp.chan -= sss; +        } +        return mcp; +    } + +    fs_path mb_root(const size_t mboard){ +        const std::string name = _tree->list("/mboards").at(mboard); +        return "/mboards/" + name; +    } + +    fs_path rx_dsp_root(const size_t chan){ +        mboard_chan_pair mcp = rx_chan_to_mcp(chan); +        const std::string name = _tree->list(mb_root(mcp.mboard) / "rx_dsps").at(mcp.chan); +        return mb_root(mcp.mboard) / "rx_dsps" / name; +    } + +    fs_path tx_dsp_root(const size_t chan){ +        mboard_chan_pair mcp = tx_chan_to_mcp(chan); +        const std::string name = _tree->list(mb_root(mcp.mboard) / "tx_dsps").at(mcp.chan); +        return mb_root(mcp.mboard) / "tx_dsps" / name; +    } + +    fs_path rx_rf_fe_root(const size_t chan){ +        mboard_chan_pair mcp = rx_chan_to_mcp(chan); +        const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); +        return mb_root(mcp.mboard) / "dboards" / spec.db_name / "rx_frontends" / spec.sd_name; +    } + +    fs_path tx_rf_fe_root(const size_t chan){ +        mboard_chan_pair mcp = tx_chan_to_mcp(chan); +        const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); +        return mb_root(mcp.mboard) / "dboards" / spec.db_name / "tx_frontends" / spec.sd_name; +    } + +    gain_group::sptr rx_gain_group(size_t chan){ +        mboard_chan_pair mcp = rx_chan_to_mcp(chan); +        const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); +        gain_group::sptr gg = gain_group::make(); +        BOOST_FOREACH(const std::string &name, _tree->list(mb_root(mcp.mboard) / "rx_codecs" / spec.db_name / "gains")){ +            gg->register_fcns("ADC-"+name, make_gain_fcns_from_subtree(_tree->subtree(mb_root(mcp.mboard) / "rx_codecs" / spec.db_name / "gains" / name)), 0 /* low prio */); +        } +        BOOST_FOREACH(const std::string &name, _tree->list(rx_rf_fe_root(chan) / "gains")){ +            gg->register_fcns(name, make_gain_fcns_from_subtree(_tree->subtree(rx_rf_fe_root(chan) / "gains" / name)), 1 /* high prio */); +        } +        return gg; +    } + +    gain_group::sptr tx_gain_group(size_t chan){ +        mboard_chan_pair mcp = tx_chan_to_mcp(chan); +        const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); +        gain_group::sptr gg = gain_group::make(); +        BOOST_FOREACH(const std::string &name, _tree->list(mb_root(mcp.mboard) / "tx_codecs" / spec.db_name / "gains")){ +            gg->register_fcns("ADC-"+name, make_gain_fcns_from_subtree(_tree->subtree(mb_root(mcp.mboard) / "tx_codecs" / spec.db_name / "gains" / name)), 1 /* high prio */); +        } +        BOOST_FOREACH(const std::string &name, _tree->list(tx_rf_fe_root(chan) / "gains")){ +            gg->register_fcns(name, make_gain_fcns_from_subtree(_tree->subtree(tx_rf_fe_root(chan) / "gains" / name)), 0 /* low prio */); +        } +        return gg; +    } +}; + +/*********************************************************************** + * The Make Function + **********************************************************************/ +multi_usrp::sptr multi_usrp::make(const device_addr_t &dev_addr){ +    return sptr(new multi_usrp_impl(dev_addr)); +} diff --git a/host/lib/usrp/subdev_spec.cpp b/host/lib/usrp/subdev_spec.cpp new file mode 100644 index 000000000..6912afec8 --- /dev/null +++ b/host/lib/usrp/subdev_spec.cpp @@ -0,0 +1,80 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/exception.hpp> +#include <boost/algorithm/string.hpp> //for split +#include <boost/tokenizer.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <sstream> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; + +#define pair_tokenizer(inp) \ +    boost::tokenizer<boost::char_separator<char> > \ +    (inp, boost::char_separator<char>(" ")) + +subdev_spec_pair_t::subdev_spec_pair_t( +    const std::string &db_name, const std::string &sd_name +): +    db_name(db_name), +    sd_name(sd_name) +{ +    /* NOP */ +} + +bool usrp::operator==(const subdev_spec_pair_t &lhs, const subdev_spec_pair_t &rhs){ +    return (lhs.db_name == rhs.db_name) and (lhs.sd_name == rhs.sd_name); +} + +subdev_spec_t::subdev_spec_t(const std::string &markup){ +    BOOST_FOREACH(const std::string &pair, pair_tokenizer(markup)){ +        if (pair.empty()) continue; +        std::vector<std::string> db_sd; boost::split(db_sd, pair, boost::is_any_of(":")); +        switch(db_sd.size()){ +        case 1: this->push_back(subdev_spec_pair_t("", db_sd.front())); break; +        case 2: this->push_back(subdev_spec_pair_t(db_sd.front(), db_sd.back())); break; +        default: throw uhd::value_error("invalid subdev-spec markup string: "+markup); +        } +    } +} + +std::string subdev_spec_t::to_pp_string(void) const{ +    if (this->size() == 0) return "Empty Subdevice Specification"; + +    std::stringstream ss; +    size_t count = 0; +    ss << "Subdevice Specification:" << std::endl; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, *this){ +        ss << boost::format( +            "    Channel %d: Daughterboard %s, Subdevice %s" +        ) % (count++) % pair.db_name % pair.sd_name << std::endl; +    } +    return ss.str(); +} + +std::string subdev_spec_t::to_string(void) const{ +    std::string markup; +    size_t count = 0; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, *this){ +        markup += ((count++)? " " : "") + pair.db_name + ":" + pair.sd_name; +    } +    return markup; +} diff --git a/host/lib/usrp/usrp1/CMakeLists.txt b/host/lib/usrp/usrp1/CMakeLists.txt new file mode 100644 index 000000000..70bebe502 --- /dev/null +++ b/host/lib/usrp/usrp1/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# 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 USRP1 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF) + +IF(ENABLE_USRP1) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/soft_time_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/usrp1_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/usrp1_impl.cpp +    ) +ENDIF(ENABLE_USRP1) diff --git a/host/lib/usrp/usrp1/codec_ctrl.cpp b/host/lib/usrp/usrp1/codec_ctrl.cpp new file mode 100644 index 000000000..c82569ea3 --- /dev/null +++ b/host/lib/usrp/usrp1/codec_ctrl.cpp @@ -0,0 +1,419 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9862_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/assign/list_of.hpp> +#include <iomanip> + +using namespace uhd; + +const gain_range_t usrp1_codec_ctrl::tx_pga_gain_range(-20, 0, double(0.1)); +const gain_range_t usrp1_codec_ctrl::rx_pga_gain_range(0, 20, 1); + +/*********************************************************************** + * Codec Control Implementation + **********************************************************************/ +class usrp1_codec_ctrl_impl : public usrp1_codec_ctrl { +public: +    //structors +    usrp1_codec_ctrl_impl(spi_iface::sptr iface, int spi_slave); +    ~usrp1_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); + +    //duc control +    void set_duc_freq(double freq, double); +    void enable_tx_digital(bool enb); + +    //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); +     +    //rx adc buffer control +    void bypass_adc_buffers(bool bypass); + +private: +    spi_iface::sptr _iface; +    int _spi_slave; +    ad9862_regs_t _ad9862_regs; +    void send_reg(boost::uint8_t addr); +    void recv_reg(boost::uint8_t addr); + +    double coarse_tune(double codec_rate, double freq); +    double fine_tune(double codec_rate, double freq); +}; + +/*********************************************************************** + * Codec Control Structors + **********************************************************************/ +usrp1_codec_ctrl_impl::usrp1_codec_ctrl_impl(spi_iface::sptr iface, int spi_slave){ +    _iface = iface; +    _spi_slave = spi_slave; + +    //soft reset +    _ad9862_regs.soft_reset = 1; +    this->send_reg(0); + +    //initialize the codec register settings +    _ad9862_regs.sdio_bidir = ad9862_regs_t::SDIO_BIDIR_SDIO_SDO; +    _ad9862_regs.lsb_first = ad9862_regs_t::LSB_FIRST_MSB; +    _ad9862_regs.soft_reset = 0; + +    //setup rx side of codec +    _ad9862_regs.byp_buffer_a = 1; +    _ad9862_regs.byp_buffer_b = 1; +    _ad9862_regs.buffer_a_pd = 1; +    _ad9862_regs.buffer_b_pd = 1; +    _ad9862_regs.rx_pga_a = 0; +    _ad9862_regs.rx_pga_b = 0; +    _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_pga_gain = 199; +    _ad9862_regs.tx_hilbert = ad9862_regs_t::TX_HILBERT_DIS; +    _ad9862_regs.interp = ad9862_regs_t::INTERP_4; +    _ad9862_regs.tx_twos_comp = 1; +    _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_NCO; +    _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; +    _ad9862_regs.dac_a_coarse_gain = 0x3; +    _ad9862_regs.dac_b_coarse_gain = 0x3; + +    //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; + +    //setup clockout +    _ad9862_regs.clkout2_div_factor = ad9862_regs_t::CLKOUT2_DIV_FACTOR_2; + +    //write the register settings to the codec +    for (boost::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); +} + +usrp1_codec_ctrl_impl::~usrp1_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 usrp1_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 usrp1_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 usrp1_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 usrp1_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 usrp1_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 usrp1_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 usrp1_codec_ctrl_impl::send_reg(boost::uint8_t addr) +{ +    boost::uint32_t reg = _ad9862_regs.get_write_reg(addr); + +    UHD_LOGV(often) +        << "codec control write reg: 0x" +        << std::setw(8) << std::hex << reg << std::endl +    ; +    _iface->write_spi(_spi_slave, +                         spi_config_t::EDGE_RISE, reg, 16); +} + +void usrp1_codec_ctrl_impl::recv_reg(boost::uint8_t addr) +{ +    boost::uint32_t reg = _ad9862_regs.get_read_reg(addr); + +    UHD_LOGV(often) +        << "codec control read reg: 0x" +        << std::setw(8) << std::hex << reg << std::endl +    ; + +    boost::uint32_t ret = _iface->read_spi(_spi_slave, +                                        spi_config_t::EDGE_RISE, reg, 16); + +    UHD_LOGV(often) +        << "codec control read ret: 0x" +        << std::setw(8) << std::hex << ret << std::endl +    ; + +    _ad9862_regs.set_reg(addr, boost::uint16_t(ret)); +} + +/*********************************************************************** + * DUC tuning  + **********************************************************************/ +double usrp1_codec_ctrl_impl::coarse_tune(double codec_rate, double freq) +{ +    double coarse_freq; + +    double coarse_freq_1 = codec_rate / 8; +    double coarse_freq_2 = codec_rate / 4; +    double coarse_limit_1 = coarse_freq_1 / 2; +    double coarse_limit_2 = (coarse_freq_1 + coarse_freq_2) / 2; +    double max_freq = coarse_freq_2 + .09375 * codec_rate; +  +    if (freq < -max_freq) { +        return false; +    } +    else if (freq < -coarse_limit_2) { +        _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_NEG_SHIFT; +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_4; +        coarse_freq = -coarse_freq_2; +    } +    else if (freq < -coarse_limit_1) { +        _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_NEG_SHIFT; +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_8; +        coarse_freq = -coarse_freq_1; +    } +    else if (freq < coarse_limit_1) { +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_BYPASS; +        coarse_freq = 0;  +    } +    else if (freq < coarse_limit_2) { +        _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_POS_SHIFT; +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_8; +        coarse_freq = coarse_freq_1; +    } +    else if (freq <= max_freq) { +        _ad9862_regs.neg_coarse_tune = ad9862_regs_t::NEG_COARSE_TUNE_POS_SHIFT; +        _ad9862_regs.coarse_mod = ad9862_regs_t::COARSE_MOD_FDAC_4; +        coarse_freq = coarse_freq_2; +    } +    else { +        return 0;  +    } + +    return coarse_freq; +} + +double usrp1_codec_ctrl_impl::fine_tune(double codec_rate, double target_freq) +{ +    static const double scale_factor = std::pow(2.0, 24); + +    boost::uint32_t freq_word = boost::uint32_t( +        boost::math::round(abs((target_freq / codec_rate) * scale_factor))); + +    double actual_freq = freq_word * codec_rate / scale_factor; + +    if (target_freq < 0) { +        _ad9862_regs.neg_fine_tune = ad9862_regs_t::NEG_FINE_TUNE_NEG_SHIFT; +        actual_freq = -actual_freq;  +    } +    else { +        _ad9862_regs.neg_fine_tune = ad9862_regs_t::NEG_FINE_TUNE_POS_SHIFT; +    }  + +    _ad9862_regs.fine_mode = ad9862_regs_t::FINE_MODE_NCO; +    _ad9862_regs.ftw_23_16 = (freq_word >> 16) & 0xff; +    _ad9862_regs.ftw_15_8  = (freq_word >>  8) & 0xff; +    _ad9862_regs.ftw_7_0   = (freq_word >>  0) & 0xff; + +    return actual_freq; +} + +void usrp1_codec_ctrl_impl::set_duc_freq(double freq, double rate) +{ +    double codec_rate = rate * 2; +    double coarse_freq = coarse_tune(codec_rate, freq); +    double fine_freq = fine_tune(codec_rate / 4, freq - coarse_freq); + +    UHD_LOG +        << "ad9862 tuning result:" << std::endl +        << "   requested:   " << freq << std::endl +        << "   actual:      " << coarse_freq + fine_freq << std::endl +        << "   coarse freq: " << coarse_freq << std::endl +        << "   fine freq:   " << fine_freq << std::endl +        << "   codec rate:  " << codec_rate << std::endl +    ; + +    this->send_reg(20); +    this->send_reg(21); +    this->send_reg(22); +    this->send_reg(23); +} + +void usrp1_codec_ctrl_impl::enable_tx_digital(bool enb){ +    _ad9862_regs.tx_digital_pd = (enb)? 0 : 1; +    this->send_reg(8); +} + +/*********************************************************************** + * Codec Control ADC buffer bypass + * Disable this for AC-coupled daughterboards (TVRX) + * By default it is initialized TRUE. + **********************************************************************/ +void usrp1_codec_ctrl_impl::bypass_adc_buffers(bool bypass) { +    _ad9862_regs.byp_buffer_a = bypass; +    _ad9862_regs.byp_buffer_b = bypass; +    this->send_reg(2); +} + +/*********************************************************************** + * Codec Control Make + **********************************************************************/ +usrp1_codec_ctrl::sptr usrp1_codec_ctrl::make(spi_iface::sptr iface, +                                              int spi_slave) +{ +    return sptr(new usrp1_codec_ctrl_impl(iface, spi_slave)); +} diff --git a/host/lib/usrp/usrp1/codec_ctrl.hpp b/host/lib/usrp/usrp1/codec_ctrl.hpp new file mode 100644 index 000000000..70f4e0b61 --- /dev/null +++ b/host/lib/usrp/usrp1/codec_ctrl.hpp @@ -0,0 +1,99 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP1_CODEC_CTRL_HPP +#define INCLUDED_USRP1_CODEC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <uhd/types/ranges.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp1 codec control: + * - Init/power down codec. + * - Read aux adc, write aux dac. + */ +class usrp1_codec_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp1_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 clock control object. +     * \param iface the spi iface object +     * \param spi_slave which spi device +     */ +    static sptr make(uhd::spi_iface::sptr iface, int spi_slave); + +    //! 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 +    }; + +    /*! +     * 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; + +    //! Set the TX modulator frequency +    virtual void set_duc_freq(double freq, double rate) = 0; + +    //! Enable or disable the digital part of the DAC +    virtual void enable_tx_digital(bool enb) = 0; + +    //! Enable or disable ADC buffer bypass +    virtual void bypass_adc_buffers(bool bypass) = 0; +}; + +#endif /* INCLUDED_USRP1_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp new file mode 100644 index 000000000..449ec64fe --- /dev/null +++ b/host/lib/usrp/usrp1/dboard_iface.cpp @@ -0,0 +1,397 @@ +// +// 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 "usrp1_iface.hpp" +#include "usrp1_impl.hpp" +#include "fpga_regs_common.h" +#include "usrp_spi_defs.h" +#include "fpga_regs_standard.h" +#include "codec_ctrl.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +static const dboard_id_t tvrx_id(0x0040); + +class usrp1_dboard_iface : public dboard_iface { +public: + +    usrp1_dboard_iface(usrp1_iface::sptr iface, +                       usrp1_codec_ctrl::sptr codec, +                       usrp1_impl::dboard_slot_t dboard_slot, +                       const double master_clock_rate, +                       const dboard_id_t &rx_dboard_id +    ): +        _dboard_slot(dboard_slot), +        _master_clock_rate(master_clock_rate), +        _rx_dboard_id(rx_dboard_id) +    { +        _iface = iface; +        _codec = codec; + +        //init the clock rate shadows +        this->set_clock_rate(UNIT_RX, this->get_clock_rates(UNIT_RX).front()); +        this->set_clock_rate(UNIT_TX, this->get_clock_rates(UNIT_TX).front()); +         +        //yes this is evil but it's necessary for TVRX to work on USRP1 +        if(_rx_dboard_id == tvrx_id) _codec->bypass_adc_buffers(false); +        //else _codec->bypass_adc_buffers(false); //don't think this is necessary +    } + +    ~usrp1_dboard_iface() +    { +        /* NOP */ +    } + +    special_props_t get_special_props() +    { +        special_props_t props; +        props.soft_clock_divider = true; +        props.mangle_i2c_addrs = (_dboard_slot == usrp1_impl::DBOARD_SLOT_B); +        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: +    usrp1_iface::sptr _iface; +    usrp1_codec_ctrl::sptr _codec; +    uhd::dict<unit_t, double> _clock_rates; +    const usrp1_impl::dboard_slot_t _dboard_slot; +    const double _master_clock_rate; +    const dboard_id_t _rx_dboard_id; +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr usrp1_impl::make_dboard_iface(usrp1_iface::sptr iface, +                                           usrp1_codec_ctrl::sptr codec, +                                           usrp1_impl::dboard_slot_t dboard_slot, +                                           const double master_clock_rate, +                                           const dboard_id_t &rx_dboard_id +){ +    return dboard_iface::sptr(new usrp1_dboard_iface( +        iface, codec, dboard_slot, master_clock_rate, rx_dboard_id +    )); +} + +/*********************************************************************** + * Clock Rates + **********************************************************************/ +static const dboard_id_t dbsrx_classic_id(0x0002); + +/* + * Daughterboard reference clock register + * + * Bit  7    - 1 turns on refclk, 0 allows IO use + * Bits 6:0  - Divider value + */ +void usrp1_dboard_iface::set_clock_rate(unit_t unit, double rate) +{ +    assert_has(this->get_clock_rates(unit), rate, "dboard clock rate"); +    _clock_rates[unit] = rate; + +    if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ +        size_t divider = size_t(_master_clock_rate/rate); +        switch(_dboard_slot){ +        case usrp1_impl::DBOARD_SLOT_A: +            _iface->poke32(FR_RX_A_REFCLK, (divider & 0x7f) | 0x80); +            break; + +        case usrp1_impl::DBOARD_SLOT_B: +            _iface->poke32(FR_RX_B_REFCLK, (divider & 0x7f) | 0x80); +            break; +        } +    } +} + +std::vector<double> usrp1_dboard_iface::get_clock_rates(unit_t unit) +{ +    std::vector<double> rates; +    if (unit == UNIT_RX && _rx_dboard_id == dbsrx_classic_id){ +        for (size_t div = 1; div <= 127; div++) +            rates.push_back(_master_clock_rate / div); +    } +    else{ +        rates.push_back(_master_clock_rate); +    } +    return rates; +} + +double usrp1_dboard_iface::get_clock_rate(unit_t unit) +{ +    return _clock_rates[unit]; +} + +void usrp1_dboard_iface::set_clock_enabled(unit_t, bool) +{ +    //TODO we can only enable for special case anyway... +} + +double usrp1_dboard_iface::get_codec_rate(unit_t){ +    return _master_clock_rate; +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) +{ +    switch(unit) { +    case UNIT_RX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +             _iface->poke32(FR_ATR_MASK_1, value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +             _iface->poke32(FR_ATR_MASK_3, value); +        break; +    case UNIT_TX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_ATR_MASK_0, value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_ATR_MASK_2, value); +        break; +    } +} + +void usrp1_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) +{ +    switch(unit) { +    case UNIT_RX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_OE_1, 0xffff0000 | value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_OE_3, 0xffff0000 | value); +        break; +    case UNIT_TX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_OE_0, 0xffff0000 | value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_OE_2, 0xffff0000 | value); +        break; +    } +} + +void usrp1_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) +{ +    switch(unit) { +    case UNIT_RX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_IO_1, 0xffff0000 | value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_IO_3, 0xffff0000 | value); +        break; +    case UNIT_TX: +        if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +            _iface->poke32(FR_IO_0, 0xffff0000 | value); +        else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +            _iface->poke32(FR_IO_2, 0xffff0000 | value); +        break; +    } +} + +void usrp1_dboard_iface::set_gpio_debug(unit_t, int) +{ +    /* NOP */ +} + +boost::uint16_t usrp1_dboard_iface::read_gpio(unit_t unit) +{ +    boost::uint32_t out_value; + +    if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +        out_value = _iface->peek32(1); +    else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +        out_value = _iface->peek32(2); +    else +        UHD_THROW_INVALID_CODE_PATH(); + +    switch(unit) { +    case UNIT_RX: +        return (boost::uint16_t)((out_value >> 16) & 0x0000ffff); +    case UNIT_TX: +        return (boost::uint16_t)((out_value >>  0) & 0x0000ffff); +    } +    UHD_ASSERT_THROW(false); +} + +void usrp1_dboard_iface::_set_atr_reg(unit_t unit, +                                     atr_reg_t atr, boost::uint16_t value) +{ +    // Ignore unsupported states +    if ((atr == ATR_REG_IDLE) || (atr == ATR_REG_TX_ONLY)) +        return; +    if(atr == ATR_REG_RX_ONLY) { +        switch(unit) { +        case UNIT_RX: +            if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +                _iface->poke32(FR_ATR_RXVAL_1, value); +            else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +                _iface->poke32(FR_ATR_RXVAL_3, value); +            break; +        case UNIT_TX: +            if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +                _iface->poke32(FR_ATR_RXVAL_0, value); +            else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +                _iface->poke32(FR_ATR_RXVAL_2, value); +            break; +        } +    } else if (atr == ATR_REG_FULL_DUPLEX) { +        switch(unit) { +        case UNIT_RX: +            if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +                _iface->poke32(FR_ATR_TXVAL_1, value); +            else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +                _iface->poke32(FR_ATR_TXVAL_3, value); +            break; +        case UNIT_TX: +            if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) +                _iface->poke32(FR_ATR_TXVAL_0, value); +            else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) +                _iface->poke32(FR_ATR_TXVAL_2, value); +            break; +        } +    } +} +/*********************************************************************** + * SPI + **********************************************************************/ +/*! + * Static function to convert a unit type to a spi slave device number. + * \param unit the dboard interface unit type enum + * \param slot the side (A or B) the dboard is attached + * \return the slave device number + */ +static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit, +                                           usrp1_impl::dboard_slot_t slot) +{ +    switch(unit) { +    case dboard_iface::UNIT_TX: +        if (slot == usrp1_impl::DBOARD_SLOT_A) +            return SPI_ENABLE_TX_A; +        else if (slot == usrp1_impl::DBOARD_SLOT_B) +            return SPI_ENABLE_TX_B; +        else +            break; +    case dboard_iface::UNIT_RX: +        if (slot == usrp1_impl::DBOARD_SLOT_A) +            return SPI_ENABLE_RX_A; +        else if (slot == usrp1_impl::DBOARD_SLOT_B) +            return SPI_ENABLE_RX_B; +        else +            break; +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +void usrp1_dboard_iface::write_spi(unit_t unit, +                                   const spi_config_t &config, +                                   boost::uint32_t data, +                                   size_t num_bits) +{ +    _iface->write_spi(unit_to_otw_spi_dev(unit, _dboard_slot), +                         config, data, num_bits); +} + +boost::uint32_t usrp1_dboard_iface::read_write_spi(unit_t unit, +                                                   const spi_config_t &config, +                                                   boost::uint32_t data, +                                                   size_t num_bits) +{ +    return _iface->read_spi(unit_to_otw_spi_dev(unit, _dboard_slot), +                                config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp1_dboard_iface::write_i2c(boost::uint8_t addr, +                                   const byte_vector_t &bytes) +{ +    return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp1_dboard_iface::read_i2c(boost::uint8_t addr, +                                           size_t num_bytes) +{ +    return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp1_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, usrp1_codec_ctrl::aux_dac_t> +        which_to_aux_dac = map_list_of +                                     (AUX_DAC_A, usrp1_codec_ctrl::AUX_DAC_A) +                                     (AUX_DAC_B, usrp1_codec_ctrl::AUX_DAC_B) +                                     (AUX_DAC_C, usrp1_codec_ctrl::AUX_DAC_C) +                                     (AUX_DAC_D, usrp1_codec_ctrl::AUX_DAC_D); + +    _codec->write_aux_dac(which_to_aux_dac[which], value); +} + +double usrp1_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, usrp1_codec_ctrl::aux_adc_t> > +        unit_to_which_to_aux_adc = map_list_of(UNIT_RX, map_list_of +                                    (AUX_ADC_A, usrp1_codec_ctrl::AUX_ADC_A1) +                                    (AUX_ADC_B, usrp1_codec_ctrl::AUX_ADC_B1)) +                                              (UNIT_TX, map_list_of +                                    (AUX_ADC_A, usrp1_codec_ctrl::AUX_ADC_A2) +                                    (AUX_ADC_B, usrp1_codec_ctrl::AUX_ADC_B2)); + +    return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); +} diff --git a/host/lib/usrp/usrp1/io_impl.cpp b/host/lib/usrp/usrp1/io_impl.cpp new file mode 100644 index 000000000..e81b00d1c --- /dev/null +++ b/host/lib/usrp/usrp1/io_impl.cpp @@ -0,0 +1,508 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "validate_subdev_spec.hpp" +#define SRPH_DONT_CHECK_SEQUENCE +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "usrp1_calc_mux.hpp" +#include "fpga_regs_standard.h" +#include "usrp_commands.h" +#include "usrp1_impl.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/math/special_functions/sign.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/thread/thread.hpp> +#include <boost/bind.hpp> +#include <boost/format.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const size_t alignment_padding = 512; + +/*********************************************************************** + * Helper struct to associate an offset with a buffer + **********************************************************************/ +struct offset_send_buffer{ +    offset_send_buffer(void){ +        /* NOP */ +    } + +    offset_send_buffer(managed_send_buffer::sptr buff, size_t offset = 0): +        buff(buff), offset(offset) +    { +        /* NOP */ +    } + +    //member variables +    managed_send_buffer::sptr buff; +    size_t offset; /* in bytes */ +}; + +/*********************************************************************** + * Reusable managed send buffer to handle aligned commits + **********************************************************************/ +class offset_managed_send_buffer : public managed_send_buffer{ +public: +    typedef boost::function<void(offset_send_buffer&, offset_send_buffer&, size_t)> commit_cb_type; +    offset_managed_send_buffer(const commit_cb_type &commit_cb): +        _commit_cb(commit_cb) +    { +        /* NOP */ +    } + +    void commit(size_t size){ +        if (size != 0) this->_commit_cb(_curr_buff, _next_buff, size); +    } + +    sptr get_new( +        offset_send_buffer &curr_buff, +        offset_send_buffer &next_buff +    ){ +        _curr_buff = curr_buff; +        _next_buff = next_buff; +        return make_managed_buffer(this); +    } + +private: +    void  *get_buff(void) const{return _curr_buff.buff->cast<char *>() + _curr_buff.offset;} +    size_t get_size(void) const{return _curr_buff.buff->size()         - _curr_buff.offset;} + +    offset_send_buffer _curr_buff, _next_buff; +    commit_cb_type _commit_cb; +}; + +/*********************************************************************** + * BS VRT packer/unpacker functions (since samples don't have headers) + **********************************************************************/ +static void usrp1_bs_vrt_packer( +    boost::uint32_t *, +    vrt::if_packet_info_t &if_packet_info +){ +    if_packet_info.num_header_words32 = 0; +    if_packet_info.num_packet_words32 = if_packet_info.num_payload_words32; +} + +static void usrp1_bs_vrt_unpacker( +    const boost::uint32_t *, +    vrt::if_packet_info_t &if_packet_info +){ +    if_packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_DATA; +    if_packet_info.num_payload_words32 = if_packet_info.num_packet_words32; +    if_packet_info.num_header_words32 = 0; +    if_packet_info.packet_count = 0; +    if_packet_info.sob = false; +    if_packet_info.eob = false; +    if_packet_info.has_sid = false; +    if_packet_info.has_cid = false; +    if_packet_info.has_tsi = false; +    if_packet_info.has_tsf = false; +    if_packet_info.has_tlr = false; +} + +/*********************************************************************** + * IO Implementation Details + **********************************************************************/ +struct usrp1_impl::io_impl{ +    io_impl(zero_copy_if::sptr data_transport): +        data_transport(data_transport), +        curr_buff(offset_send_buffer(data_transport->get_send_buff())), +        omsb(boost::bind(&usrp1_impl::io_impl::commit_send_buff, this, _1, _2, _3)) +    { +        /* NOP */ +    } + +    ~io_impl(void){ +        UHD_SAFE_CALL(flush_send_buff();) +    } + +    zero_copy_if::sptr data_transport; + +    //state management for the vrt packet handler code +    sph::recv_packet_handler recv_handler; +    sph::send_packet_handler send_handler; + +    //wrapper around the actual send buffer interface +    //all of this to ensure only aligned lengths are committed +    //NOTE: you must commit before getting a new buffer +    //since the vrt packet handler obeys this, we are ok +    offset_send_buffer curr_buff; +    offset_managed_send_buffer omsb; +    void commit_send_buff(offset_send_buffer&, offset_send_buffer&, size_t); +    void flush_send_buff(void); +    managed_send_buffer::sptr get_send_buff(double timeout){ +        //try to get a new managed buffer with timeout +        offset_send_buffer next_buff(data_transport->get_send_buff(timeout)); +        if (not next_buff.buff.get()) return managed_send_buffer::sptr(); /* propagate timeout here */ + +        //make a new managed buffer with the offset buffs +        return omsb.get_new(curr_buff, next_buff); +    } + +    task::sptr vandal_task; +    boost::system_time last_send_time; +}; + +/*! + * Perform an actual commit on the send buffer: + * Copy the remainder of alignment to the next buffer. + * Commit the current buffer at multiples of alignment. + */ +void usrp1_impl::io_impl::commit_send_buff( +    offset_send_buffer &curr, +    offset_send_buffer &next, +    size_t num_bytes +){ +    //total number of bytes now in the current buffer +    size_t bytes_in_curr_buffer = curr.offset + num_bytes; + +    //calculate how many to commit and remainder +    size_t num_bytes_remaining = bytes_in_curr_buffer % alignment_padding; +    size_t num_bytes_to_commit = bytes_in_curr_buffer - num_bytes_remaining; + +    //copy the remainder into the next buffer +    std::memcpy( +        next.buff->cast<char *>() + next.offset, +        curr.buff->cast<char *>() + num_bytes_to_commit, +        num_bytes_remaining +    ); + +    //update the offset into the next buffer +    next.offset += num_bytes_remaining; + +    //commit the current buffer +    curr.buff->commit(num_bytes_to_commit); + +    //store the next buffer for the next call +    curr_buff = next; +} + +/*! + * Flush the current buffer by padding out to alignment and committing. + */ +void usrp1_impl::io_impl::flush_send_buff(void){ +    //calculate the number of bytes to alignment +    size_t bytes_to_pad = (-1*curr_buff.offset)%alignment_padding; + +    //send at least alignment_padding to guarantee zeros are sent +    if (bytes_to_pad == 0) bytes_to_pad = alignment_padding; + +    //get the buffer, clear, and commit (really current buffer) +    managed_send_buffer::sptr buff = this->get_send_buff(.1); +    if (buff.get() != NULL){ +        std::memset(buff->cast<void *>(), 0, bytes_to_pad); +        buff->commit(bytes_to_pad); +    } +} + +/*********************************************************************** + * Initialize internals within this file + **********************************************************************/ +void usrp1_impl::io_init(void){ +    _rx_otw_type.width = 16; +    _rx_otw_type.shift = 0; +    _rx_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + +    _tx_otw_type.width = 16; +    _tx_otw_type.shift = 0; +    _tx_otw_type.byteorder = otw_type_t::BO_LITTLE_ENDIAN; + +    _io_impl = UHD_PIMPL_MAKE(io_impl, (_data_transport)); + +    //create a new vandal thread to poll xerflow conditions +    _io_impl->vandal_task = task::make(boost::bind( +        &usrp1_impl::vandal_conquest_loop, this +    )); + +    //init some handler stuff +    _io_impl->recv_handler.set_tick_rate(_master_clock_rate); +    _io_impl->recv_handler.set_vrt_unpacker(&usrp1_bs_vrt_unpacker); +    _io_impl->recv_handler.set_xport_chan_get_buff(0, boost::bind( +        &uhd::transport::zero_copy_if::get_recv_buff, _io_impl->data_transport, _1 +    )); +    _io_impl->send_handler.set_tick_rate(_master_clock_rate); +    _io_impl->send_handler.set_vrt_packer(&usrp1_bs_vrt_packer); +    _io_impl->send_handler.set_xport_chan_get_buff(0, boost::bind( +        &usrp1_impl::io_impl::get_send_buff, _io_impl.get(), _1 +    )); + +    //init as disabled, then call the real function (uses restore) +    this->enable_rx(false); +    this->enable_tx(false); +    rx_stream_on_off(false); +    tx_stream_on_off(false); +    _io_impl->flush_send_buff(); +} + +void usrp1_impl::rx_stream_on_off(bool enb){ +    this->restore_rx(enb); +    //drain any junk in the receive transport after stop streaming command +    while(not enb and _data_transport->get_recv_buff().get() != NULL){ +        /* NOP */ +    } +} + +void usrp1_impl::tx_stream_on_off(bool enb){ +    _io_impl->last_send_time = boost::get_system_time(); +    if (_tx_enabled and not enb) _io_impl->flush_send_buff(); +    this->restore_tx(enb); +} + +/*! + * Casually poll the overflow and underflow registers. + * On an underflow, push an async message into the queue and print. + * On an overflow, interleave an inline message into recv and print. + * This procedure creates "soft" inline and async user messages. + */ +void usrp1_impl::vandal_conquest_loop(void){ + +    //initialize the async metadata +    async_metadata_t async_metadata; +    async_metadata.channel = 0; +    async_metadata.has_time_spec = true; +    async_metadata.event_code = async_metadata_t::EVENT_CODE_UNDERFLOW; + +    //initialize the inline metadata +    rx_metadata_t inline_metadata; +    inline_metadata.has_time_spec = true; +    inline_metadata.error_code = rx_metadata_t::ERROR_CODE_OVERFLOW; + +    //start the polling loop... +    try{ while (not boost::this_thread::interruption_requested()){ +        boost::uint8_t underflow = 0, overflow = 0; + +        //shutoff transmit if it has been too long since send() was called +        if (_tx_enabled and (boost::get_system_time() - _io_impl->last_send_time) > boost::posix_time::milliseconds(100)){ +            this->tx_stream_on_off(false); +        } + +        //always poll regardless of enabled so we can clear the conditions +        _fx2_ctrl->usrp_control_read( +            VRQ_GET_STATUS, 0, GS_TX_UNDERRUN, &underflow, sizeof(underflow) +        ); +        _fx2_ctrl->usrp_control_read( +            VRQ_GET_STATUS, 0, GS_RX_OVERRUN, &overflow, sizeof(overflow) +        ); + +        //handle message generation for xerflow conditions +        if (_tx_enabled and underflow){ +            async_metadata.time_spec = _soft_time_ctrl->get_time(); +            _soft_time_ctrl->get_async_queue().push_with_pop_on_full(async_metadata); +            UHD_MSG(fastpath) << "U"; +        } +        if (_rx_enabled and overflow){ +            inline_metadata.time_spec = _soft_time_ctrl->get_time(); +            _soft_time_ctrl->get_inline_queue().push_with_pop_on_full(inline_metadata); +            UHD_MSG(fastpath) << "O"; +        } + +        boost::this_thread::sleep(boost::posix_time::milliseconds(50)); +    }} +    catch(const boost::thread_interrupted &){} //normal exit condition +    catch(const std::exception &e){ +        UHD_MSG(error) << "The vandal caught an unexpected exception " << e.what() << std::endl; +    } +} + +/*********************************************************************** + * Properties callback methods below + **********************************************************************/ +void usrp1_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ +    boost::mutex::scoped_lock lock = _io_impl->recv_handler.get_scoped_lock(); + +    //sanity checking +    validate_subdev_spec(_tree, spec, "rx"); + +    _rx_subdev_spec = spec; //shadow +    _io_impl->recv_handler.resize(spec.size()); +    _io_impl->recv_handler.set_converter(_rx_otw_type, spec.size()); + +    //set the mux and set the number of rx channels +    std::vector<mapping_pair_t> mapping; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, spec){ +        const std::string conn = _tree->access<std::string>(str(boost::format( +            "/mboards/0/dboards/%s/rx_frontends/%s/connection" +        ) % pair.db_name % pair.sd_name)).get(); +        mapping.push_back(std::make_pair(pair.db_name, conn)); +    } +    bool s = this->disable_rx(); +    _iface->poke32(FR_RX_MUX, calc_rx_mux(mapping)); +    this->restore_rx(s); +} + +void usrp1_impl::update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec){ +    boost::mutex::scoped_lock lock = _io_impl->send_handler.get_scoped_lock(); + +    //sanity checking +    validate_subdev_spec(_tree, spec, "tx"); + +    _tx_subdev_spec = spec; //shadow +    _io_impl->send_handler.resize(spec.size()); +    _io_impl->send_handler.set_converter(_tx_otw_type, spec.size()); + +    //set the mux and set the number of tx channels +    std::vector<mapping_pair_t> mapping; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, spec){ +        const std::string conn = _tree->access<std::string>(str(boost::format( +            "/mboards/0/dboards/%s/tx_frontends/%s/connection" +        ) % pair.db_name % pair.sd_name)).get(); +        mapping.push_back(std::make_pair(pair.db_name, conn)); +    } +    bool s = this->disable_tx(); +    _iface->poke32(FR_TX_MUX, calc_tx_mux(mapping)); +    this->restore_tx(s); + +    //if the spec changes size, so does the max samples per packet... +    _io_impl->send_handler.set_max_samples_per_packet(get_max_send_samps_per_packet()); +} + +double usrp1_impl::update_rx_samp_rate(const double samp_rate){ +    boost::mutex::scoped_lock lock = _io_impl->recv_handler.get_scoped_lock(); + +    const size_t rate = uhd::clip<size_t>( +        boost::math::iround(_master_clock_rate / samp_rate), size_t(std::ceil(_master_clock_rate / 8e6)), 256 +    ); + +    bool s = this->disable_rx(); +    _iface->poke32(FR_DECIM_RATE, rate/2 - 1); +    this->restore_rx(s); + +    _io_impl->recv_handler.set_samp_rate(_master_clock_rate / rate); +    return _master_clock_rate / rate; +} + +double usrp1_impl::update_tx_samp_rate(const double samp_rate){ +    boost::mutex::scoped_lock lock = _io_impl->send_handler.get_scoped_lock(); + +    const size_t rate = uhd::clip<size_t>( +        boost::math::iround(_master_clock_rate / samp_rate), size_t(std::ceil(_master_clock_rate / 8e6)), 256 +    ); + +    bool s = this->disable_tx(); +    _iface->poke32(FR_INTERP_RATE, rate/2 - 1); +    this->restore_tx(s); + +    _io_impl->send_handler.set_samp_rate(_master_clock_rate / rate); +    return _master_clock_rate / rate; +} + +double usrp1_impl::update_rx_dsp_freq(const size_t dspno, const double freq_){ + +    //correct for outside of rate (wrap around) +    double freq = std::fmod(freq_, _master_clock_rate); +    if (std::abs(freq) > _master_clock_rate/2.0) +        freq -= boost::math::sign(freq)*_master_clock_rate; + +    //calculate the freq register word (signed) +    UHD_ASSERT_THROW(std::abs(freq) <= _master_clock_rate/2.0); +    static const double scale_factor = std::pow(2.0, 32); +    const boost::int32_t freq_word = boost::int32_t(boost::math::round((freq / _master_clock_rate) * scale_factor)); + +    static const boost::uint32_t dsp_index_to_reg_val[4] = { +        FR_RX_FREQ_0, FR_RX_FREQ_1, FR_RX_FREQ_2, FR_RX_FREQ_3 +    }; +    _iface->poke32(dsp_index_to_reg_val[dspno], ~freq_word + 1); + +    return (double(freq_word) / scale_factor) * _master_clock_rate; +} + +double usrp1_impl::update_tx_dsp_freq(const size_t dspno, const double freq){ +    //map the freq shift key to a subdev spec to a particular codec chip +    _dbc[_tx_subdev_spec.at(dspno).db_name].codec->set_duc_freq(freq, _master_clock_rate); +    return freq; //assume infinite precision +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool usrp1_impl::recv_async_msg( +    async_metadata_t &async_metadata, double timeout +){ +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return _soft_time_ctrl->get_async_queue().pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Data send + helper functions + **********************************************************************/ +size_t usrp1_impl::get_max_send_samps_per_packet(void) const { +    return (_data_transport->get_send_frame_size() - alignment_padding) +        / _tx_otw_type.get_sample_size() +        / _tx_subdev_spec.size() +    ; +} + +size_t usrp1_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 +){ +    if (_soft_time_ctrl->send_pre(metadata, timeout)) return 0; + +    this->tx_stream_on_off(true); //always enable (it will do the right thing) +    size_t num_samps_sent = _io_impl->send_handler.send( +        buffs, nsamps_per_buff, +        metadata, io_type, +        send_mode, timeout +    ); + +    //handle eob flag (commit the buffer, /*disable the DACs*/) +    //check num samps sent to avoid flush on incomplete/timeout +    if (metadata.end_of_burst and num_samps_sent == nsamps_per_buff){ +        async_metadata_t metadata; +        metadata.channel = 0; +        metadata.has_time_spec = true; +        metadata.time_spec = _soft_time_ctrl->get_time(); +        metadata.event_code = async_metadata_t::EVENT_CODE_BURST_ACK; +        _soft_time_ctrl->get_async_queue().push_with_pop_on_full(metadata); +        this->tx_stream_on_off(false); +    } + +    return num_samps_sent; +} + +/*********************************************************************** + * Data recv + helper functions + **********************************************************************/ +size_t usrp1_impl::get_max_recv_samps_per_packet(void) const { +    return _data_transport->get_recv_frame_size() +        / _rx_otw_type.get_sample_size() +        / _rx_subdev_spec.size() +    ; +} + +size_t usrp1_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 +){ +    //interleave a "soft" inline message into the receive stream: +    if (_soft_time_ctrl->get_inline_queue().pop_with_haste(metadata)) return 0; + +    size_t num_samps_recvd = _io_impl->recv_handler.recv( +        buffs, nsamps_per_buff, +        metadata, io_type, +        recv_mode, timeout +    ); + +    return _soft_time_ctrl->recv_post(metadata, num_samps_recvd); +} diff --git a/host/lib/usrp/usrp1/soft_time_ctrl.cpp b/host/lib/usrp/usrp1/soft_time_ctrl.cpp new file mode 100644 index 000000000..78481c3ff --- /dev/null +++ b/host/lib/usrp/usrp1/soft_time_ctrl.cpp @@ -0,0 +1,228 @@ +// +// 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 "soft_time_ctrl.hpp" +#include <uhd/utils/tasks.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/condition_variable.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace pt = boost::posix_time; + +static const time_spec_t TWIDDLE(0.0011); + +/*********************************************************************** + * Soft time control implementation + **********************************************************************/ +class soft_time_ctrl_impl : public soft_time_ctrl{ +public: + +    soft_time_ctrl_impl(const cb_fcn_type &stream_on_off): +        _nsamps_remaining(0), +        _stream_mode(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS), +        _cmd_queue(2), +        _async_msg_queue(100), +        _inline_msg_queue(100), +        _stream_on_off(stream_on_off) +    { +        //synchronously spawn a new thread +        _recv_cmd_task = task::make(boost::bind(&soft_time_ctrl_impl::recv_cmd_task, this)); + +        //initialize the time to something +        this->set_time(time_spec_t(0.0)); +    } + +    /******************************************************************* +     * Time control +     ******************************************************************/ +    void set_time(const time_spec_t &time){ +        boost::mutex::scoped_lock lock(_update_mutex); +        _time_offset = time_spec_t::get_system_time() - time; +    } + +    time_spec_t get_time(void){ +        boost::mutex::scoped_lock lock(_update_mutex); +        return time_now(); +    } + +    UHD_INLINE time_spec_t time_now(void){ +        //internal get time without scoped lock +        return time_spec_t::get_system_time() - _time_offset; +    } + +    UHD_INLINE void sleep_until_time( +        boost::mutex::scoped_lock &lock, const time_spec_t &time +    ){ +        boost::condition_variable cond; +        //use a condition variable to unlock, sleep, lock +        double seconds_to_sleep = (time - time_now()).get_real_secs(); +        cond.timed_wait(lock, pt::microseconds(long(seconds_to_sleep*1e6))); +    } + +    /******************************************************************* +     * Receive control +     ******************************************************************/ +    size_t recv_post(rx_metadata_t &md, const size_t nsamps){ +        boost::mutex::scoped_lock lock(_update_mutex); + +        //Since it timed out on the receive, check for inline messages... +        //Must do a post check because recv() will not wake up for a message. +        if (md.error_code == rx_metadata_t::ERROR_CODE_TIMEOUT){ +            if (_inline_msg_queue.pop_with_haste(md)) return 0; +        } + +        //load the metadata with the expected time +        md.has_time_spec = true; +        md.time_spec = time_now(); + +        //none of the stuff below matters in continuous streaming mode +        if (_stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS) return nsamps; + +        //When to stop streaming: +        //The samples have been received and the stream mode is non-continuous. +        //Rewrite the sample count to clip to the requested number of samples. +        if (_nsamps_remaining <= nsamps) switch(_stream_mode){ +        case stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE:{ +            rx_metadata_t metadata; +            metadata.has_time_spec = true; +            metadata.time_spec = this->time_now(); +            metadata.error_code = rx_metadata_t::ERROR_CODE_BROKEN_CHAIN; +            _inline_msg_queue.push_with_pop_on_full(metadata); +        } //continue to next case... +        case stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE: +            md.end_of_burst = true; +            this->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); +            return _nsamps_remaining; +        default: break; +        } + +        //update the consumed samples +        _nsamps_remaining -= nsamps; +        return nsamps; +    } + +    void issue_stream_cmd(const stream_cmd_t &cmd){ +        _cmd_queue.push_with_wait(boost::make_shared<stream_cmd_t>(cmd)); +    } + +    void stream_on_off(bool enb){ +        _stream_on_off(enb); +        _nsamps_remaining = 0; +    } + +    /******************************************************************* +     * Transmit control +     ******************************************************************/ +    bool send_pre(const tx_metadata_t &md, double &timeout){ +        if (not md.has_time_spec) return false; + +        boost::mutex::scoped_lock lock(_update_mutex); + +        time_spec_t time_at(md.time_spec - TWIDDLE); + +        //handle late packets +        if (time_at < time_now()){ +            async_metadata_t metadata; +            metadata.channel = 0; +            metadata.has_time_spec = true; +            metadata.time_spec = this->time_now(); +            metadata.event_code = async_metadata_t::EVENT_CODE_TIME_ERROR; +            _async_msg_queue.push_with_pop_on_full(metadata); +            return true; +        } + +        timeout -= (time_at - time_now()).get_real_secs(); +        sleep_until_time(lock, time_at); +        return false; +    } + +    /******************************************************************* +     * Thread control +     ******************************************************************/ +    void recv_cmd_handle_cmd(const stream_cmd_t &cmd){ +        boost::mutex::scoped_lock lock(_update_mutex); + +        //handle the stream at time by sleeping +        if (not cmd.stream_now){ +            time_spec_t time_at(cmd.time_spec - TWIDDLE); +            if (time_at < time_now()){ +                rx_metadata_t metadata; +                metadata.has_time_spec = true; +                metadata.time_spec = this->time_now(); +                metadata.error_code = rx_metadata_t::ERROR_CODE_LATE_COMMAND; +                _inline_msg_queue.push_with_pop_on_full(metadata); +                this->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); +                return; +            } +            else{ +                sleep_until_time(lock, time_at); +            } +        } + +        //When to stop streaming: +        //Stop streaming when the command is a stop and streaming. +        if (cmd.stream_mode == stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS +           and _stream_mode != stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS +        ) stream_on_off(false); + +        //When to start streaming: +        //Start streaming when the command is not a stop and not streaming. +        if (cmd.stream_mode != stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS +           and _stream_mode == stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS +        ) stream_on_off(true); + +        //update the state +        _nsamps_remaining += cmd.num_samps; +        _stream_mode = cmd.stream_mode; +    } + +    void recv_cmd_task(void){ //task is looped +        boost::shared_ptr<stream_cmd_t> cmd; +        _cmd_queue.pop_with_wait(cmd); +        recv_cmd_handle_cmd(*cmd); +    } + +    bounded_buffer<async_metadata_t> &get_async_queue(void){ +        return _async_msg_queue; +    } + +    bounded_buffer<rx_metadata_t> &get_inline_queue(void){ +        return _inline_msg_queue; +    } + +private: +    boost::mutex _update_mutex; +    size_t _nsamps_remaining; +    stream_cmd_t::stream_mode_t _stream_mode; +    time_spec_t _time_offset; +    bounded_buffer<boost::shared_ptr<stream_cmd_t> > _cmd_queue; +    bounded_buffer<async_metadata_t> _async_msg_queue; +    bounded_buffer<rx_metadata_t> _inline_msg_queue; +    const cb_fcn_type _stream_on_off; +    task::sptr _recv_cmd_task; +}; + +/*********************************************************************** + * Soft time control factor + **********************************************************************/ +soft_time_ctrl::sptr soft_time_ctrl::make(const cb_fcn_type &stream_on_off){ +    return sptr(new soft_time_ctrl_impl(stream_on_off)); +} diff --git a/host/lib/usrp/usrp1/soft_time_ctrl.hpp b/host/lib/usrp/usrp1/soft_time_ctrl.hpp new file mode 100644 index 000000000..e91aaf6a2 --- /dev/null +++ b/host/lib/usrp/usrp1/soft_time_ctrl.hpp @@ -0,0 +1,74 @@ +// +// Copyright 2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USRP1_SOFT_TIME_CTRL_HPP +#define INCLUDED_LIBUHD_USRP_USRP1_SOFT_TIME_CTRL_HPP + +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> + +namespace uhd{ namespace usrp{ + +/*! + * The soft time control emulates some of the + * advanced streaming capabilities of the later USRP models. + * Soft time control uses the system time to emulate + * timed transmits, timed receive commands, device time, + * and inline and async error messages. + */ +class soft_time_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<soft_time_ctrl> sptr; +    typedef boost::function<void(bool)> cb_fcn_type; + +    /*! +     * Make a new soft time control. +     * \param stream_on_off a function to enable/disable rx +     * \return a new soft time control object +     */ +    static sptr make(const cb_fcn_type &stream_on_off); + +    //! Set the current time +    virtual void set_time(const time_spec_t &time) = 0; + +    //! Get the current time +    virtual time_spec_t get_time(void) = 0; + +    //! Call after the internal recv function +    virtual size_t recv_post(rx_metadata_t &md, const size_t nsamps) = 0; + +    //! Call before the internal send function +    virtual bool send_pre(const tx_metadata_t &md, double &timeout) = 0; + +    //! Issue a stream command to receive +    virtual void issue_stream_cmd(const stream_cmd_t &cmd) = 0; + +    //! Get access to a buffer of async metadata +    virtual transport::bounded_buffer<async_metadata_t> &get_async_queue(void) = 0; + +    //! Get access to a buffer of inline metadata +    virtual transport::bounded_buffer<rx_metadata_t> &get_inline_queue(void) = 0; +}; + +}} //namespace + +#endif /* INCLUDED_LIBUHD_USRP_USRP1_SOFT_TIME_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_calc_mux.hpp b/host/lib/usrp/usrp1/usrp1_calc_mux.hpp new file mode 100644 index 000000000..31c190db0 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_calc_mux.hpp @@ -0,0 +1,156 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <utility> +#include <vector> +#include <string> + +#ifndef INCLUDED_USRP1_CALC_MUX_HPP +#define INCLUDED_USRP1_CALC_MUX_HPP + +//db_name, conn_type for the mux calculations below... +typedef std::pair<std::string, std::string> mapping_pair_t; + +/*********************************************************************** + * Calculate the RX mux value: + *    The I and Q mux values are intentionally reversed to flip I and Q + *    to account for the reversal in the type conversion routines. + **********************************************************************/ +static int calc_rx_mux_pair(int adc_for_i, int adc_for_q){ +    return (adc_for_i << 2) | (adc_for_q << 0); //shift reversal here +} + +/*! + *    3                   2                   1                   0 + *  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 + * +-----------------------+-------+-------+-------+-------+-+-----+ + * |      must be zero     | Q3| I3| Q2| I2| Q1| I1| Q0| I0|Z| NCH | + * +-----------------------+-------+-------+-------+-------+-+-----+ + */ +static boost::uint32_t calc_rx_mux(const std::vector<mapping_pair_t> &mapping){ +    //create look-up-table for mapping dboard name and connection type to ADC flags +    static const int ADC0 = 0, ADC1 = 1, ADC2 = 2, ADC3 = 3; +    static const uhd::dict<std::string, uhd::dict<std::string, int> > name_to_conn_to_flag = boost::assign::map_list_of +        ("A", boost::assign::map_list_of +            ("IQ", calc_rx_mux_pair(ADC0, ADC1)) //I and Q +            ("QI", calc_rx_mux_pair(ADC1, ADC0)) //I and Q +            ("I",  calc_rx_mux_pair(ADC0, ADC0)) //I and Q (Q identical but ignored Z=1) +            ("Q",  calc_rx_mux_pair(ADC1, ADC1)) //I and Q (Q identical but ignored Z=1) +        ) +        ("B", boost::assign::map_list_of +            ("IQ", calc_rx_mux_pair(ADC2, ADC3)) //I and Q +            ("QI", calc_rx_mux_pair(ADC3, ADC2)) //I and Q +            ("I",  calc_rx_mux_pair(ADC2, ADC2)) //I and Q (Q identical but ignored Z=1) +            ("Q",  calc_rx_mux_pair(ADC3, ADC3)) //I and Q (Q identical but ignored Z=1) +        ) +    ; + +    //extract the number of channels +    const size_t nchan = mapping.size(); + +    //calculate the channel flags +    int channel_flags = 0; +    size_t num_reals = 0, num_quads = 0; +    BOOST_FOREACH(const mapping_pair_t &pair, uhd::reversed(mapping)){ +        const std::string name = pair.first, conn = pair.second; +        if (conn == "IQ" or conn == "QI") num_quads++; +        if (conn == "I" or conn == "Q") num_reals++; +        channel_flags = (channel_flags << 4) | name_to_conn_to_flag[name][conn]; +    } + +    //calculate Z: +    //    for all real sources: Z = 1 +    //    for all quadrature sources: Z = 0 +    //    for mixed sources: warning + Z = 0 +    int Z = (num_quads > 0)? 0 : 1; +    if (num_quads != 0 and num_reals != 0) UHD_MSG(warning) << boost::format( +        "Mixing real and quadrature rx subdevices is not supported.\n" +        "The Q input to the real source(s) will be non-zero.\n" +    ); + +    //calculate the rx mux value +    return ((channel_flags & 0xffff) << 4) | ((Z & 0x1) << 3) | ((nchan & 0x7) << 0); +} + +/*********************************************************************** + * Calculate the TX mux value: + *    The I and Q mux values are intentionally reversed to flip I and Q + *    to account for the reversal in the type conversion routines. + **********************************************************************/ +static int calc_tx_mux_pair(int chn_for_i, int chn_for_q){ +    return (chn_for_i << 4) | (chn_for_q << 0); //shift reversal here +} + +/*! + *    3                   2                   1                   0 + *  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 + * +-----------------------+-------+-------+-------+-------+-+-----+ + * |                       | DAC1Q | DAC1I | DAC0Q | DAC0I |0| NCH | + * +-----------------------------------------------+-------+-+-----+ + */ +static boost::uint32_t calc_tx_mux(const std::vector<mapping_pair_t> &mapping){ +    //create look-up-table for mapping channel number and connection type to flags +    static const int ENB = 1 << 3, CHAN_I0 = 0, CHAN_Q0 = 1, CHAN_I1 = 2, CHAN_Q1 = 3; +    static const uhd::dict<size_t, uhd::dict<std::string, int> > chan_to_conn_to_flag = boost::assign::map_list_of +        (0, boost::assign::map_list_of +            ("IQ", calc_tx_mux_pair(CHAN_I0 | ENB, CHAN_Q0 | ENB)) +            ("QI", calc_tx_mux_pair(CHAN_Q0 | ENB, CHAN_I0 | ENB)) +            ("I",  calc_tx_mux_pair(CHAN_I0 | ENB, 0            )) +            ("Q",  calc_tx_mux_pair(0,             CHAN_I0 | ENB)) +        ) +        (1, boost::assign::map_list_of +            ("IQ", calc_tx_mux_pair(CHAN_I1 | ENB, CHAN_Q1 | ENB)) +            ("QI", calc_tx_mux_pair(CHAN_Q1 | ENB, CHAN_I1 | ENB)) +            ("I",  calc_tx_mux_pair(CHAN_I1 | ENB, 0            )) +            ("Q",  calc_tx_mux_pair(0,             CHAN_I1 | ENB)) +        ) +    ; + +    //extract the number of channels +    size_t nchan = mapping.size(); + +    //calculate the channel flags +    int channel_flags = 0, chan = 0; +    uhd::dict<std::string, int> slot_to_chan_count = boost::assign::map_list_of("A", 0)("B", 0); +    BOOST_FOREACH(const mapping_pair_t &pair, mapping){ +        const std::string name = pair.first, conn = pair.second; + +        //combine the channel flags: shift for slot A vs B +        if (name == "A") channel_flags |= chan_to_conn_to_flag[chan][conn] << 0; +        if (name == "B") channel_flags |= chan_to_conn_to_flag[chan][conn] << 8; + +        //sanity check, only 1 channel per slot +        slot_to_chan_count[name]++; +        if (slot_to_chan_count[name] > 1) throw uhd::value_error( +            "cannot assign dboard slot to multiple channels: " + name +        ); + +        //increment for the next channel +        chan++; +    } + +    //calculate the tx mux value +    return ((channel_flags & 0xffff) << 4) | ((nchan & 0x7) << 0); +} + +#endif /* INCLUDED_USRP1_CALC_MUX_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_iface.cpp b/host/lib/usrp/usrp1/usrp1_iface.cpp new file mode 100644 index 000000000..c790aecb4 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_iface.cpp @@ -0,0 +1,205 @@ +// +// 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 "usrp1_iface.hpp" +#include "usrp_commands.h" +#include <uhd/utils/log.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <boost/format.hpp> +#include <stdexcept> +#include <iomanip> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +class usrp1_iface_impl : public usrp1_iface{ +public: +    /******************************************************************* +     * Structors +     ******************************************************************/ +    usrp1_iface_impl(uhd::usrp::fx2_ctrl::sptr ctrl_transport) +    { +        _ctrl_transport = ctrl_transport; +    } + +    ~usrp1_iface_impl(void) +    { +        /* NOP */ +    } + +    /******************************************************************* +     * Peek and Poke +     ******************************************************************/ +    void poke32(boost::uint32_t addr, boost::uint32_t value) +    { +        boost::uint32_t swapped = uhd::htonx(value); + +        UHD_LOGV(always) +            << "poke32(" +            << std::dec << std::setw(2) << addr << ", 0x" +            << std::hex << std::setw(8) << value << ")" << std::endl +        ; + +        boost::uint8_t w_index_h = SPI_ENABLE_FPGA & 0xff; +        boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff; + +        int ret =_ctrl_transport->usrp_control_write( +                                          VRQ_SPI_WRITE, +                                          addr & 0x7f, +                                          (w_index_h << 8) | (w_index_l << 0), +                                          (unsigned char*) &swapped, +                                          sizeof(boost::uint32_t)); + +        if (ret < 0) throw uhd::io_error("USRP1: failed control write"); +    } + +    boost::uint32_t peek32(boost::uint32_t addr) +    { +        UHD_LOGV(always) +            << "peek32(" +            << std::dec << std::setw(2) << addr << ")" << std::endl +        ; + +        boost::uint32_t value_out; + +        boost::uint8_t w_index_h = SPI_ENABLE_FPGA & 0xff; +        boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_1) & 0xff; + +        int ret = _ctrl_transport->usrp_control_read( +                                          VRQ_SPI_READ, +                                          0x80 | (addr & 0x7f), +                                          (w_index_h << 8) | (w_index_l << 0), +                                          (unsigned char*) &value_out, +                                          sizeof(boost::uint32_t)); + +        if (ret < 0) throw uhd::io_error("USRP1: failed control read"); + +        return uhd::ntohx(value_out); +    } +     +    void poke16(boost::uint32_t, boost::uint16_t) { +        throw uhd::not_implemented_error("Unhandled command poke16()"); +    } +     +    boost::uint16_t peek16(boost::uint32_t) { +        throw uhd::not_implemented_error("Unhandled command peek16()"); +        return 0; +    } + +    /******************************************************************* +     * I2C +     ******************************************************************/ +    void write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +        return _ctrl_transport->write_i2c(addr, bytes); +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ +        return _ctrl_transport->read_i2c(addr, num_bytes); +    } + +    /******************************************************************* +     * SPI +     * +     * For non-readback transactions use the SPI_WRITE command, which is +     * simpler and uses the USB control buffer for OUT data. No data +     * needs to be returned. +     * +     * For readback transactions use SPI_TRANSACT, which places up to +     * 4 bytes of OUT data in the device request fields and uses the +     * control buffer for IN data. +     ******************************************************************/ +    boost::uint32_t transact_spi(int which_slave, +                                 const spi_config_t &, +                                 boost::uint32_t bits, +                                 size_t num_bits, +                                 bool readback) +    { +        UHD_LOGV(always) +            << "transact_spi: " << std::endl +            << "  slave: " << which_slave << std::endl +            << "  bits: " << bits << std::endl +            << "  num_bits: " << num_bits << std::endl +            << "  readback: " << readback << std::endl +        ; +        UHD_ASSERT_THROW((num_bits <= 32) && !(num_bits % 8)); +        size_t num_bytes = num_bits / 8; + +        if (readback) { +            unsigned char buff[4] = { +                (bits >> 0) & 0xff, (bits >> 8) & 0xff, +                (bits >> 16) & 0xff, (bits >> 24) & 0xff +            }; +            //conditions where there are two header bytes +            if (num_bytes >= 3 and buff[num_bytes-1] != 0 and buff[num_bytes-2] != 0 and buff[num_bytes-3] == 0){ +                if (int(num_bytes-2) != _ctrl_transport->usrp_control_read( +                    VRQ_SPI_READ, (buff[num_bytes-1] << 8) | (buff[num_bytes-2] << 0), +                    (which_slave << 8) | SPI_FMT_MSB | SPI_FMT_HDR_2, +                    buff, num_bytes-2 +                )) throw uhd::io_error("USRP1: failed SPI readback transaction"); +            } + +            //conditions where there is one header byte +            else if (num_bytes >= 2 and buff[num_bytes-1] != 0 and buff[num_bytes-2] == 0){ +                if (int(num_bytes-1) != _ctrl_transport->usrp_control_read( +                    VRQ_SPI_READ, buff[num_bytes-1], +                    (which_slave << 8) | SPI_FMT_MSB | SPI_FMT_HDR_1, +                    buff, num_bytes-1 +                )) throw uhd::io_error("USRP1: failed SPI readback transaction"); +            } +            else{ +                throw uhd::io_error("USRP1: invalid input data for SPI readback"); +            } +            boost::uint32_t val = (((boost::uint32_t)buff[0]) <<  0) | +                                  (((boost::uint32_t)buff[1]) <<  8) | +                                  (((boost::uint32_t)buff[2]) << 16) | +                                  (((boost::uint32_t)buff[3]) << 24); +            return val;  +        } +        else { +            // Byteswap on num_bytes +            unsigned char buff[4] = { 0 }; +            for (size_t i = 1; i <= num_bytes; i++) +                buff[num_bytes - i] = (bits >> ((i - 1) * 8)) & 0xff; + +            boost::uint8_t w_index_h = which_slave & 0xff; +            boost::uint8_t w_index_l = (SPI_FMT_MSB | SPI_FMT_HDR_0) & 0xff; + +            int ret =_ctrl_transport->usrp_control_write( +                                          VRQ_SPI_WRITE, +                                          0x00, +                                          (w_index_h << 8) | (w_index_l << 0), +                                          buff, num_bytes); + +            if (ret < 0) throw uhd::io_error("USRP1: failed SPI transaction"); + +            return 0; +        } +    } + +private: +    uhd::usrp::fx2_ctrl::sptr _ctrl_transport; +}; + +/*********************************************************************** + * Public Make Function + **********************************************************************/ +usrp1_iface::sptr usrp1_iface::make(uhd::usrp::fx2_ctrl::sptr ctrl_transport) +{ +    return sptr(new usrp1_iface_impl(ctrl_transport)); +} diff --git a/host/lib/usrp/usrp1/usrp1_iface.hpp b/host/lib/usrp/usrp1/usrp1_iface.hpp new file mode 100644 index 000000000..c1ac34f25 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_iface.hpp @@ -0,0 +1,44 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP1_IFACE_HPP +#define INCLUDED_USRP1_IFACE_HPP + +#include "fx2_ctrl.hpp" +#include "wb_iface.hpp" +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +/*! + * The usrp1 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp1_iface : public wb_iface, public uhd::i2c_iface, public uhd::spi_iface, boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp1_iface> sptr; + +    /*! +     * Make a new usrp1 interface with the control transport. +     * \param ctrl_transport the usrp controller object +     * \return a new usrp1 interface object +     */ +    static sptr make(uhd::usrp::fx2_ctrl::sptr ctrl_transport); +}; + +#endif /* INCLUDED_USRP1_IFACE_HPP */ diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp new file mode 100644 index 000000000..7c9e61e94 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -0,0 +1,444 @@ +// +// 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 "usrp1_impl.hpp" +#include "usrp_spi_defs.h" +#include "usrp_commands.h" +#include "fpga_regs_standard.h" +#include "fpga_regs_common.h" +#include "usrp_i2c_addr.h" +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/transport/usb_control.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/images.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> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +const boost::uint16_t USRP1_VENDOR_ID  = 0xfffe; +const boost::uint16_t USRP1_PRODUCT_ID = 0x0002; +const boost::uint16_t FX2_VENDOR_ID    = 0x04b4; +const boost::uint16_t FX2_PRODUCT_ID   = 0x8613; + +const std::vector<usrp1_impl::dboard_slot_t> usrp1_impl::_dboard_slots = boost::assign::list_of +    (usrp1_impl::DBOARD_SLOT_A)(usrp1_impl::DBOARD_SLOT_B) +; + +/*********************************************************************** + * Discovery + **********************************************************************/ +static device_addrs_t usrp1_find(const device_addr_t &hint) +{ +    device_addrs_t usrp1_addrs; + +    //return an empty list of addresses when type is set to non-usrp1 +    if (hint.has_key("type") and hint["type"] != "usrp1") return usrp1_addrs; + +    //Return an empty list of addresses when an address is specified, +    //since an address is intended for a different, non-USB, device. +    if (hint.has_key("addr")) return usrp1_addrs; + +    boost::uint16_t vid = hint.has_key("uninit") ? FX2_VENDOR_ID : USRP1_VENDOR_ID; +    boost::uint16_t pid = hint.has_key("uninit") ? FX2_PRODUCT_ID : USRP1_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)) { +        //extract the firmware path for the USRP1 +        std::string usrp1_fw_image; +        try{ +            usrp1_fw_image = find_image_path(hint.get("fw", "usrp1_fw.ihx")); +        } +        catch(...){ +            UHD_MSG(warning) << boost::format( +                "Could not locate USRP1 firmware.\n" +                "Please install the images package.\n" +            ); +            return usrp1_addrs; +        } +        UHD_LOG << "USRP1 firmware image: " << usrp1_fw_image << std::endl; + +        usb_control::sptr control; +        try{control = usb_control::make(handle);} +        catch(const uhd::exception &){continue;} //ignore claimed + +        fx2_ctrl::make(control)->usrp_load_firmware(usrp1_fw_image); +    } + +    //get descriptors again with serial number, but using the initialized VID/PID now since we have firmware +    vid = USRP1_VENDOR_ID; +    pid = USRP1_PRODUCT_ID; + +    BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { +        usb_control::sptr control; +        try{control = usb_control::make(handle);} +        catch(const uhd::exception &){continue;} //ignore claimed + +        fx2_ctrl::sptr fx2_ctrl = fx2_ctrl::make(control); +        const mboard_eeprom_t mb_eeprom(*fx2_ctrl, mboard_eeprom_t::MAP_B000); +        device_addr_t new_addr; +        new_addr["type"] = "usrp1"; +        new_addr["name"] = mb_eeprom["name"]; +        new_addr["serial"] = handle->get_serial(); +        //this is a found usrp1 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"]) +        ){ +            usrp1_addrs.push_back(new_addr); +        } +    } + +    return usrp1_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp1_make(const device_addr_t &device_addr){ +    return device::sptr(new usrp1_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_usrp1_device){ +    device::register_device(&usrp1_find, &usrp1_make); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ +    UHD_MSG(status) << "Opening a USRP1 device..." << std::endl; + +    //extract the FPGA path for the USRP1 +    std::string usrp1_fpga_image = find_image_path( +        device_addr.get("fpga", "usrp1_fpga.rbf") +    ); +    UHD_LOG << "USRP1 FPGA image: " << usrp1_fpga_image << std::endl; + +    //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(USRP1_VENDOR_ID, USRP1_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 controller objects +    //////////////////////////////////////////////////////////////////// +    //usb_control::sptr usb_ctrl = usb_control::make(handle); +    _fx2_ctrl = fx2_ctrl::make(usb_control::make(handle)); +    _fx2_ctrl->usrp_load_fpga(usrp1_fpga_image); +    _fx2_ctrl->usrp_init(); +    _data_transport = usb_zero_copy::make( +        handle,        // identifier +        6,             // IN endpoint +        2,             // OUT endpoint +        device_addr    // param hints +    ); +    _iface = usrp1_iface::make(_fx2_ctrl); +    _soft_time_ctrl = soft_time_ctrl::make( +        boost::bind(&usrp1_impl::rx_stream_on_off, this, _1) +    ); +    _dbc["A"]; _dbc["B"]; //ensure that keys exist + +    // Normal mode with no loopback or Rx counting +    _iface->poke32(FR_MODE, 0x00000000); +    _iface->poke32(FR_DEBUG_EN, 0x00000000); +    _iface->poke32(FR_RX_SAMPLE_RATE_DIV, 0x00000001); //divide by 2 +    _iface->poke32(FR_TX_SAMPLE_RATE_DIV, 0x00000001); //divide by 2 +    _iface->poke32(FR_DC_OFFSET_CL_EN, 0x0000000f); + +    // Reset offset correction registers +    _iface->poke32(FR_ADC_OFFSET_0, 0x00000000); +    _iface->poke32(FR_ADC_OFFSET_1, 0x00000000); +    _iface->poke32(FR_ADC_OFFSET_2, 0x00000000); +    _iface->poke32(FR_ADC_OFFSET_3, 0x00000000); + +    // Set default for RX format to 16-bit I&Q and no half-band filter bypass +    _iface->poke32(FR_RX_FORMAT, 0x00000300); + +    // Set default for TX format to 16-bit I&Q +    _iface->poke32(FR_TX_FORMAT, 0x00000000); + +    UHD_LOG +        << "USRP1 Capabilities" << std::endl +        << "    number of duc's: " << get_num_ddcs() << std::endl +        << "    number of ddc's: " << get_num_ducs() << std::endl +        << "    rx halfband:     " << has_rx_halfband() << std::endl +        << "    tx halfband:     " << has_tx_halfband() << std::endl +    ; + +    //////////////////////////////////////////////////////////////////// +    // Initialize the properties tree +    //////////////////////////////////////////////////////////////////// +    _tree = property_tree::make(); +    _tree->create<std::string>("/name").set("USRP1 Device"); +    const fs_path mb_path = "/mboards/0"; +    _tree->create<std::string>(mb_path / "name").set("USRP1 (Classic)"); +    _tree->create<std::string>(mb_path / "load_eeprom") +        .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + +    //////////////////////////////////////////////////////////////////// +    // setup the mboard eeprom +    //////////////////////////////////////////////////////////////////// +    const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, mboard_eeprom_t::MAP_B000); +    _tree->create<mboard_eeprom_t>(mb_path / "eeprom") +        .set(mb_eeprom) +        .subscribe(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create clock control objects +    //////////////////////////////////////////////////////////////////// +    _master_clock_rate = 64e6; +    try{ +        if (not mb_eeprom["mcr"].empty()) +            _master_clock_rate = boost::lexical_cast<double>(mb_eeprom["mcr"]); +    }catch(const std::exception &e){ +        UHD_MSG(error) << "Error parsing FPGA clock rate from EEPROM: " << e.what() << std::endl; +    } +    UHD_MSG(status) << boost::format("Using FPGA clock rate of %fMHz...") % (_master_clock_rate/1e6) << std::endl; +    _tree->create<double>(mb_path / "tick_rate").set(_master_clock_rate); + +    //////////////////////////////////////////////////////////////////// +    // create codec control objects +    //////////////////////////////////////////////////////////////////// +    BOOST_FOREACH(const std::string &db, _dbc.keys()){ +        _dbc[db].codec = usrp1_codec_ctrl::make(_iface, (db == "A")? SPI_ENABLE_CODEC_A : SPI_ENABLE_CODEC_B); +        const fs_path rx_codec_path = mb_path / "rx_codecs" / db; +        const fs_path tx_codec_path = mb_path / "tx_codecs" / db; +        _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); +        _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::rx_pga_gain_range); +        _tree->create<double>(rx_codec_path / "gains/pga/value") +            .coerce(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1)); +        _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); +        _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::tx_pga_gain_range); +        _tree->create<double>(tx_codec_path / "gains/pga/value") +            .subscribe(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1)) +            .publish(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec)); +    } + +    //////////////////////////////////////////////////////////////////// +    // and do the misc mboard sensors +    //////////////////////////////////////////////////////////////////// +    //none for now... +    _tree->create<int>(mb_path / "sensors"); //phony property so this dir exists + +    //////////////////////////////////////////////////////////////////// +    // create frontend control objects +    //////////////////////////////////////////////////////////////////// +    _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") +        .subscribe(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1)); +    _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") +        .subscribe(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1)); + +    //////////////////////////////////////////////////////////////////// +    // create rx dsp control objects +    //////////////////////////////////////////////////////////////////// +    for (size_t dspno = 0; dspno < get_num_ddcs(); dspno++){ +        fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); +        _tree->create<double>(rx_dsp_path / "rate/value") +            .coerce(boost::bind(&usrp1_impl::update_rx_samp_rate, this, _1)); +        _tree->create<double>(rx_dsp_path / "freq/value") +            .coerce(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1)); +        _tree->create<meta_range_t>(rx_dsp_path / "freq/range") +            .set(meta_range_t(-_master_clock_rate/2, +_master_clock_rate/2)); +        _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd"); +        if (dspno == 0){ +            //only subscribe the callback for dspno 0 since it will stream all dsps +            _tree->access<stream_cmd_t>(rx_dsp_path / "stream_cmd") +                .subscribe(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1)); +        } +    } + +    //////////////////////////////////////////////////////////////////// +    // create tx dsp control objects +    //////////////////////////////////////////////////////////////////// +    for (size_t dspno = 0; dspno < get_num_ducs(); dspno++){ +        fs_path tx_dsp_path = mb_path / str(boost::format("tx_dsps/%u") % dspno); +        _tree->create<double>(tx_dsp_path / "rate/value") +            .coerce(boost::bind(&usrp1_impl::update_tx_samp_rate, this, _1)); +        _tree->create<double>(tx_dsp_path / "freq/value") +            .coerce(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1)); +        _tree->create<meta_range_t>(tx_dsp_path / "freq/range") //magic scalar comes from codec control: +            .set(meta_range_t(-_master_clock_rate*0.6875, +_master_clock_rate*0.6875)); +    } + +    //////////////////////////////////////////////////////////////////// +    // create time control objects +    //////////////////////////////////////////////////////////////////// +    _tree->create<time_spec_t>(mb_path / "time/now") +        .publish(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl)) +        .subscribe(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1)); + +    _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(std::vector<std::string>(1, "internal")); +    _tree->create<std::vector<std::string> >(mb_path / "time_source/options").set(std::vector<std::string>(1, "none")); +    _tree->create<std::string>(mb_path / "clock_source/value").set("internal"); +    _tree->create<std::string>(mb_path / "time_source/value").set("none"); + +    //////////////////////////////////////////////////////////////////// +    // create dboard control objects +    //////////////////////////////////////////////////////////////////// +    BOOST_FOREACH(const std::string &db, _dbc.keys()){ + +        //read the dboard eeprom to extract the dboard ids +        dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; +        rx_db_eeprom.load(*_fx2_ctrl, (db == "A")? (I2C_ADDR_RX_A) : (I2C_ADDR_RX_B)); +        tx_db_eeprom.load(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A) : (I2C_ADDR_TX_B)); +        gdb_eeprom.load(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A ^ 5) : (I2C_ADDR_TX_B ^ 5)); + +        //create the properties and register subscribers +        _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "rx_eeprom") +            .set(rx_db_eeprom) +            .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1)); +        _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "tx_eeprom") +            .set(tx_db_eeprom) +            .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1)); +        _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "gdb_eeprom") +            .set(gdb_eeprom) +            .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1)); + +        //create a new dboard interface and manager +        _dbc[db].dboard_iface = make_dboard_iface( +            _iface, _dbc[db].codec, +            (db == "A")? DBOARD_SLOT_A : DBOARD_SLOT_B, +            _master_clock_rate, rx_db_eeprom.id +        ); +        _tree->create<dboard_iface::sptr>(mb_path / "dboards" / db/ "iface").set(_dbc[db].dboard_iface); +        _dbc[db].dboard_manager = dboard_manager::make( +            rx_db_eeprom.id, +            ((gdb_eeprom.id == dboard_id_t::none())? tx_db_eeprom : gdb_eeprom).id, +            _dbc[db].dboard_iface +        ); +        BOOST_FOREACH(const std::string &name, _dbc[db].dboard_manager->get_rx_subdev_names()){ +            dboard_manager::populate_prop_tree_from_subdev( +                _tree->subtree(mb_path / "dboards" / db/ "rx_frontends" / name), +                _dbc[db].dboard_manager->get_rx_subdev(name) +            ); +        } +        BOOST_FOREACH(const std::string &name, _dbc[db].dboard_manager->get_tx_subdev_names()){ +            dboard_manager::populate_prop_tree_from_subdev( +                _tree->subtree(mb_path / "dboards" / db/ "tx_frontends" / name), +                _dbc[db].dboard_manager->get_tx_subdev(name) +            ); +        } + +        //init the subdev specs if we have a dboard (wont leave this loop empty) +        if (rx_db_eeprom.id != dboard_id_t::none() or _rx_subdev_spec.empty()){ +            _rx_subdev_spec = subdev_spec_t(db + ":" + _dbc[db].dboard_manager->get_rx_subdev_names()[0]); +        } +        if (tx_db_eeprom.id != dboard_id_t::none() or _tx_subdev_spec.empty()){ +            _tx_subdev_spec = subdev_spec_t(db + ":" + _dbc[db].dboard_manager->get_tx_subdev_names()[0]); +        } +    } + +    //initialize io handling +    this->io_init(); + +    //////////////////////////////////////////////////////////////////// +    // do some post-init tasks +    //////////////////////////////////////////////////////////////////// +    //and now that the tick rate is set, init the host rates to something +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ +        _tree->access<double>(mb_path / "rx_dsps" / name / "rate" / "value").set(1e6); +    } +    BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "tx_dsps")){ +        _tree->access<double>(mb_path / "tx_dsps" / name / "rate" / "value").set(1e6); +    } + +    _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(_rx_subdev_spec); +    _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(_tx_subdev_spec); +  +} + +usrp1_impl::~usrp1_impl(void){ +    _io_impl.reset(); //stops vandal before other stuff gets deconstructed +    UHD_SAFE_CALL( +        this->enable_rx(false); +        this->enable_tx(false); +    ) +} + +/*! + * Capabilities Register + * + *    3                   2                   1                   0 + *  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 + * +-----------------------------------------------+-+-----+-+-----+ + * |               Reserved                        |T|DUCs |R|DDCs | + * +-----------------------------------------------+-+-----+-+-----+ + */ +size_t usrp1_impl::get_num_ddcs(void){ +    boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); +    return (regval >> 0) & 0x0007; +} + +size_t usrp1_impl::get_num_ducs(void){ +    boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); +    return (regval >> 4) & 0x0007; +} + +bool usrp1_impl::has_rx_halfband(void){ +    boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); +    return (regval >> 3) & 0x0001; +} + +bool usrp1_impl::has_tx_halfband(void){ +    boost::uint32_t regval = _iface->peek32(FR_RB_CAPS); +    return (regval >> 7) & 0x0001; +} + +/*********************************************************************** + * Properties callback methods below + **********************************************************************/ +void usrp1_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom){ +    mb_eeprom.commit(*_fx2_ctrl, mboard_eeprom_t::MAP_B000); +} + +void usrp1_impl::set_db_eeprom(const std::string &db, const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ +    if (type == "rx") db_eeprom.store(*_fx2_ctrl, (db == "A")? (I2C_ADDR_RX_A) : (I2C_ADDR_RX_B)); +    if (type == "tx") db_eeprom.store(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A) : (I2C_ADDR_TX_B)); +    if (type == "gdb") db_eeprom.store(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A ^ 5) : (I2C_ADDR_TX_B ^ 5)); +} + +double usrp1_impl::update_rx_codec_gain(const std::string &db, const double gain){ +    //set gain on both I and Q, readback on one +    //TODO in the future, gains should have individual control +    _dbc[db].codec->set_rx_pga_gain(gain, 'A'); +    _dbc[db].codec->set_rx_pga_gain(gain, 'B'); +    return _dbc[db].codec->get_rx_pga_gain('A'); +} diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp new file mode 100644 index 000000000..261223883 --- /dev/null +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -0,0 +1,177 @@ +// +// 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 "usrp1_iface.hpp" +#include "codec_ctrl.hpp" +#include "soft_time_ctrl.hpp" +#include <uhd/device.hpp> +#include <uhd/property_tree.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/mboard_eeprom.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_USRP1_IMPL_HPP +#define INCLUDED_USRP1_IMPL_HPP + +/*! + * USRP1 implementation guts: + * The implementation details are encapsulated here. + * Handles properties on the mboard, dboard, dsps... + */ +class usrp1_impl : public uhd::device { +public: +    //! used everywhere to differentiate slots/sides... +    enum dboard_slot_t{ +        DBOARD_SLOT_A = 'A', +        DBOARD_SLOT_B = 'B' +    }; +    //and a way to enumerate through a list of the above... +    static const std::vector<dboard_slot_t> _dboard_slots; + +    //structors +    usrp1_impl(const uhd::device_addr_t &); +    ~usrp1_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: +    uhd::property_tree::sptr _tree; + +    //device properties interface +    void get(const wax::obj &, wax::obj &val){ +        val = _tree; //entry point into property tree +    } +    uhd::property_tree::sptr get_tree(void) const{ +        return _tree; +    } + +    //controllers +    uhd::usrp::fx2_ctrl::sptr _fx2_ctrl; +    usrp1_iface::sptr _iface; +    uhd::usrp::soft_time_ctrl::sptr _soft_time_ctrl; +    uhd::transport::usb_zero_copy::sptr _data_transport; +    struct db_container_type{ +        usrp1_codec_ctrl::sptr codec; +        uhd::usrp::dboard_iface::sptr dboard_iface; +        uhd::usrp::dboard_manager::sptr dboard_manager; +    }; +    uhd::dict<std::string, db_container_type> _dbc; + +    double _master_clock_rate; //clock rate shadow + +    void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); +    void set_db_eeprom(const std::string &, const std::string &, const uhd::usrp::dboard_eeprom_t &); +    double update_rx_codec_gain(const std::string &, const double); //sets A and B at once +    void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); +    double update_rx_samp_rate(const double); +    double update_tx_samp_rate(const double); +    double update_rx_dsp_freq(const size_t, const double); +    double update_tx_dsp_freq(const size_t, const double); + +    static uhd::usrp::dboard_iface::sptr make_dboard_iface( +        usrp1_iface::sptr, +        usrp1_codec_ctrl::sptr, +        dboard_slot_t, +        const double, +        const uhd::usrp::dboard_id_t & +    ); + +    //handle io stuff +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void io_init(void); +    void rx_stream_on_off(bool); +    void tx_stream_on_off(bool); +    void handle_overrun(size_t); + +    //otw types +    uhd::otw_type_t _rx_otw_type, _tx_otw_type; +    uhd::usrp::subdev_spec_t _rx_subdev_spec, _tx_subdev_spec; + +    //capabilities +    size_t get_num_ducs(void); +    size_t get_num_ddcs(void); +    bool has_rx_halfband(void); +    bool has_tx_halfband(void); + +    void vandal_conquest_loop(void); + +    //handle the enables +    bool _rx_enabled, _tx_enabled; +    void enable_rx(bool enb){ +        _rx_enabled = enb; +        _fx2_ctrl->usrp_rx_enable(enb); +    } +    void enable_tx(bool enb){ +        _tx_enabled = enb; +        _fx2_ctrl->usrp_tx_enable(enb); +    } + +    //conditionally disable and enable rx +    bool disable_rx(void){ +        if (_rx_enabled){ +            enable_rx(false); +            return true; +        } +        return false; +    } +    void restore_rx(bool last){ +        if (last != _rx_enabled){ +            enable_rx(last); +        } +    } + +    //conditionally disable and enable tx +    bool disable_tx(void){ +        if (_tx_enabled){ +            enable_tx(false); +            return true; +        } +        return false; +    } +    void restore_tx(bool last){ +        if (last != _tx_enabled){ +            enable_tx(last); +        } +    } +}; + +#endif /* INCLUDED_USRP1_IMPL_HPP */ diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt new file mode 100644 index 000000000..10f7407b0 --- /dev/null +++ b/host/lib/usrp/usrp2/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the USRP2 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF) + +IF(ENABLE_USRP2) +    LIBUHD_APPEND_SOURCES( +        ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/io_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/usrp2_impl.cpp +    ) +ENDIF(ENABLE_USRP2) diff --git a/host/lib/usrp/usrp2/clock_ctrl.cpp b/host/lib/usrp/usrp2/clock_ctrl.cpp new file mode 100644 index 000000000..66c7a6c28 --- /dev/null +++ b/host/lib/usrp/usrp2/clock_ctrl.cpp @@ -0,0 +1,377 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "clock_ctrl.hpp" +#include "ad9510_regs.hpp" +#include "usrp2_regs.hpp" //spi slave constants +#include "usrp2_clk_regs.hpp" +#include <uhd/utils/safe_call.hpp> +#include <uhd/utils/assert_has.hpp> +#include <boost/cstdint.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> + +using namespace uhd; + +static const bool enb_test_clk = false; + +/*! + * A usrp2 clock control specific to the ad9510 ic. + */ +class usrp2_clock_ctrl_impl : public usrp2_clock_ctrl{ +public: +    usrp2_clock_ctrl_impl(usrp2_iface::sptr iface){ +        _iface = iface; +        clk_regs = usrp2_clk_regs_t(_iface->get_rev()); + +        _ad9510_regs.cp_current_setting = ad9510_regs_t::CP_CURRENT_SETTING_3_0MA; +        this->write_reg(clk_regs.pll_3); + +        // Setup the clock registers to 100MHz: +        //  This was already done by the firmware (or the host couldnt communicate). +        //  We could remove this part, and just leave it to the firmware. +        //  But why not leave it in for those who want to mess with clock settings? +        //  100mhz = 10mhz/R * (P*B + A) + +        _ad9510_regs.pll_power_down = ad9510_regs_t::PLL_POWER_DOWN_NORMAL; +        _ad9510_regs.prescaler_value = ad9510_regs_t::PRESCALER_VALUE_DIV2; +        this->write_reg(clk_regs.pll_4); + +        _ad9510_regs.acounter = 0; +        this->write_reg(clk_regs.acounter); + +        _ad9510_regs.bcounter_msb = 0; +        _ad9510_regs.bcounter_lsb = 5; +        this->write_reg(clk_regs.bcounter_msb); +        this->write_reg(clk_regs.bcounter_lsb); + +        _ad9510_regs.ref_counter_msb = 0; +        _ad9510_regs.ref_counter_lsb = 1; // r divider = 1 +        this->write_reg(clk_regs.ref_counter_msb); +        this->write_reg(clk_regs.ref_counter_lsb); + +        /* regs will be updated in commands below */ + +        this->enable_external_ref(false); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +        this->enable_mimo_clock_out(false); + +        /* private clock enables, must be set here */ +        this->enable_dac_clock(true); +        this->enable_adc_clock(true); +        this->enable_test_clock(enb_test_clk); +    } + +    ~usrp2_clock_ctrl_impl(void){UHD_SAFE_CALL( +        //power down clock outputs +        this->enable_external_ref(false); +        this->enable_rx_dboard_clock(false); +        this->enable_tx_dboard_clock(false); +        this->enable_dac_clock(false); +        this->enable_adc_clock(false); +        this->enable_mimo_clock_out(false); +        this->enable_test_clock(false); +    )} + +    void enable_mimo_clock_out(bool enb){ +        //calculate the low and high dividers +        size_t divider = size_t(this->get_master_clock_rate()/10e6); +        size_t high = divider/2; +        size_t low = divider - high; + +        switch(clk_regs.exp){ +        case 2: //U2 rev 3 +            _ad9510_regs.power_down_lvpecl_out2 = enb? +                ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_NORMAL : +                ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_SAFE_PD; +            _ad9510_regs.output_level_lvpecl_out2 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT2_810MV; +            //set the registers (divider - 1) +            _ad9510_regs.divider_low_cycles_out2 = low - 1; +            _ad9510_regs.divider_high_cycles_out2 = high - 1; +            _ad9510_regs.bypass_divider_out2 = 0; +            break; + +        case 5: //U2 rev 4 +            _ad9510_regs.power_down_lvds_cmos_out5 = enb? 0 : 1; +            _ad9510_regs.lvds_cmos_select_out5 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT5_LVDS; +            _ad9510_regs.output_level_lvds_out5 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT5_1_75MA; +            //set the registers (divider - 1) +            _ad9510_regs.divider_low_cycles_out5 = low - 1; +            _ad9510_regs.divider_high_cycles_out5 = high - 1; +            _ad9510_regs.bypass_divider_out5 = 0; +            break; +             +        case 6: //U2+ +            _ad9510_regs.power_down_lvds_cmos_out6 = enb? 0 : 1; +            _ad9510_regs.lvds_cmos_select_out6 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT6_LVDS; +            _ad9510_regs.output_level_lvds_out6 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT6_1_75MA; +            //set the registers (divider - 1) +            _ad9510_regs.divider_low_cycles_out6 = low - 1; +            _ad9510_regs.divider_high_cycles_out6 = high - 1; +            _ad9510_regs.bypass_divider_out5 = 0; +            break; + +        default: +            break; +        } +        this->write_reg(clk_regs.output(clk_regs.exp)); +        this->write_reg(clk_regs.div_lo(clk_regs.exp)); +        this->update_regs(); +    } + +    //uses output clock 7 (cmos) +    void enable_rx_dboard_clock(bool enb){ +        switch(_iface->get_rev()) { +            case usrp2_iface::USRP_N200_R4: +            case usrp2_iface::USRP_N210_R4: +                _ad9510_regs.power_down_lvds_cmos_out7 = enb? 0 : 1; +                _ad9510_regs.lvds_cmos_select_out7 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT7_LVDS; +                _ad9510_regs.output_level_lvds_out7 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT7_1_75MA; +                this->write_reg(clk_regs.output(clk_regs.rx_db)); +                this->update_regs(); +                break; +            default: +                _ad9510_regs.power_down_lvds_cmos_out7 = enb? 0 : 1; +                _ad9510_regs.lvds_cmos_select_out7 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT7_CMOS; +                _ad9510_regs.output_level_lvds_out7 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT7_1_75MA; +                this->write_reg(clk_regs.output(clk_regs.rx_db)); +                this->update_regs(); +                break; +        } +    } + +    void set_rate_rx_dboard_clock(double rate){ +        assert_has(get_rates_rx_dboard_clock(), rate, "rx dboard clock rate"); +        size_t divider = size_t(get_master_clock_rate()/rate); +        //bypass when the divider ratio is one +        _ad9510_regs.bypass_divider_out7 = (divider == 1)? 1 : 0; +        //calculate the low and high dividers +        size_t high = divider/2; +        size_t low = divider - high; +        //set the registers (divider - 1) +        _ad9510_regs.divider_low_cycles_out7 = low - 1; +        _ad9510_regs.divider_high_cycles_out7 = high - 1; +        //write the registers +        this->write_reg(clk_regs.div_lo(clk_regs.rx_db)); +        this->write_reg(clk_regs.div_hi(clk_regs.rx_db)); +        this->update_regs(); +    } + +    std::vector<double> get_rates_rx_dboard_clock(void){ +        std::vector<double> rates; +        for (size_t i = 1; i <= 16+16; i++) rates.push_back(get_master_clock_rate()/i); +        return rates; +    } + +    //uses output clock 6 (cmos) on USRP2 and output clock 5 (cmos) on USRP2+ +    void enable_tx_dboard_clock(bool enb){ +        switch(clk_regs.tx_db) { +        case 5: //USRP2+ +          _ad9510_regs.power_down_lvds_cmos_out5 = enb? 0 : 1; +          _ad9510_regs.lvds_cmos_select_out5 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT5_CMOS; +          _ad9510_regs.output_level_lvds_out5 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT5_1_75MA; +          break; +        case 6: //USRP2 +          _ad9510_regs.power_down_lvds_cmos_out6 = enb? 0 : 1; +          _ad9510_regs.lvds_cmos_select_out6 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT6_CMOS; +          _ad9510_regs.output_level_lvds_out6 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT6_1_75MA; +          break; +        } + +        this->write_reg(clk_regs.output(clk_regs.tx_db)); +        this->update_regs(); +    } + +    void set_rate_tx_dboard_clock(double rate){ +        assert_has(get_rates_tx_dboard_clock(), rate, "tx dboard clock rate"); +        size_t divider = size_t(get_master_clock_rate()/rate); +        //bypass when the divider ratio is one +        _ad9510_regs.bypass_divider_out6 = (divider == 1)? 1 : 0; +        //calculate the low and high dividers +        size_t high = divider/2; +        size_t low = divider - high; + +        switch(clk_regs.tx_db) { +        case 5: //USRP2+ +          _ad9510_regs.bypass_divider_out5 = (divider == 1)? 1 : 0; +          _ad9510_regs.divider_low_cycles_out5 = low - 1; +          _ad9510_regs.divider_high_cycles_out5 = high - 1; +          break; +        case 6: //USRP2 +          //bypass when the divider ratio is one +          _ad9510_regs.bypass_divider_out6 = (divider == 1)? 1 : 0; +          //set the registers (divider - 1) +          _ad9510_regs.divider_low_cycles_out6 = low - 1; +          _ad9510_regs.divider_high_cycles_out6 = high - 1; +          break; +        } + +        //write the registers +        this->write_reg(clk_regs.div_hi(clk_regs.tx_db)); +        this->write_reg(clk_regs.div_lo(clk_regs.tx_db)); +        this->update_regs(); +    } + +    std::vector<double> get_rates_tx_dboard_clock(void){ +        return get_rates_rx_dboard_clock(); //same master clock, same dividers... +    } +     +    void enable_test_clock(bool enb) { +        _ad9510_regs.power_down_lvpecl_out0 = enb? +            ad9510_regs_t::POWER_DOWN_LVPECL_OUT0_NORMAL : +            ad9510_regs_t::POWER_DOWN_LVPECL_OUT0_SAFE_PD; +        _ad9510_regs.output_level_lvpecl_out0 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT0_810MV; +        _ad9510_regs.divider_low_cycles_out0 = 0; +        _ad9510_regs.divider_high_cycles_out0 = 0; +        _ad9510_regs.bypass_divider_out0 = 1; +        this->write_reg(0x3c); +        this->write_reg(0x48); +        this->write_reg(0x49); +    } + +    /*! +     * If we are to use an external reference, enable the charge pump. +     * \param enb true to enable the CP +     */ +    void enable_external_ref(bool enb){ +        _ad9510_regs.charge_pump_mode = (enb)? +            ad9510_regs_t::CHARGE_PUMP_MODE_NORMAL : +            ad9510_regs_t::CHARGE_PUMP_MODE_3STATE ; +        _ad9510_regs.pll_mux_control = ad9510_regs_t::PLL_MUX_CONTROL_DLD_HIGH; +        _ad9510_regs.pfd_polarity = ad9510_regs_t::PFD_POLARITY_POS; +        this->write_reg(clk_regs.pll_2); +        this->update_regs(); +    } + +    double get_master_clock_rate(void){ +        return 100e6; +    } +     +    void set_mimo_clock_delay(double delay) { +        //delay_val is a 5-bit value (0-31) for fine control +        //the equations below determine delay for a given ramp current, # of caps and fine delay register +        //delay range: +        //range_ns = 200*((caps+3)/i_ramp_ua)*1.3286 +        //offset (zero delay): +        //offset_ns = 0.34 + (1600 - i_ramp_ua)*1e-4 + ((caps-1)/ramp)*6 +        //delay_ns = offset_ns + range_ns * delay / 31 + +        int delay_val = boost::math::iround(delay/9.744e-9*31); + +        if(delay_val == 0) { +            switch(clk_regs.exp) { +            case 5: +                _ad9510_regs.delay_control_out5 = 1; +                break; +            case 6: +                _ad9510_regs.delay_control_out6 = 1; +                break; +            default: +                break; //delay not supported on U2 rev 3 +            } +        } else { +            switch(clk_regs.exp) { +            case 5: +                _ad9510_regs.delay_control_out5 = 0; +                _ad9510_regs.ramp_current_out5 = ad9510_regs_t::RAMP_CURRENT_OUT5_200UA; +                _ad9510_regs.ramp_capacitor_out5 = ad9510_regs_t::RAMP_CAPACITOR_OUT5_4CAPS; +                _ad9510_regs.delay_fine_adjust_out5 = delay_val; +                this->write_reg(0x34); +                this->write_reg(0x35); +                this->write_reg(0x36); +                break; +            case 6: +                _ad9510_regs.delay_control_out6 = 0; +                _ad9510_regs.ramp_current_out6 = ad9510_regs_t::RAMP_CURRENT_OUT6_200UA; +                _ad9510_regs.ramp_capacitor_out6 = ad9510_regs_t::RAMP_CAPACITOR_OUT6_4CAPS; +                _ad9510_regs.delay_fine_adjust_out6 = delay_val; +                this->write_reg(0x38); +                this->write_reg(0x39); +                this->write_reg(0x3A); +                break; +            default: +                break; +            } +        } +    } + +private: +    /*! +     * Write a single register to the spi regs. +     * \param addr the address to write +     */ +    void write_reg(boost::uint8_t addr){ +        boost::uint32_t data = _ad9510_regs.get_write_reg(addr); +        _iface->write_spi(SPI_SS_AD9510, spi_config_t::EDGE_RISE, data, 24); +    } + +    /*! +     * Tells the ad9510 to latch the settings into the operational registers. +     */ +    void update_regs(void){ +        _ad9510_regs.update_registers = 1; +        this->write_reg(clk_regs.update); +    } + +    //uses output clock 3 (pecl) +    //this is the same between USRP2 and USRP2+ and doesn't get a switch statement +    void enable_dac_clock(bool enb){ +        _ad9510_regs.power_down_lvpecl_out3 = (enb)? +            ad9510_regs_t::POWER_DOWN_LVPECL_OUT3_NORMAL : +            ad9510_regs_t::POWER_DOWN_LVPECL_OUT3_SAFE_PD; +        _ad9510_regs.output_level_lvpecl_out3 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT3_810MV; +        _ad9510_regs.bypass_divider_out3 = 1; +        this->write_reg(clk_regs.output(clk_regs.dac)); +        this->write_reg(clk_regs.div_hi(clk_regs.dac)); +        this->update_regs(); +    } + +    //uses output clock 4 (lvds) on USRP2 and output clock 2 (lvpecl) on USRP2+ +    void enable_adc_clock(bool enb){ +        switch(clk_regs.adc) { +        case 2: +          _ad9510_regs.power_down_lvpecl_out2 = enb? ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_NORMAL : ad9510_regs_t::POWER_DOWN_LVPECL_OUT2_SAFE_PD; +          _ad9510_regs.output_level_lvpecl_out2 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT2_500MV; +          _ad9510_regs.bypass_divider_out2 = 1; +          break; +        case 4: +          _ad9510_regs.power_down_lvds_cmos_out4 = enb? 0 : 1; +          _ad9510_regs.lvds_cmos_select_out4 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT4_LVDS; +          _ad9510_regs.output_level_lvds_out4 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT4_1_75MA; +          _ad9510_regs.bypass_divider_out4 = 1; +          break; +        } + +        this->write_reg(clk_regs.output(clk_regs.adc)); +        this->write_reg(clk_regs.div_hi(clk_regs.adc)); +        this->update_regs(); +    } +     +    usrp2_iface::sptr _iface; + +    usrp2_clk_regs_t clk_regs; +    ad9510_regs_t _ad9510_regs; +}; + +/*********************************************************************** + * Public make function for the ad9510 clock control + **********************************************************************/ +usrp2_clock_ctrl::sptr usrp2_clock_ctrl::make(usrp2_iface::sptr iface){ +    return sptr(new usrp2_clock_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp2/clock_ctrl.hpp b/host/lib/usrp/usrp2/clock_ctrl.hpp new file mode 100644 index 000000000..9ccbc959e --- /dev/null +++ b/host/lib/usrp/usrp2/clock_ctrl.hpp @@ -0,0 +1,109 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_CLOCK_CTRL_HPP +#define INCLUDED_CLOCK_CTRL_HPP + +#include "usrp2_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + +class usrp2_clock_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp2_clock_ctrl> sptr; + +    /*! +     * Make a clock config for the ad9510 ic. +     * \param _iface a pointer to the usrp2 interface object +     * \return a new clock control object +     */ +    static sptr make(usrp2_iface::sptr iface); + +    /*! +     * Get the master clock frequency for the fpga. +     * \return the clock frequency in Hz +     */ +    virtual double get_master_clock_rate(void) = 0; + +    /*! +     * Enable/disable the rx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_rx_dboard_clock(bool enb) = 0; + +    /*! +     * Set the clock rate on the rx dboard clock. +     * \param rate the new clock rate +     * \throw exception when rate invalid +     */ +    virtual void set_rate_rx_dboard_clock(double rate) = 0; + +    /*! +     * Get a list of possible rx dboard clock rates. +     * \return a list of clock rates in Hz +     */ +    virtual std::vector<double> get_rates_rx_dboard_clock(void) = 0; + +    /*! +     * Enable/disable the tx dboard clock. +     * \param enb true to enable +     */ +    virtual void enable_tx_dboard_clock(bool enb) = 0; + +    /*! +     * Set the clock rate on the tx dboard clock. +     * \param rate the new clock rate +     * \throw exception when rate invalid +     */ +    virtual void set_rate_tx_dboard_clock(double rate) = 0; + +    /*! +     * Get a list of possible tx dboard clock rates. +     * \return a list of clock rates in Hz +     */ +    virtual std::vector<double> get_rates_tx_dboard_clock(void) = 0; + +    /*! +     * Enable/disable external reference. +     * \param enb true to enable +     */ +    virtual void enable_external_ref(bool enb) = 0; +     +    /*! +     * Enable/disable test clock output. +     * \param enb true to enable +     */ +    virtual void enable_test_clock(bool enb) = 0; + +    /*! +     * Enable/disable the ref clock output over the serdes cable. +     * \param enb true to enable +     */ +    virtual void enable_mimo_clock_out(bool enb) = 0; +     +    /*! +     * Set the output delay of the mimo clock +     * Used to synchronise daisy-chained USRPs over the MIMO cable +     * Can also be used to adjust delay for uneven reference cable lengths +     * \param delay the clock delay in seconds +     */ +    virtual void set_mimo_clock_delay(double delay) = 0; + +}; + +#endif /* INCLUDED_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/codec_ctrl.cpp b/host/lib/usrp/usrp2/codec_ctrl.cpp new file mode 100644 index 000000000..06bf83b15 --- /dev/null +++ b/host/lib/usrp/usrp2/codec_ctrl.cpp @@ -0,0 +1,216 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "codec_ctrl.hpp" +#include "ad9777_regs.hpp" +#include "ads62p44_regs.hpp" +#include "usrp2_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/exception.hpp> +#include <boost/cstdint.hpp> +#include <boost/foreach.hpp> + +using namespace uhd; + +/*! + * A usrp2 codec control specific to the ad9777 ic. + */ +class usrp2_codec_ctrl_impl : public usrp2_codec_ctrl{ +public: +    usrp2_codec_ctrl_impl(usrp2_iface::sptr iface){ +        _iface = iface; + +        //setup the ad9777 dac +        _ad9777_regs.x_1r_2r_mode = ad9777_regs_t::X_1R_2R_MODE_1R; +        _ad9777_regs.filter_interp_rate = ad9777_regs_t::FILTER_INTERP_RATE_4X; +        _ad9777_regs.mix_mode = ad9777_regs_t::MIX_MODE_COMPLEX; +        _ad9777_regs.pll_divide_ratio = ad9777_regs_t::PLL_DIVIDE_RATIO_DIV1; +        _ad9777_regs.pll_state = ad9777_regs_t::PLL_STATE_ON; +        _ad9777_regs.auto_cp_control = ad9777_regs_t::AUTO_CP_CONTROL_AUTO; +        //I dac values +        _ad9777_regs.idac_fine_gain_adjust = 0; +        _ad9777_regs.idac_coarse_gain_adjust = 0xf; +        _ad9777_regs.idac_offset_adjust_lsb = 0; +        _ad9777_regs.idac_offset_adjust_msb = 0; +        //Q dac values +        _ad9777_regs.qdac_fine_gain_adjust = 0; +        _ad9777_regs.qdac_coarse_gain_adjust = 0xf; +        _ad9777_regs.qdac_offset_adjust_lsb = 0; +        _ad9777_regs.qdac_offset_adjust_msb = 0; +        //write all regs +        for(boost::uint8_t addr = 0; addr <= 0xC; addr++){ +            this->send_ad9777_reg(addr); +        } +        set_tx_mod_mode(0); + +        //power-up adc +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP2_REV3: +        case usrp2_iface::USRP2_REV4: +            _iface->poke32(U2_REG_MISC_CTRL_ADC, U2_FLAG_MISC_CTRL_ADC_ON); +            break; + +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +            _ads62p44_regs.reset = 1; +            this->send_ads62p44_reg(0x00); //issue a reset to the ADC +            //everything else should be pretty much default, i think +            //_ads62p44_regs.decimation = DECIMATION_DECIMATE_1; +            _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_NORMAL; +            this->send_ads62p44_reg(0x14); +            this->set_rx_analog_gain(1); +            break; + +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _ads62p44_regs.reset = 1; +            this->send_ads62p44_reg(0x00); //issue a reset to the ADC +            //everything else should be pretty much default, i think +            //_ads62p44_regs.decimation = DECIMATION_DECIMATE_1; +            _ads62p44_regs.override = 1; +            this->send_ads62p44_reg(0x14); +            _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_NORMAL; +            _ads62p44_regs.output_interface = ads62p44_regs_t::OUTPUT_INTERFACE_LVDS; +            _ads62p44_regs.lvds_current = ads62p44_regs_t::LVDS_CURRENT_2_5MA; +            _ads62p44_regs.lvds_data_term = ads62p44_regs_t::LVDS_DATA_TERM_100; +            this->send_ads62p44_reg(0x11); +            this->send_ads62p44_reg(0x12); +            this->send_ads62p44_reg(0x14); +            this->set_rx_analog_gain(1); +            break; + +        case usrp2_iface::USRP_NXXX: break; +        } +    } + +    ~usrp2_codec_ctrl_impl(void){UHD_SAFE_CALL( +        //power-down dac +        _ad9777_regs.power_down_mode = 1; +        this->send_ad9777_reg(0); + +        //power-down adc +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP2_REV3: +        case usrp2_iface::USRP2_REV4: +            _iface->poke32(U2_REG_MISC_CTRL_ADC, U2_FLAG_MISC_CTRL_ADC_OFF); +            break; + +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            //send a global power-down to the ADC here... it will get lifted on reset +            _ads62p44_regs.power_down = ads62p44_regs_t::POWER_DOWN_GLOBAL_PD; +            this->send_ads62p44_reg(0x14); +            break; + +        case usrp2_iface::USRP_NXXX: break; +        } +    )} + +    void set_tx_mod_mode(int mod_mode){ +        //set the sign of the frequency shift +        _ad9777_regs.modulation_form = (mod_mode > 0)? +            ad9777_regs_t::MODULATION_FORM_E_PLUS_JWT: +            ad9777_regs_t::MODULATION_FORM_E_MINUS_JWT +        ; + +        //set the frequency shift +        switch(std::abs(mod_mode)){ +        case 0: +        case 1: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_NONE; break; +        case 2: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_2; break; +        case 4: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_4; break; +        case 8: _ad9777_regs.modulation_mode = ad9777_regs_t::MODULATION_MODE_FS_8; break; +        default: throw uhd::value_error("unknown modulation mode for ad9777"); +        } + +        this->send_ad9777_reg(0x01); //set the register +    } + +    void set_rx_digital_gain(double gain) {  //fine digital gain +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _ads62p44_regs.fine_gain = int(gain/0.5); +            this->send_ads62p44_reg(0x17); +            break; + +        default: UHD_THROW_INVALID_CODE_PATH(); +        } +    } + +    void set_rx_digital_fine_gain(double gain) { //gain correction       +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _ads62p44_regs.gain_correction = int(gain / 0.05); +            this->send_ads62p44_reg(0x1A); +            break; + +        default: UHD_THROW_INVALID_CODE_PATH(); +        } +    } + +    void set_rx_analog_gain(bool /*gain*/) { //turns on/off analog 3.5dB preamp +        switch(_iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _ads62p44_regs.coarse_gain = ads62p44_regs_t::COARSE_GAIN_3_5DB;//gain ? ads62p44_regs_t::COARSE_GAIN_3_5DB : ads62p44_regs_t::COARSE_GAIN_0DB; +            this->send_ads62p44_reg(0x14); +            break; + +        default: UHD_THROW_INVALID_CODE_PATH(); +        } +    } + +private: +    ad9777_regs_t _ad9777_regs; +    ads62p44_regs_t _ads62p44_regs; +    usrp2_iface::sptr _iface; + +    void send_ad9777_reg(boost::uint8_t addr){ +        boost::uint16_t reg = _ad9777_regs.get_write_reg(addr); +        UHD_LOGV(always) << "send_ad9777_reg: " << std::hex << reg << std::endl; +        _iface->write_spi( +            SPI_SS_AD9777, spi_config_t::EDGE_RISE, +            reg, 16 +        ); +    } + +    void send_ads62p44_reg(boost::uint8_t addr) { +        boost::uint16_t reg = _ads62p44_regs.get_write_reg(addr); +        _iface->write_spi( +            SPI_SS_ADS62P44, spi_config_t::EDGE_FALL, +            reg, 16 +        ); +    } +}; + +/*********************************************************************** + * Public make function for the usrp2 codec control + **********************************************************************/ +usrp2_codec_ctrl::sptr usrp2_codec_ctrl::make(usrp2_iface::sptr iface){ +    return sptr(new usrp2_codec_ctrl_impl(iface)); +} diff --git a/host/lib/usrp/usrp2/codec_ctrl.hpp b/host/lib/usrp/usrp2/codec_ctrl.hpp new file mode 100644 index 000000000..ca300e2b1 --- /dev/null +++ b/host/lib/usrp/usrp2/codec_ctrl.hpp @@ -0,0 +1,68 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_CODEC_CTRL_HPP +#define INCLUDED_CODEC_CTRL_HPP + +#include "usrp2_iface.hpp" +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class usrp2_codec_ctrl : boost::noncopyable{ +public: +    typedef boost::shared_ptr<usrp2_codec_ctrl> sptr; + +    /*! +     * Make a codec control for the DAC and ADC. +     * \param _iface a pointer to the usrp2 interface object +     * \return a new codec control object +     */ +    static sptr make(usrp2_iface::sptr iface); + +    /*! +     * Set the modulation mode for the DAC. +     * Possible modes are 0, +/-1, +/-2, +/-4, +/-8 +     * which correspond to shifts of fs/mod_mode. +     * A mode of 0 or +/-1 means no modulation. +     * \param mod_mode the modulation mode +     */ +    virtual void set_tx_mod_mode(int mod_mode) = 0; + +    /*! +     * Set the analog preamplifier on the USRP2+ ADC (ADS62P44). +     * \param gain enable or disable the 3.5dB preamp +     */ + +    virtual void set_rx_analog_gain(bool gain) = 0; + +    /*! +     * Set the digital gain on the USRP2+ ADC (ADS62P44). +     * \param gain from 0-6dB +     */ + +    virtual void set_rx_digital_gain(double gain) = 0; + +    /*! +     * Set the digital gain correction on the USRP2+ ADC (ADS62P44). +     * \param gain from 0-0.5dB +     */ + +    virtual void set_rx_digital_fine_gain(double gain) = 0; + +}; + +#endif /* INCLUDED_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp new file mode 100644 index 000000000..c31fc52b7 --- /dev/null +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -0,0 +1,295 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "gpio_core_200.hpp" +#include "usrp2_iface.hpp" +#include "clock_ctrl.hpp" +#include "usrp2_regs.hpp" //wishbone address constants +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/algorithm.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio.hpp> //htonl and ntohl +#include <boost/math/special_functions/round.hpp> +#include "ad7922_regs.hpp" //aux adc +#include "ad5623_regs.hpp" //aux dac + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +class usrp2_dboard_iface : public dboard_iface{ +public: +    usrp2_dboard_iface(usrp2_iface::sptr iface, usrp2_clock_ctrl::sptr clock_ctrl); +    ~usrp2_dboard_iface(void); + +    special_props_t get_special_props(void){ +        special_props_t props; +        props.soft_clock_divider = false; +        props.mangle_i2c_addrs = false; +        return props; +    } + +    void write_aux_dac(unit_t, aux_dac_t, double); +    double read_aux_adc(unit_t, aux_adc_t); + +    void _set_pin_ctrl(unit_t, boost::uint16_t); +    void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); +    void _set_gpio_ddr(unit_t, boost::uint16_t); +    void _set_gpio_out(unit_t, boost::uint16_t); +    void set_gpio_debug(unit_t, int); +    boost::uint16_t read_gpio(unit_t); + +    void write_i2c(boost::uint8_t, const byte_vector_t &); +    byte_vector_t read_i2c(boost::uint8_t, size_t); + +    void set_clock_rate(unit_t, double); +    double get_clock_rate(unit_t); +    std::vector<double> get_clock_rates(unit_t); +    void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t); + +    void write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +    boost::uint32_t read_write_spi( +        unit_t unit, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits +    ); + +private: +    usrp2_iface::sptr _iface; +    usrp2_clock_ctrl::sptr _clock_ctrl; +    gpio_core_200::sptr _gpio; + +    uhd::dict<unit_t, ad5623_regs_t> _dac_regs; +    uhd::dict<unit_t, double> _clock_rates; +    void _write_aux_dac(unit_t); +}; + +/*********************************************************************** + * Make Function + **********************************************************************/ +dboard_iface::sptr make_usrp2_dboard_iface( +    usrp2_iface::sptr iface, +    usrp2_clock_ctrl::sptr clock_ctrl +){ +    return dboard_iface::sptr(new usrp2_dboard_iface(iface, clock_ctrl)); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp2_dboard_iface::usrp2_dboard_iface( +    usrp2_iface::sptr iface, +    usrp2_clock_ctrl::sptr clock_ctrl +){ +    _iface = iface; +    _clock_ctrl = clock_ctrl; +    _gpio = gpio_core_200::make(_iface, GPIO_BASE); + +    //reset the aux dacs +    _dac_regs[UNIT_RX] = ad5623_regs_t(); +    _dac_regs[UNIT_TX] = ad5623_regs_t(); +    BOOST_FOREACH(unit_t unit, _dac_regs.keys()){ +        _dac_regs[unit].data = 1; +        _dac_regs[unit].addr = ad5623_regs_t::ADDR_ALL; +        _dac_regs[unit].cmd  = ad5623_regs_t::CMD_RESET; +        this->_write_aux_dac(unit); +    } + +    //init the clock rate shadows with max rate clock +    this->set_clock_rate(UNIT_RX, sorted(this->get_clock_rates(UNIT_RX)).back()); +    this->set_clock_rate(UNIT_TX, sorted(this->get_clock_rates(UNIT_TX)).back()); +} + +usrp2_dboard_iface::~usrp2_dboard_iface(void){ +    /* NOP */ +} + +/*********************************************************************** + * Clocks + **********************************************************************/ +void usrp2_dboard_iface::set_clock_rate(unit_t unit, double rate){ +    _clock_rates[unit] = rate; //set to shadow +    switch(unit){ +    case UNIT_RX: _clock_ctrl->set_rate_rx_dboard_clock(rate); return; +    case UNIT_TX: _clock_ctrl->set_rate_tx_dboard_clock(rate); return; +    } +} + +double usrp2_dboard_iface::get_clock_rate(unit_t unit){ +    return _clock_rates[unit]; //get from shadow +} + +std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ +    switch(unit){ +    case UNIT_RX: return _clock_ctrl->get_rates_rx_dboard_clock(); +    case UNIT_TX: return _clock_ctrl->get_rates_tx_dboard_clock(); +    default: UHD_THROW_INVALID_CODE_PATH(); +    } +} + +void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ +    switch(unit){ +    case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; +    case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; +    } +} + +double usrp2_dboard_iface::get_codec_rate(unit_t){ +    return _clock_ctrl->get_master_clock_rate(); +} +/*********************************************************************** + * GPIO + **********************************************************************/ +void usrp2_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ +    return _gpio->set_pin_ctrl(unit, value); +} + +void usrp2_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ +    return _gpio->set_gpio_ddr(unit, value); +} + +void usrp2_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ +    return _gpio->set_gpio_out(unit, value); +} + +boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){ +    return _gpio->read_gpio(unit); +} + +void usrp2_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ +    return _gpio->set_atr_reg(unit, atr, value); +} + +void usrp2_dboard_iface::set_gpio_debug(unit_t, int){ +    throw uhd::not_implemented_error("no set_gpio_debug implemented"); +} + +/*********************************************************************** + * SPI + **********************************************************************/ +static const uhd::dict<dboard_iface::unit_t, int> unit_to_spi_dev = map_list_of +    (dboard_iface::UNIT_TX, SPI_SS_TX_DB) +    (dboard_iface::UNIT_RX, SPI_SS_RX_DB) +; + +void usrp2_dboard_iface::write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    _iface->write_spi(unit_to_spi_dev[unit], config, data, num_bits); +} + +boost::uint32_t usrp2_dboard_iface::read_write_spi( +    unit_t unit, +    const spi_config_t &config, +    boost::uint32_t data, +    size_t num_bits +){ +    return _iface->read_spi(unit_to_spi_dev[unit], config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void usrp2_dboard_iface::write_i2c(boost::uint8_t addr, const byte_vector_t &bytes){ +    return _iface->write_i2c(addr, bytes); +} + +byte_vector_t usrp2_dboard_iface::read_i2c(boost::uint8_t addr, size_t num_bytes){ +    return _iface->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ +    static const uhd::dict<unit_t, int> unit_to_spi_dac = map_list_of +        (UNIT_RX, SPI_SS_RX_DAC) +        (UNIT_TX, SPI_SS_TX_DAC) +    ; +    _iface->write_spi( +        unit_to_spi_dac[unit], spi_config_t::EDGE_FALL,  +        _dac_regs[unit].get_reg(), 24 +    ); +} + +void usrp2_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value){ +    _dac_regs[unit].data = boost::math::iround(4095*value/3.3); +    _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; + +    typedef uhd::dict<aux_dac_t, ad5623_regs_t::addr_t> aux_dac_to_addr; +    static const uhd::dict<unit_t, aux_dac_to_addr> unit_to_which_to_addr = map_list_of +        (UNIT_RX, map_list_of +            (AUX_DAC_A, ad5623_regs_t::ADDR_DAC_B) +            (AUX_DAC_B, ad5623_regs_t::ADDR_DAC_A) +            (AUX_DAC_C, ad5623_regs_t::ADDR_DAC_A) +            (AUX_DAC_D, ad5623_regs_t::ADDR_DAC_B) +        ) +        (UNIT_TX, map_list_of +            (AUX_DAC_A, ad5623_regs_t::ADDR_DAC_A) +            (AUX_DAC_B, ad5623_regs_t::ADDR_DAC_B) +            (AUX_DAC_C, ad5623_regs_t::ADDR_DAC_B) +            (AUX_DAC_D, ad5623_regs_t::ADDR_DAC_A) +        ) +    ; +    _dac_regs[unit].addr = unit_to_which_to_addr[unit][which]; +    this->_write_aux_dac(unit); +} + +double usrp2_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which){ +    static const uhd::dict<unit_t, int> unit_to_spi_adc = map_list_of +        (UNIT_RX, SPI_SS_RX_ADC) +        (UNIT_TX, SPI_SS_TX_ADC) +    ; + +    //setup spi config args +    spi_config_t config; +    config.mosi_edge = spi_config_t::EDGE_FALL; +    config.miso_edge = spi_config_t::EDGE_RISE; + +    //setup the spi registers +    ad7922_regs_t ad7922_regs; +    switch(which){ +    case AUX_ADC_A: ad7922_regs.mod = 0; break; +    case AUX_ADC_B: ad7922_regs.mod = 1; break; +    } ad7922_regs.chn = ad7922_regs.mod; //normal mode: mod == chn + +    //write and read spi +    _iface->write_spi( +        unit_to_spi_adc[unit], config, +        ad7922_regs.get_reg(), 16 +    ); +    ad7922_regs.set_reg(boost::uint16_t(_iface->read_spi( +        unit_to_spi_adc[unit], config, +        ad7922_regs.get_reg(), 16 +    ))); + +    //convert to voltage and return +    return 3.3*ad7922_regs.result/4095; +} diff --git a/host/lib/usrp/usrp2/fw_common.h b/host/lib/usrp/usrp2/fw_common.h new file mode 100644 index 000000000..46a57fc95 --- /dev/null +++ b/host/lib/usrp/usrp2/fw_common.h @@ -0,0 +1,160 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_FW_COMMON_H +#define INCLUDED_USRP2_FW_COMMON_H + +#include <stdint.h> + +/*! + * Structs and constants for usrp2 communication. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +//fpga and firmware compatibility numbers +#define USRP2_FPGA_COMPAT_NUM 7 +#define USRP2_FW_COMPAT_NUM 10 +#define USRP2_FW_VER_MINOR 2 + +//used to differentiate control packets over data port +#define USRP2_INVALID_VRT_HEADER 0 + +// udp ports for the usrp2 communication +// Dynamic and/or private ports: 49152-65535 +#define USRP2_UDP_CTRL_PORT 49152 +//#define USRP2_UDP_UPDATE_PORT 49154 +#define USRP2_UDP_RX_DSP0_PORT 49156 +#define USRP2_UDP_TX_DSP0_PORT 49157 +#define USRP2_UDP_RX_DSP1_PORT 49158 + +// Map for virtual firmware regs (not very big so we can keep it here for now) +#define U2_FW_REG_LOCK_TIME 0 +#define U2_FW_REG_LOCK_GPID 1 +#define U2_FW_REG_VER_MINOR 7 + +//////////////////////////////////////////////////////////////////////// +// I2C addresses +//////////////////////////////////////////////////////////////////////// +#define USRP2_I2C_DEV_EEPROM  0x50 // 24LC02[45]:  7-bits 1010xxx +#define	USRP2_I2C_ADDR_MBOARD (USRP2_I2C_DEV_EEPROM | 0x0) +#define	USRP2_I2C_ADDR_TX_DB  (USRP2_I2C_DEV_EEPROM | 0x4) +#define	USRP2_I2C_ADDR_RX_DB  (USRP2_I2C_DEV_EEPROM | 0x5) + +//////////////////////////////////////////////////////////////////////// +// EEPROM Layout +//////////////////////////////////////////////////////////////////////// +#define USRP2_EE_MBOARD_REV      0x00 //2 bytes, little-endian (historic, don't blame me) +#define USRP2_EE_MBOARD_MAC_ADDR 0x02 //6 bytes +#define USRP2_EE_MBOARD_IP_ADDR  0x0C //uint32, big-endian +#define USRP2_EE_MBOARD_BOOTLOADER_FLAGS 0xF7 + +typedef enum{ +    USRP2_CTRL_ID_HUH_WHAT = ' ', +    //USRP2_CTRL_ID_FOR_SURE, //TODO error condition enums +    //USRP2_CTRL_ID_SUX_MAN, + +    USRP2_CTRL_ID_WAZZUP_BRO = 'a', +    USRP2_CTRL_ID_WAZZUP_DUDE = 'A', + +    USRP2_CTRL_ID_TRANSACT_ME_SOME_SPI_BRO = 's', +    USRP2_CTRL_ID_OMG_TRANSACTED_SPI_DUDE = 'S', + +    USRP2_CTRL_ID_DO_AN_I2C_READ_FOR_ME_BRO = 'i', +    USRP2_CTRL_ID_HERES_THE_I2C_DATA_DUDE = 'I', + +    USRP2_CTRL_ID_WRITE_THESE_I2C_VALUES_BRO = 'h', +    USRP2_CTRL_ID_COOL_IM_DONE_I2C_WRITE_DUDE = 'H', + +    USRP2_CTRL_ID_GET_THIS_REGISTER_FOR_ME_BRO = 'r', +    USRP2_CTRL_ID_OMG_GOT_REGISTER_SO_BAD_DUDE = 'R', + +    USRP2_CTRL_ID_HEY_WRITE_THIS_UART_FOR_ME_BRO = 'u', +    USRP2_CTRL_ID_MAN_I_TOTALLY_WROTE_THAT_UART_DUDE = 'U', + +    USRP2_CTRL_ID_SO_LIKE_CAN_YOU_READ_THIS_UART_BRO = 'v', +    USRP2_CTRL_ID_I_HELLA_READ_THAT_UART_DUDE = 'V', + +    USRP2_CTRL_ID_HOLLER_AT_ME_BRO = 'l', +    USRP2_CTRL_ID_HOLLER_BACK_DUDE = 'L', + +    USRP2_CTRL_ID_PEACE_OUT = '~' + +} usrp2_ctrl_id_t; + +typedef enum{ +    USRP2_DIR_RX = 'r', +    USRP2_DIR_TX = 't' +} usrp2_dir_which_t; + +typedef enum{ +    USRP2_CLK_EDGE_RISE = 'r', +    USRP2_CLK_EDGE_FALL = 'f' +} usrp2_clk_edge_t; + +typedef enum{ +    USRP2_REG_ACTION_FPGA_PEEK32 = 1, +    USRP2_REG_ACTION_FPGA_PEEK16 = 2, +    USRP2_REG_ACTION_FPGA_POKE32 = 3, +    USRP2_REG_ACTION_FPGA_POKE16 = 4, +    USRP2_REG_ACTION_FW_PEEK32   = 5, +    USRP2_REG_ACTION_FW_POKE32   = 6 +} usrp2_reg_action_t; + +typedef struct{ +    uint32_t proto_ver; +    uint32_t id; +    uint32_t seq; +    union{ +        uint32_t ip_addr; +        struct { +            uint32_t dev; +            uint32_t data; +            uint8_t miso_edge; +            uint8_t mosi_edge; +            uint8_t num_bits; +            uint8_t readback; +        } spi_args; +        struct { +            uint8_t addr; +            uint8_t bytes; +            uint8_t data[20]; +        } i2c_args; +        struct { +            uint32_t addr; +            uint32_t data; +            uint8_t action; +        } reg_args; +        struct { +            uint8_t dev; +            uint8_t bytes; +            uint8_t data[20]; +        } uart_args; +        struct { +            uint32_t len; +        } echo_args; +    } data; +} usrp2_ctrl_data_t; + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_USRP2_FW_COMMON_H */ diff --git a/host/lib/usrp/usrp2/io_impl.cpp b/host/lib/usrp/usrp2/io_impl.cpp new file mode 100644 index 000000000..aef3f3a39 --- /dev/null +++ b/host/lib/usrp/usrp2/io_impl.cpp @@ -0,0 +1,440 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "validate_subdev_spec.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "usrp2_impl.hpp" +#include "usrp2_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/thread/thread.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/thread/mutex.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; +namespace pt = boost::posix_time; + +/*********************************************************************** + * helpers + **********************************************************************/ +static UHD_INLINE pt::time_duration to_time_dur(double timeout){ +    return pt::microseconds(long(timeout*1e6)); +} + +static UHD_INLINE double from_time_dur(const pt::time_duration &time_dur){ +    return 1e-6*time_dur.total_microseconds(); +} + +/*********************************************************************** + * constants + **********************************************************************/ +static const size_t vrt_send_header_offset_words32 = 1; + +/*********************************************************************** + * flow control monitor for a single tx channel + *  - the pirate thread calls update + *  - the get send buffer calls check + **********************************************************************/ +class flow_control_monitor{ +public: +    typedef boost::uint32_t seq_type; +    typedef boost::shared_ptr<flow_control_monitor> sptr; + +    /*! +     * Make a new flow control monitor. +     * \param max_seqs_out num seqs before throttling +     */ +    flow_control_monitor(seq_type max_seqs_out){ +        _last_seq_out = 0; +        _last_seq_ack = 0; +        _max_seqs_out = max_seqs_out; +        _ready_fcn = boost::bind(&flow_control_monitor::ready, this); +    } + +    /*! +     * Gets the current sequence number to go out. +     * Increments the sequence for the next call +     * \return the sequence to be sent to the dsp +     */ +    UHD_INLINE seq_type get_curr_seq_out(void){ +        return _last_seq_out++; +    } + +    /*! +     * Check the flow control condition. +     * \param timeout the timeout in seconds +     * \return false on timeout +     */ +    UHD_INLINE bool check_fc_condition(double timeout){ +        boost::mutex::scoped_lock lock(_fc_mutex); +        if (this->ready()) return true; +        boost::this_thread::disable_interruption di; //disable because the wait can throw +        return _fc_cond.timed_wait(lock, to_time_dur(timeout), _ready_fcn); +    } + +    /*! +     * Update the flow control condition. +     * \param seq the last sequence number to be ACK'd +     */ +    UHD_INLINE void update_fc_condition(seq_type seq){ +        boost::mutex::scoped_lock lock(_fc_mutex); +        _last_seq_ack = seq; +        lock.unlock(); +        _fc_cond.notify_one(); +    } + +private: +    bool ready(void){ +        return seq_type(_last_seq_out -_last_seq_ack) < _max_seqs_out; +    } + +    boost::mutex _fc_mutex; +    boost::condition _fc_cond; +    seq_type _last_seq_out, _last_seq_ack, _max_seqs_out; +    boost::function<bool(void)> _ready_fcn; +}; + +/*********************************************************************** + * io impl details (internal to this file) + * - pirate crew + * - alignment buffer + * - thread loop + * - vrt packet handler states + **********************************************************************/ +struct usrp2_impl::io_impl{ + +    io_impl(void): +        async_msg_fifo(100/*messages deep*/) +    { +        /* NOP */ +    } + +    managed_send_buffer::sptr get_send_buff(size_t chan, double timeout){ +        flow_control_monitor &fc_mon = *fc_mons[chan]; + +        //wait on flow control w/ timeout +        if (not fc_mon.check_fc_condition(timeout)) return managed_send_buffer::sptr(); + +        //get a buffer from the transport w/ timeout +        managed_send_buffer::sptr buff = tx_xports[chan]->get_send_buff(timeout); + +        //write the flow control word into the buffer +        if (buff.get()) buff->cast<boost::uint32_t *>()[0] = uhd::htonx(fc_mon.get_curr_seq_out()); + +        return buff; +    } + +    //tx dsp: xports and flow control monitors +    std::vector<zero_copy_if::sptr> tx_xports; +    std::vector<flow_control_monitor::sptr> fc_mons; + +    //state management for the vrt packet handler code +    sph::recv_packet_handler recv_handler; +    sph::send_packet_handler send_handler; + +    //methods and variables for the pirate crew +    void recv_pirate_loop(zero_copy_if::sptr, size_t); +    std::list<task::sptr> pirate_tasks; +    bounded_buffer<async_metadata_t> async_msg_fifo; +    double tick_rate; +}; + +/*********************************************************************** + * Receive Pirate Loop + * - while raiding, loot for message packet + * - update flow control condition count + * - put async message packets into queue + **********************************************************************/ +void usrp2_impl::io_impl::recv_pirate_loop( +    zero_copy_if::sptr err_xport, size_t index +){ +    set_thread_priority_safe(); + +    //store a reference to the flow control monitor (offset by max dsps) +    flow_control_monitor &fc_mon = *(this->fc_mons[index]); + +    while (not boost::this_thread::interruption_requested()){ +        managed_recv_buffer::sptr buff = err_xport->get_recv_buff(); +        if (not buff.get()) continue; //ignore timeout/error buffers + +        try{ +            //extract the vrt header packet info +            vrt::if_packet_info_t if_packet_info; +            if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); +            const boost::uint32_t *vrt_hdr = buff->cast<const boost::uint32_t *>(); +            vrt::if_hdr_unpack_be(vrt_hdr, if_packet_info); + +            //handle a tx async report message +            if (if_packet_info.sid == USRP2_TX_ASYNC_SID and if_packet_info.packet_type != vrt::if_packet_info_t::PACKET_TYPE_DATA){ + +                //fill in the async metadata +                async_metadata_t metadata; +                metadata.channel = index; +                metadata.has_time_spec = if_packet_info.has_tsi and if_packet_info.has_tsf; +                metadata.time_spec = time_spec_t( +                    time_t(if_packet_info.tsi), size_t(if_packet_info.tsf), tick_rate +                ); +                metadata.event_code = async_metadata_t::event_code_t(sph::get_context_code(vrt_hdr, if_packet_info)); + +                //catch the flow control packets and react +                if (metadata.event_code == 0){ +                    boost::uint32_t fc_word32 = (vrt_hdr + if_packet_info.num_header_words32)[1]; +                    fc_mon.update_fc_condition(uhd::ntohx(fc_word32)); +                    continue; +                } +                //else UHD_MSG(often) << "metadata.event_code " << metadata.event_code << std::endl; +                async_msg_fifo.push_with_pop_on_full(metadata); + +                if (metadata.event_code & +                    ( async_metadata_t::EVENT_CODE_UNDERFLOW +                    | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET) +                ) UHD_MSG(fastpath) << "U"; +                else if (metadata.event_code & +                    ( async_metadata_t::EVENT_CODE_SEQ_ERROR +                    | async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST) +                ) UHD_MSG(fastpath) << "S"; +            } +            else{ +                //TODO unknown received packet, may want to print error... +            } +        }catch(const std::exception &e){ +            UHD_MSG(error) << "Error in recv pirate loop: " << e.what() << std::endl; +        } +    } +} + +/*********************************************************************** + * Helper Functions + **********************************************************************/ +void usrp2_impl::io_init(void){ + +    //setup rx otw type +    _rx_otw_type.width = 16; +    _rx_otw_type.shift = 0; +    _rx_otw_type.byteorder = uhd::otw_type_t::BO_BIG_ENDIAN; + +    //setup tx otw type +    _tx_otw_type.width = 16; +    _tx_otw_type.shift = 0; +    _tx_otw_type.byteorder = uhd::otw_type_t::BO_BIG_ENDIAN; + +    //create new io impl +    _io_impl = UHD_PIMPL_MAKE(io_impl, ()); + +    //init first so we dont have an access race +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        //init the tx xport and flow control monitor +        _io_impl->tx_xports.push_back(_mbc[mb].tx_dsp_xport); +        _io_impl->fc_mons.push_back(flow_control_monitor::sptr(new flow_control_monitor( +            USRP2_SRAM_BYTES/_mbc[mb].tx_dsp_xport->get_send_frame_size() +        ))); +    } + +    //create a new pirate thread for each zc if (yarr!!) +    size_t index = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        //spawn a new pirate to plunder the recv booty +        _io_impl->pirate_tasks.push_back(task::make(boost::bind( +            &usrp2_impl::io_impl::recv_pirate_loop, _io_impl.get(), +            _mbc[mb].tx_dsp_xport, index++ +        ))); +    } + +    //init some handler stuff +    _io_impl->recv_handler.set_vrt_unpacker(&vrt::if_hdr_unpack_be); +    _io_impl->recv_handler.set_converter(_rx_otw_type); +    _io_impl->send_handler.set_vrt_packer(&vrt::if_hdr_pack_be, vrt_send_header_offset_words32); +    _io_impl->send_handler.set_converter(_tx_otw_type); +    _io_impl->send_handler.set_max_samples_per_packet(get_max_send_samps_per_packet()); + +    //set the packet threshold to be an entire socket buffer's worth +    const size_t packets_per_sock_buff = size_t(50e6/_mbc[_mbc.keys().front()].rx_dsp_xports[0]->get_recv_frame_size()); +    _io_impl->recv_handler.set_alignment_failure_threshold(packets_per_sock_buff); +} + +void usrp2_impl::update_tick_rate(const double rate){ +    _io_impl->tick_rate = rate; +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.set_tick_rate(rate); +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.set_tick_rate(rate); +} + +void usrp2_impl::update_rx_samp_rate(const double rate){ +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    _io_impl->recv_handler.set_samp_rate(rate); +    const double adj = _mbc[_mbc.keys().front()].rx_dsps.front()->get_scaling_adjustment(); +    _io_impl->recv_handler.set_scale_factor(adj/32767.); +} + +void usrp2_impl::update_tx_samp_rate(const double rate){ +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    _io_impl->send_handler.set_samp_rate(rate); +} + +static subdev_spec_t replace_zero_in_spec(const std::string &type, const subdev_spec_t &spec){ +    subdev_spec_t new_spec; +    BOOST_FOREACH(const subdev_spec_pair_t &pair, spec){ +        if (pair.db_name == "0"){ +            UHD_MSG(warning) +                << boost::format("In the %s subdevice specification: %s") % type % spec.to_string() << std::endl +                << "Accepting dboard slot name \"0\" for backward compatibility." << std::endl +                << "The official name of the dboard slot on USRP2/N-Series is \"A\"." << std::endl +            ; +            new_spec.push_back(subdev_spec_pair_t("A", pair.sd_name)); +        } +        else new_spec.push_back(pair); +    } +    return new_spec; +} + +subdev_spec_t usrp2_impl::update_rx_subdev_spec(const std::string &which_mb, const subdev_spec_t &spec_){ +    const subdev_spec_t spec = replace_zero_in_spec("RX", spec_); +    boost::mutex::scoped_lock recv_lock = _io_impl->recv_handler.get_scoped_lock(); +    fs_path root = "/mboards/" + which_mb + "/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "rx", which_mb); + +    //setup mux for this spec +    bool fe_swapped = false; +    for (size_t i = 0; i < spec.size(); i++){ +        const std::string conn = _tree->access<std::string>(root / spec[i].db_name / "rx_frontends" / spec[i].sd_name / "connection").get(); +        if (i == 0 and (conn == "QI" or conn == "Q")) fe_swapped = true; +        _mbc[which_mb].rx_dsps[i]->set_mux(conn, fe_swapped); +    } +    _mbc[which_mb].rx_fe->set_mux(fe_swapped); + +    //compute the new occupancy and resize +    _mbc[which_mb].rx_chan_occ = spec.size(); +    size_t nchan = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()) nchan += _mbc[mb].rx_chan_occ; +    _io_impl->recv_handler.resize(nchan); + +    //bind new callbacks for the handler +    size_t chan = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        for (size_t dsp = 0; dsp < _mbc[mb].rx_chan_occ; dsp++){ +            _mbc[mb].rx_dsps[dsp]->set_nsamps_per_packet(get_max_recv_samps_per_packet()); //seems to be a good place to set this +            _io_impl->recv_handler.set_xport_chan_get_buff(chan++, boost::bind( +                &zero_copy_if::get_recv_buff, _mbc[mb].rx_dsp_xports[dsp], _1 +            )); +        } +    } +    return spec; +} + +subdev_spec_t usrp2_impl::update_tx_subdev_spec(const std::string &which_mb, const subdev_spec_t &spec_){ +    const subdev_spec_t spec = replace_zero_in_spec("TX", spec_); +    boost::mutex::scoped_lock send_lock = _io_impl->send_handler.get_scoped_lock(); +    fs_path root = "/mboards/" + which_mb + "/dboards"; + +    //sanity checking +    validate_subdev_spec(_tree, spec, "tx", which_mb); + +    //set the mux for this spec +    const std::string conn = _tree->access<std::string>(root / spec[0].db_name / "tx_frontends" / spec[0].sd_name / "connection").get(); +    _mbc[which_mb].tx_fe->set_mux(conn); + +    //compute the new occupancy and resize +    _mbc[which_mb].tx_chan_occ = spec.size(); +    size_t nchan = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()) nchan += _mbc[mb].tx_chan_occ; +    _io_impl->send_handler.resize(nchan); + +    //bind new callbacks for the handler +    size_t chan = 0, i = 0; +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        for (size_t dsp = 0; dsp < _mbc[mb].tx_chan_occ; dsp++){ +            _io_impl->send_handler.set_xport_chan_get_buff(chan++, boost::bind( +                &usrp2_impl::io_impl::get_send_buff, _io_impl.get(), i++, _1 +            )); +        } +    } +    return spec; +} + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool usrp2_impl::recv_async_msg( +    async_metadata_t &async_metadata, double timeout +){ +    boost::this_thread::disable_interruption di; //disable because the wait can throw +    return _io_impl->async_msg_fifo.pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Send Data + **********************************************************************/ +size_t usrp2_impl::get_max_send_samps_per_packet(void) const{ +    static const size_t hdr_size = 0 +        + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +        + vrt_send_header_offset_words32*sizeof(boost::uint32_t) +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +    ; +    const size_t bpp = _mbc[_mbc.keys().front()].tx_dsp_xport->get_send_frame_size() - hdr_size; +    return bpp/_tx_otw_type.get_sample_size(); +} + +size_t usrp2_impl::send( +    const send_buffs_type &buffs, size_t nsamps_per_buff, +    const tx_metadata_t &metadata, const io_type_t &io_type, +    send_mode_t send_mode, double timeout +){ +    return _io_impl->send_handler.send( +        buffs, nsamps_per_buff, +        metadata, io_type, +        send_mode, timeout +    ); +} + +/*********************************************************************** + * Receive Data + **********************************************************************/ +size_t usrp2_impl::get_max_recv_samps_per_packet(void) const{ +    static const size_t hdr_size = 0 +        + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) +        + sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer +        - sizeof(vrt::if_packet_info_t().cid) //no class id ever used +    ; +    const size_t bpp = _mbc[_mbc.keys().front()].rx_dsp_xports[0]->get_recv_frame_size() - hdr_size; +    return bpp/_rx_otw_type.get_sample_size(); +} + +size_t usrp2_impl::recv( +    const recv_buffs_type &buffs, size_t nsamps_per_buff, +    rx_metadata_t &metadata, const io_type_t &io_type, +    recv_mode_t recv_mode, double timeout +){ +    return _io_impl->recv_handler.recv( +        buffs, nsamps_per_buff, +        metadata, io_type, +        recv_mode, timeout +    ); +} diff --git a/host/lib/usrp/usrp2/usrp2_clk_regs.hpp b/host/lib/usrp/usrp2/usrp2_clk_regs.hpp new file mode 100644 index 000000000..8b185eac0 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_clk_regs.hpp @@ -0,0 +1,87 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_CLK_REGS_HPP +#define INCLUDED_USRP2_CLK_REGS_HPP + +#include "usrp2_iface.hpp" + +class usrp2_clk_regs_t { +public: +  usrp2_clk_regs_t(void) { ; } +  usrp2_clk_regs_t(usrp2_iface::rev_type rev) { +    test = 0; +    fpga = 1; +    dac = 3; + +    switch(rev) { +    case usrp2_iface::USRP2_REV3: +        exp = 2; +        adc = 4; +        serdes = 2; +        tx_db = 6; +        break; +    case usrp2_iface::USRP2_REV4: +        exp = 5; +        adc = 4; +        serdes = 2; +        tx_db = 6; +        break; +    case usrp2_iface::USRP_N200: +    case usrp2_iface::USRP_N210: +    case usrp2_iface::USRP_N200_R4: +    case usrp2_iface::USRP_N210_R4: +        exp = 6; +        adc = 2; +        serdes = 4; +        tx_db = 5; +        break; +    case usrp2_iface::USRP_NXXX: +        //dont throw, it may be unitialized +        break; +    } +     +    rx_db = 7; +  } + +  static int output(int clknum) { return 0x3C + clknum; } +  static int div_lo(int clknum) { return 0x48 + 2 * clknum; } +  static int div_hi(int clknum) { return 0x49 + 2 * clknum; } + +  const static int acounter = 0x04; +  const static int bcounter_msb = 0x05; +  const static int bcounter_lsb = 0x06; +  const static int pll_1 = 0x07; +  const static int pll_2 = 0x08; +  const static int pll_3 = 0x09; +  const static int pll_4 = 0x0A; +  const static int ref_counter_msb = 0x0B; +  const static int ref_counter_lsb = 0x0C; +  const static int pll_5 = 0x0D; +  const static int update = 0x5A; + +  int test; +  int fpga; +  int adc; +  int dac; +  int serdes; +  int exp; +  int tx_db; +  int rx_db; +}; + +#endif //INCLUDED_USRP2_CLK_REGS_HPP diff --git a/host/lib/usrp/usrp2/usrp2_iface.cpp b/host/lib/usrp/usrp2/usrp2_iface.cpp new file mode 100644 index 000000000..aaa5acbd5 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.cpp @@ -0,0 +1,395 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_regs.hpp" +#include "fw_common.h" +#include "usrp2_iface.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/types/dict.hpp> +#include <boost/thread.hpp> +#include <boost/foreach.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/bind.hpp> +#include <boost/tokenizer.hpp> +#include <boost/functional/hash.hpp> +#include <algorithm> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +static const double CTRL_RECV_TIMEOUT = 1.0; + +static const boost::uint32_t MIN_PROTO_COMPAT_SPI = 7; +static const boost::uint32_t MIN_PROTO_COMPAT_I2C = 7; +// The register compat number must reflect the protocol compatibility +// and the compatibility of the register mapping (more likely to change). +static const boost::uint32_t MIN_PROTO_COMPAT_REG = USRP2_FW_COMPAT_NUM; +static const boost::uint32_t MIN_PROTO_COMPAT_UART = 7; + +//Define get_gpid() to get a globally unique identifier for this process. +//The gpid is implemented as a hash of the pid and a unique machine identifier. +#ifdef UHD_PLATFORM_WIN32 +#include <Windows.h> +static inline size_t get_gpid(void){ +    //extract volume serial number +    char szVolName[MAX_PATH+1], szFileSysName[MAX_PATH+1]; +    DWORD dwSerialNumber, dwMaxComponentLen, dwFileSysFlags; +    GetVolumeInformation("C:\\", szVolName, MAX_PATH, +        &dwSerialNumber, &dwMaxComponentLen, +        &dwFileSysFlags, szFileSysName, sizeof(szFileSysName)); + +    size_t hash = 0; +    boost::hash_combine(hash, GetCurrentProcessId()); +    boost::hash_combine(hash, dwSerialNumber); +    return hash; +} +#else +#include <unistd.h> +static inline size_t get_gpid(void){ +    size_t hash = 0; +    boost::hash_combine(hash, getpid()); +    boost::hash_combine(hash, gethostid()); +    return hash; +} +#endif + +class usrp2_iface_impl : public usrp2_iface{ +public: +/*********************************************************************** + * Structors + **********************************************************************/ +    usrp2_iface_impl(udp_simple::sptr ctrl_transport): +        _ctrl_transport(ctrl_transport), +        _ctrl_seq_num(0), +        _protocol_compat(0) //initialized below... +    { +        //Obtain the firmware's compat number. +        //Save the response compat number for communication. +        //TODO can choose to reject certain older compat numbers +        usrp2_ctrl_data_t ctrl_data; +        ctrl_data.id = htonl(USRP2_CTRL_ID_WAZZUP_BRO); +        ctrl_data = ctrl_send_and_recv(ctrl_data, 0, ~0); +        if (ntohl(ctrl_data.id) != USRP2_CTRL_ID_WAZZUP_DUDE) +            throw uhd::runtime_error("firmware not responding"); +        _protocol_compat = ntohl(ctrl_data.proto_ver); + +        mb_eeprom = mboard_eeprom_t(*this, mboard_eeprom_t::MAP_N100); +    } + +    ~usrp2_iface_impl(void){ +        this->lock_device(false); +    } + +/*********************************************************************** + * Device locking + **********************************************************************/ + +    void lock_device(bool lock){ +        if (lock){ +            this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_GPID, boost::uint32_t(get_gpid())); +            _lock_task = task::make(boost::bind(&usrp2_iface_impl::lock_task, this)); +        } +        else{ +            _lock_task.reset(); //shutdown the task +            this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_TIME, 0); //unlock +        } +    } + +    bool is_device_locked(void){ +        boost::uint32_t lock_secs = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_LOCK_TIME); +        boost::uint32_t lock_gpid = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_LOCK_GPID); +        boost::uint32_t curr_secs = this->peek32(U2_REG_TIME64_SECS_RB_IMM); + +        //if the difference is larger, assume not locked anymore +        if (curr_secs - lock_secs >= 3) return false; + +        //otherwise only lock if the device hash is different that ours +        return lock_gpid != boost::uint32_t(get_gpid()); +    } + +    void lock_task(void){ +        //re-lock in task +        boost::uint32_t curr_secs = this->peek32(U2_REG_TIME64_SECS_RB_IMM); +        this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_POKE32>(U2_FW_REG_LOCK_TIME, curr_secs); +        //sleep for a bit +        boost::this_thread::sleep(boost::posix_time::milliseconds(1500)); +    } + +/*********************************************************************** + * Peek and Poke + **********************************************************************/ +    void poke32(wb_addr_type addr, boost::uint32_t data){ +        this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FPGA_POKE32>(addr, data); +    } + +    boost::uint32_t peek32(wb_addr_type addr){ +        return this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FPGA_PEEK32>(addr); +    } + +    void poke16(wb_addr_type addr, boost::uint16_t data){ +        this->get_reg<boost::uint16_t, USRP2_REG_ACTION_FPGA_POKE16>(addr, data); +    } + +    boost::uint16_t peek16(wb_addr_type addr){ +        return this->get_reg<boost::uint16_t, USRP2_REG_ACTION_FPGA_PEEK16>(addr); +    } + +    template <class T, usrp2_reg_action_t action> +    T get_reg(wb_addr_type addr, T data = 0){ +        //setup the out data +        usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); +        out_data.id = htonl(USRP2_CTRL_ID_GET_THIS_REGISTER_FOR_ME_BRO); +        out_data.data.reg_args.addr = htonl(addr); +        out_data.data.reg_args.data = htonl(boost::uint32_t(data)); +        out_data.data.reg_args.action = action; + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_REG); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_OMG_GOT_REGISTER_SO_BAD_DUDE); +        return T(ntohl(in_data.data.reg_args.data)); +    } + +/*********************************************************************** + * SPI + **********************************************************************/ +    boost::uint32_t transact_spi( +        int which_slave, +        const spi_config_t &config, +        boost::uint32_t data, +        size_t num_bits, +        bool readback +    ){ +        static const uhd::dict<spi_config_t::edge_t, int> spi_edge_to_otw = boost::assign::map_list_of +            (spi_config_t::EDGE_RISE, USRP2_CLK_EDGE_RISE) +            (spi_config_t::EDGE_FALL, USRP2_CLK_EDGE_FALL) +        ; + +        //setup the out data +        usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); +        out_data.id = htonl(USRP2_CTRL_ID_TRANSACT_ME_SOME_SPI_BRO); +        out_data.data.spi_args.dev = htonl(which_slave); +        out_data.data.spi_args.miso_edge = spi_edge_to_otw[config.miso_edge]; +        out_data.data.spi_args.mosi_edge = spi_edge_to_otw[config.mosi_edge]; +        out_data.data.spi_args.readback = (readback)? 1 : 0; +        out_data.data.spi_args.num_bits = num_bits; +        out_data.data.spi_args.data = htonl(data); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_SPI); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_OMG_TRANSACTED_SPI_DUDE); + +        return ntohl(in_data.data.spi_args.data); +    } + +/*********************************************************************** + * I2C + **********************************************************************/ +    void write_i2c(boost::uint8_t addr, const byte_vector_t &buf){ +        //setup the out data +        usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); +        out_data.id = htonl(USRP2_CTRL_ID_WRITE_THESE_I2C_VALUES_BRO); +        out_data.data.i2c_args.addr = addr; +        out_data.data.i2c_args.bytes = buf.size(); + +        //limitation of i2c transaction size +        UHD_ASSERT_THROW(buf.size() <= sizeof(out_data.data.i2c_args.data)); + +        //copy in the data +        std::copy(buf.begin(), buf.end(), out_data.data.i2c_args.data); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_I2C); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_COOL_IM_DONE_I2C_WRITE_DUDE); +    } + +    byte_vector_t read_i2c(boost::uint8_t addr, size_t num_bytes){ +        //setup the out data +        usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); +        out_data.id = htonl(USRP2_CTRL_ID_DO_AN_I2C_READ_FOR_ME_BRO); +        out_data.data.i2c_args.addr = addr; +        out_data.data.i2c_args.bytes = num_bytes; + +        //limitation of i2c transaction size +        UHD_ASSERT_THROW(num_bytes <= sizeof(out_data.data.i2c_args.data)); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_I2C); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_HERES_THE_I2C_DATA_DUDE); +        UHD_ASSERT_THROW(in_data.data.i2c_args.addr = num_bytes); + +        //copy out the data +        byte_vector_t result(num_bytes); +        std::copy(in_data.data.i2c_args.data, in_data.data.i2c_args.data + num_bytes, result.begin()); +        return result; +    } + +/*********************************************************************** + * UART + **********************************************************************/ +    void write_uart(boost::uint8_t dev, const std::string &buf){ +      //first tokenize the string into 20-byte substrings +      boost::offset_separator f(20, 20, true, true); +      boost::tokenizer<boost::offset_separator> tok(buf, f); +      std::vector<std::string> queue(tok.begin(), tok.end()); + +      BOOST_FOREACH(std::string item, queue) { +        //setup the out data +        usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); +        out_data.id = htonl(USRP2_CTRL_ID_HEY_WRITE_THIS_UART_FOR_ME_BRO); +        out_data.data.uart_args.dev = dev; +        out_data.data.uart_args.bytes = item.size(); + +        //limitation of uart transaction size +        UHD_ASSERT_THROW(item.size() <= sizeof(out_data.data.uart_args.data)); + +        //copy in the data +        std::copy(item.begin(), item.end(), out_data.data.uart_args.data); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_UART); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_MAN_I_TOTALLY_WROTE_THAT_UART_DUDE); +      } +    } + +    std::string read_uart(boost::uint8_t dev){ +      int readlen = 20; +      std::string result; +      while(readlen == 20) { //while we keep receiving full packets +        //setup the out data +        usrp2_ctrl_data_t out_data = usrp2_ctrl_data_t(); +        out_data.id = htonl(USRP2_CTRL_ID_SO_LIKE_CAN_YOU_READ_THIS_UART_BRO); +        out_data.data.uart_args.dev = dev; +        out_data.data.uart_args.bytes = 20; + +        //limitation of uart transaction size +        //UHD_ASSERT_THROW(num_bytes <= sizeof(out_data.data.uart_args.data)); + +        //send and recv +        usrp2_ctrl_data_t in_data = this->ctrl_send_and_recv(out_data, MIN_PROTO_COMPAT_UART); +        UHD_ASSERT_THROW(ntohl(in_data.id) == USRP2_CTRL_ID_I_HELLA_READ_THAT_UART_DUDE); +        readlen = in_data.data.uart_args.bytes; + +        //copy out the data +        result += std::string((const char *)in_data.data.uart_args.data, (size_t)readlen); +      } +      return result; +    } +     +    gps_send_fn_t get_gps_write_fn(void) { +        return boost::bind(&usrp2_iface_impl::write_uart, this, 2, _1); //2 is the GPS UART port on USRP2 +    } +     +    gps_recv_fn_t get_gps_read_fn(void) { +        return boost::bind(&usrp2_iface_impl::read_uart, this, 2); //2 is the GPS UART port on USRP2 +    } + +/*********************************************************************** + * Send/Recv over control + **********************************************************************/ +    usrp2_ctrl_data_t ctrl_send_and_recv( +        const usrp2_ctrl_data_t &out_data, +        boost::uint32_t lo = USRP2_FW_COMPAT_NUM, +        boost::uint32_t hi = USRP2_FW_COMPAT_NUM +    ){ +        boost::mutex::scoped_lock lock(_ctrl_mutex); + +        //fill in the seq number and send +        usrp2_ctrl_data_t out_copy = out_data; +        out_copy.proto_ver = htonl(_protocol_compat); +        out_copy.seq = htonl(++_ctrl_seq_num); +        _ctrl_transport->send(boost::asio::buffer(&out_copy, sizeof(usrp2_ctrl_data_t))); + +        //loop until we get the packet or timeout +        boost::uint8_t usrp2_ctrl_data_in_mem[udp_simple::mtu]; //allocate max bytes for recv +        const usrp2_ctrl_data_t *ctrl_data_in = reinterpret_cast<const usrp2_ctrl_data_t *>(usrp2_ctrl_data_in_mem); +        while(true){ +            size_t len = _ctrl_transport->recv(boost::asio::buffer(usrp2_ctrl_data_in_mem), CTRL_RECV_TIMEOUT); +            boost::uint32_t compat = ntohl(ctrl_data_in->proto_ver); +            if(len >= sizeof(boost::uint32_t) and (hi < compat or lo > compat)){ +                throw uhd::runtime_error(str(boost::format( +                    "\nPlease update the firmware and FPGA images for your device.\n" +                    "See the application notes for USRP2/N-Series for instructions.\n" +                    "Expected protocol compatibility number %s, but got %d:\n" +                    "The firmware build is not compatible with the host code build." +                ) % ((lo == hi)? (boost::format("%d") % hi) : (boost::format("[%d to %d]") % lo % hi)) % compat)); +            } +            if (len >= sizeof(usrp2_ctrl_data_t) and ntohl(ctrl_data_in->seq) == _ctrl_seq_num){ +                return *ctrl_data_in; +            } +            if (len == 0) break; //timeout +            //didnt get seq or bad packet, continue looking... +        } +        throw uhd::runtime_error("no control response"); +    } + +    rev_type get_rev(void){ +        switch (boost::lexical_cast<boost::uint16_t>(mb_eeprom["rev"])){ +        case 0x0300: +        case 0x0301: return USRP2_REV3; +        case 0x0400: return USRP2_REV4; +        case 0x0A00: return USRP_N200; +        case 0x0A01: return USRP_N210; +        case 0x0A10: return USRP_N200_R4; +        case 0x0A11: return USRP_N210_R4; +        } +        return USRP_NXXX; //unknown type +    } + +    const std::string get_cname(void){ +        switch(this->get_rev()){ +        case USRP2_REV3: return "USRP2-REV3"; +        case USRP2_REV4: return "USRP2-REV4"; +        case USRP_N200: return "USRP-N200"; +        case USRP_N210: return "USRP-N210"; +        case USRP_N200_R4: return "USRP-N200-REV4"; +        case USRP_N210_R4: return "USRP-N210-REV4"; +        case USRP_NXXX: return "USRP-N???"; +        } +        UHD_THROW_INVALID_CODE_PATH(); +    } + +    const std::string get_fw_version_string(void){ +        boost::uint32_t minor = this->get_reg<boost::uint32_t, USRP2_REG_ACTION_FW_PEEK32>(U2_FW_REG_VER_MINOR); +        return str(boost::format("%u.%u") % _protocol_compat % minor); +    } + +private: +    //this lovely lady makes it all possible +    udp_simple::sptr _ctrl_transport; + +    //used in send/recv +    boost::mutex _ctrl_mutex; +    boost::uint32_t _ctrl_seq_num; +    boost::uint32_t _protocol_compat; + +    //lock thread stuff +    task::sptr _lock_task; +}; + +/*********************************************************************** + * Public make function for usrp2 interface + **********************************************************************/ +usrp2_iface::sptr usrp2_iface::make(udp_simple::sptr ctrl_transport){ +    return usrp2_iface::sptr(new usrp2_iface_impl(ctrl_transport)); +} + diff --git a/host/lib/usrp/usrp2/usrp2_iface.hpp b/host/lib/usrp/usrp2/usrp2_iface.hpp new file mode 100644 index 000000000..b3c3ef4a2 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_iface.hpp @@ -0,0 +1,83 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_IFACE_HPP +#define INCLUDED_USRP2_IFACE_HPP + +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <boost/function.hpp> +#include "usrp2_regs.hpp" +#include "wb_iface.hpp" +#include <string> + +//TODO: kill this crap when you have the top level GPS include file +typedef boost::function<void(std::string)> gps_send_fn_t; +typedef boost::function<std::string(void)> gps_recv_fn_t; + +/*! + * The usrp2 interface class: + * Provides a set of functions to implementation layer. + * Including spi, peek, poke, control... + */ +class usrp2_iface : public wb_iface, public uhd::spi_iface, public uhd::i2c_iface, public uhd::uart_iface{ +public: +    typedef boost::shared_ptr<usrp2_iface> sptr; +    /*! +     * Make a new usrp2 interface with the control transport. +     * \param ctrl_transport the udp transport object +     * \return a new usrp2 interface object +     */ +    static sptr make(uhd::transport::udp_simple::sptr ctrl_transport); + +    virtual gps_recv_fn_t get_gps_read_fn(void) = 0; +    virtual gps_send_fn_t get_gps_write_fn(void) = 0; + +    //! The list of possible revision types +    enum rev_type { +        USRP2_REV3 = 3, +        USRP2_REV4 = 4, +        USRP_N200 = 200, +        USRP_N200_R4 = 201, +        USRP_N210 = 210, +        USRP_N210_R4 = 211, +        USRP_NXXX = 0 +    }; + +    //! Get the revision type for this device +    virtual rev_type get_rev(void) = 0; + +    //! Get the canonical name for this device +    virtual const std::string get_cname(void) = 0; + +    //! Lock the device to this iface +    virtual void lock_device(bool lock) = 0; + +    //! Is this device locked? +    virtual bool is_device_locked(void) = 0; + +    //! A version string for firmware +    virtual const std::string get_fw_version_string(void) = 0; + +    //motherboard eeprom map structure +    uhd::usrp::mboard_eeprom_t mb_eeprom; +}; + +#endif /* INCLUDED_USRP2_IFACE_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp new file mode 100644 index 000000000..774f91d28 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -0,0 +1,732 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp2_impl.hpp" +#include "fw_common.h" +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +namespace asio = boost::asio; + +/*********************************************************************** + * Discovery over the udp transport + **********************************************************************/ +static device_addrs_t usrp2_find(const device_addr_t &hint_){ +    //handle the multi-device discovery +    device_addrs_t hints = separate_device_addr(hint_); +    if (hints.size() > 1){ +        device_addrs_t found_devices; +        BOOST_FOREACH(const device_addr_t &hint_i, hints){ +            device_addrs_t found_devices_i = usrp2_find(hint_i); +            if (found_devices_i.size() != 1) throw uhd::value_error(str(boost::format( +                "Could not resolve device hint \"%s\" to a single device." +            ) % hint_i.to_string())); +            found_devices.push_back(found_devices_i[0]); +        } +        return device_addrs_t(1, combine_device_addrs(found_devices)); +    } + +    //initialize the hint for a single device case +    UHD_ASSERT_THROW(hints.size() <= 1); +    hints.resize(1); //in case it was empty +    device_addr_t hint = hints[0]; +    device_addrs_t usrp2_addrs; + +    //return an empty list of addresses when type is set to non-usrp2 +    if (hint.has_key("type") and hint["type"] != "usrp2") return usrp2_addrs; + +    //if no address was specified, send a broadcast on each interface +    if (not hint.has_key("addr")){ +        BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()){ +            //avoid the loopback device +            if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + +            //create a new hint with this broadcast address +            device_addr_t new_hint = hint; +            new_hint["addr"] = if_addrs.bcast; + +            //call discover with the new hint and append results +            device_addrs_t new_usrp2_addrs = usrp2_find(new_hint); +            usrp2_addrs.insert(usrp2_addrs.begin(), +                new_usrp2_addrs.begin(), new_usrp2_addrs.end() +            ); +        } +        return usrp2_addrs; +    } + +    //Create a UDP transport to communicate: +    //Some devices will cause a throw when opened for a broadcast address. +    //We print and recover so the caller can loop through all bcast addrs. +    udp_simple::sptr udp_transport; +    try{ +        udp_transport = udp_simple::make_broadcast(hint["addr"], BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT)); +    } +    catch(const std::exception &e){ +        UHD_MSG(error) << boost::format("Cannot open UDP transport on %s\n%s") % hint["addr"] % e.what() << std::endl; +        return usrp2_addrs; //dont throw, but return empty address so caller can insert +    } + +    //send a hello control packet +    usrp2_ctrl_data_t ctrl_data_out = usrp2_ctrl_data_t(); +    ctrl_data_out.proto_ver = uhd::htonx<boost::uint32_t>(USRP2_FW_COMPAT_NUM); +    ctrl_data_out.id = uhd::htonx<boost::uint32_t>(USRP2_CTRL_ID_WAZZUP_BRO); +    udp_transport->send(boost::asio::buffer(&ctrl_data_out, sizeof(ctrl_data_out))); + +    //loop and recieve until the timeout +    boost::uint8_t usrp2_ctrl_data_in_mem[udp_simple::mtu]; //allocate max bytes for recv +    const usrp2_ctrl_data_t *ctrl_data_in = reinterpret_cast<const usrp2_ctrl_data_t *>(usrp2_ctrl_data_in_mem); +    while(true){ +        size_t len = udp_transport->recv(asio::buffer(usrp2_ctrl_data_in_mem)); +        if (len > offsetof(usrp2_ctrl_data_t, data) and ntohl(ctrl_data_in->id) == USRP2_CTRL_ID_WAZZUP_DUDE){ + +            //make a boost asio ipv4 with the raw addr in host byte order +            boost::asio::ip::address_v4 ip_addr(ntohl(ctrl_data_in->data.ip_addr)); +            device_addr_t new_addr; +            new_addr["type"] = "usrp2"; +            new_addr["addr"] = ip_addr.to_string(); + +            //Attempt to read the name from the EEPROM and perform filtering. +            //This operation can throw due to compatibility mismatch. +            try{ +                usrp2_iface::sptr iface = usrp2_iface::make(udp_simple::make_connected( +                    new_addr["addr"], BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) +                )); +                if (iface->is_device_locked()) continue; //ignore locked devices +                mboard_eeprom_t mb_eeprom = iface->mb_eeprom; +                new_addr["name"] = mb_eeprom["name"]; +                new_addr["serial"] = mb_eeprom["serial"]; +            } +            catch(const std::exception &){ +                //set these values as empty string so the device may still be found +                //and the filter's below can still operate on the discovered device +                new_addr["name"] = ""; +                new_addr["serial"] = ""; +            } + +            //filter the discovered device below by matching optional keys +            if ( +                (not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and +                (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) +            ){ +                usrp2_addrs.push_back(new_addr); +            } + +            //dont break here, it will exit the while loop +            //just continue on to the next loop iteration +        } +        if (len == 0) break; //timeout +    } + +    return usrp2_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr usrp2_make(const device_addr_t &device_addr){ +    return device::sptr(new usrp2_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_usrp2_device){ +    device::register_device(&usrp2_find, &usrp2_make); +} + +/*********************************************************************** + * MTU Discovery + **********************************************************************/ +struct mtu_result_t{ +    size_t recv_mtu, send_mtu; +}; + +static mtu_result_t determine_mtu(const std::string &addr, const mtu_result_t &user_mtu){ +    udp_simple::sptr udp_sock = udp_simple::make_connected( +        addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) +    ); + +    //The FPGA offers 4K buffers, and the user may manually request this. +    //However, multiple simultaneous receives (2DSP slave + 2DSP master), +    //require that buffering to be used internally, and this is a safe setting. +    std::vector<boost::uint8_t> buffer(std::max(user_mtu.recv_mtu, user_mtu.send_mtu)); +    usrp2_ctrl_data_t *ctrl_data = reinterpret_cast<usrp2_ctrl_data_t *>(&buffer.front()); +    static const double echo_timeout = 0.020; //20 ms + +    //test holler - check if its supported in this fw version +    ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); +    ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); +    ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t)); +    udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t))); +    udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); +    if (ntohl(ctrl_data->id) != USRP2_CTRL_ID_HOLLER_BACK_DUDE) +        throw uhd::not_implemented_error("holler protocol not implemented"); + +    size_t min_recv_mtu = sizeof(usrp2_ctrl_data_t), max_recv_mtu = user_mtu.recv_mtu; +    size_t min_send_mtu = sizeof(usrp2_ctrl_data_t), max_send_mtu = user_mtu.send_mtu; + +    while (min_recv_mtu < max_recv_mtu){ + +        size_t test_mtu = (max_recv_mtu/2 + min_recv_mtu/2 + 3) & ~3; + +        ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); +        ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); +        ctrl_data->data.echo_args.len = htonl(test_mtu); +        udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t))); + +        size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); + +        if (len >= test_mtu) min_recv_mtu = test_mtu; +        else                 max_recv_mtu = test_mtu - 4; + +    } + +    while (min_send_mtu < max_send_mtu){ + +        size_t test_mtu = (max_send_mtu/2 + min_send_mtu/2 + 3) & ~3; + +        ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO); +        ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM); +        ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t)); +        udp_sock->send(boost::asio::buffer(buffer, test_mtu)); + +        size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout); +        if (len >= sizeof(usrp2_ctrl_data_t)) len = ntohl(ctrl_data->data.echo_args.len); + +        if (len >= test_mtu) min_send_mtu = test_mtu; +        else                 max_send_mtu = test_mtu - 4; +    } + +    mtu_result_t mtu; +    mtu.recv_mtu = min_recv_mtu; +    mtu.send_mtu = min_send_mtu; +    return mtu; +} + +/*********************************************************************** + * Helpers + **********************************************************************/ +static zero_copy_if::sptr make_xport( +    const std::string &addr, +    const std::string &port, +    const device_addr_t &hints, +    const std::string &filter +){ + +    //only copy hints that contain the filter word +    device_addr_t filtered_hints; +    BOOST_FOREACH(const std::string &key, hints.keys()){ +        if (key.find(filter) == std::string::npos) continue; +        filtered_hints[key] = hints[key]; +    } + +    //make the transport object with the filtered hints +    zero_copy_if::sptr xport = udp_zero_copy::make(addr, port, filtered_hints); + +    //Send a small data packet so the usrp2 knows the udp source port. +    //This setup must happen before further initialization occurs +    //or the async update packets will cause ICMP destination unreachable. +    static const boost::uint32_t data[2] = { +        uhd::htonx(boost::uint32_t(0 /* don't care seq num */)), +        uhd::htonx(boost::uint32_t(USRP2_INVALID_VRT_HEADER)) +    }; +    transport::managed_send_buffer::sptr send_buff = xport->get_send_buff(); +    std::memcpy(send_buff->cast<void*>(), &data, sizeof(data)); +    send_buff->commit(sizeof(data)); + +    return xport; +} + +/*********************************************************************** + * Structors + **********************************************************************/ +usrp2_impl::usrp2_impl(const device_addr_t &_device_addr){ +    UHD_MSG(status) << "Opening a USRP2/N-Series device..." << std::endl; +    device_addr_t device_addr = _device_addr; + +    //setup the dsp transport hints (default to a large recv buff) +    if (not device_addr.has_key("recv_buff_size")){ +        #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) +            //limit buffer resize on macos or it will error +            device_addr["recv_buff_size"] = "1e6"; +        #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) +            //set to half-a-second of buffering at max rate +            device_addr["recv_buff_size"] = "50e6"; +        #endif +    } +    if (not device_addr.has_key("send_buff_size")){ +        #if defined(UHD_PLATFORM_WIN32) +            //a large send buff is ok to have on windows +            device_addr["send_buff_size"] = "50e6"; +        #endif +    } + +    device_addrs_t device_args = separate_device_addr(device_addr); + +    //extract the user's requested MTU size or default +    mtu_result_t user_mtu; +    user_mtu.recv_mtu = size_t(device_addr.cast<double>("recv_frame_size", udp_simple::mtu)); +    user_mtu.send_mtu = size_t(device_addr.cast<double>("send_frame_size", udp_simple::mtu)); + +    try{ +        //calculate the minimum send and recv mtu of all devices +        mtu_result_t mtu = determine_mtu(device_args[0]["addr"], user_mtu); +        for (size_t i = 1; i < device_args.size(); i++){ +            mtu_result_t mtu_i = determine_mtu(device_args[i]["addr"], user_mtu); +            mtu.recv_mtu = std::min(mtu.recv_mtu, mtu_i.recv_mtu); +            mtu.send_mtu = std::min(mtu.send_mtu, mtu_i.send_mtu); +        } + +        device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(mtu.recv_mtu); +        device_addr["send_frame_size"] = boost::lexical_cast<std::string>(mtu.send_mtu); + +        UHD_MSG(status) << boost::format("Current recv frame size: %d bytes") % mtu.recv_mtu << std::endl; +        UHD_MSG(status) << boost::format("Current send frame size: %d bytes") % mtu.send_mtu << std::endl; +    } +    catch(const uhd::not_implemented_error &){ +        //just ignore this error, makes older fw work... +    } + +    device_args = separate_device_addr(device_addr); //update args for new frame sizes + +    //////////////////////////////////////////////////////////////////// +    // create controller objects and initialize the properties tree +    //////////////////////////////////////////////////////////////////// +    _tree = property_tree::make(); +    _tree->create<std::string>("/name").set("USRP2 / N-Series Device"); + +    for (size_t mbi = 0; mbi < device_args.size(); mbi++){ +        const device_addr_t device_args_i = device_args[mbi]; +        const std::string mb = boost::lexical_cast<std::string>(mbi); +        const std::string addr = device_args_i["addr"]; +        const fs_path mb_path = "/mboards/" + mb; + +        //////////////////////////////////////////////////////////////// +        // create the iface that controls i2c, spi, uart, and wb +        //////////////////////////////////////////////////////////////// +        _mbc[mb].iface = usrp2_iface::make(udp_simple::make_connected( +            addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT) +        )); +        _tree->create<std::string>(mb_path / "name").set(_mbc[mb].iface->get_cname()); +        _tree->create<std::string>(mb_path / "fw_version").set(_mbc[mb].iface->get_fw_version_string()); + +        //check the fpga compatibility number +        const boost::uint32_t fpga_compat_num = _mbc[mb].iface->peek32(U2_REG_COMPAT_NUM_RB); +        boost::uint16_t fpga_major = fpga_compat_num >> 16, fpga_minor = fpga_compat_num & 0xffff; +        if (fpga_major == 0){ //old version scheme +            fpga_major = fpga_minor; +            fpga_minor = 0; +        } +        if (fpga_major != USRP2_FPGA_COMPAT_NUM){ +            throw uhd::runtime_error(str(boost::format( +                "\nPlease update the firmware and FPGA images for your device.\n" +                "See the application notes for USRP2/N-Series for instructions.\n" +                "Expected FPGA compatibility number %d, but got %d:\n" +                "The FPGA build is not compatible with the host code build." +            ) % int(USRP2_FPGA_COMPAT_NUM) % fpga_major)); +        } +        _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % fpga_major % fpga_minor)); + +        //-------------------------------------------------------------- +        if (fpga_minor == 0){ //!!! special temporary check !!!// +            throw uhd::runtime_error( +                "Detected FPGA pre-release minor version 7.0.\n" +                "This host code build requires at least 7.1.\n" +                "Please update both firmware and FPGA images\n" +            ); +        } +        //-------------------------------------------------------------- + +        //lock the device/motherboard to this process +        _mbc[mb].iface->lock_device(true); + +        //////////////////////////////////////////////////////////////// +        // construct transports for RX and TX DSPs +        //////////////////////////////////////////////////////////////// +        UHD_LOG << "Making transport for RX DSP0..." << std::endl; +        _mbc[mb].rx_dsp_xports.push_back(make_xport( +            addr, BOOST_STRINGIZE(USRP2_UDP_RX_DSP0_PORT), device_args_i, "recv" +        )); +        UHD_LOG << "Making transport for RX DSP1..." << std::endl; +        _mbc[mb].rx_dsp_xports.push_back(make_xport( +            addr, BOOST_STRINGIZE(USRP2_UDP_RX_DSP1_PORT), device_args_i, "recv" +        )); +        UHD_LOG << "Making transport for TX DSP0..." << std::endl; +        _mbc[mb].tx_dsp_xport = make_xport( +            addr, BOOST_STRINGIZE(USRP2_UDP_TX_DSP0_PORT), device_args_i, "send" +        ); +        //set the filter on the router to take dsp data from this port +        _mbc[mb].iface->poke32(U2_REG_ROUTER_CTRL_PORTS, USRP2_UDP_TX_DSP0_PORT); + +        //////////////////////////////////////////////////////////////// +        // setup the mboard eeprom +        //////////////////////////////////////////////////////////////// +        _tree->create<mboard_eeprom_t>(mb_path / "eeprom") +            .set(_mbc[mb].iface->mb_eeprom) +            .subscribe(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); + +        //////////////////////////////////////////////////////////////// +        // create clock control objects +        //////////////////////////////////////////////////////////////// +        _mbc[mb].clock = usrp2_clock_ctrl::make(_mbc[mb].iface); +        _tree->create<double>(mb_path / "tick_rate") +            .publish(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) +            .subscribe(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); + +        //////////////////////////////////////////////////////////////// +        // create codec control objects +        //////////////////////////////////////////////////////////////// +        const fs_path rx_codec_path = mb_path / "rx_codecs/A"; +        const fs_path tx_codec_path = mb_path / "tx_codecs/A"; +        _tree->create<int>(rx_codec_path / "gains"); //phony property so this dir exists +        _tree->create<int>(tx_codec_path / "gains"); //phony property so this dir exists +        _mbc[mb].codec = usrp2_codec_ctrl::make(_mbc[mb].iface); +        switch(_mbc[mb].iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4:{ +            _tree->create<std::string>(rx_codec_path / "name").set("ads62p44"); +            _tree->create<meta_range_t>(rx_codec_path / "gains/digital/range").set(meta_range_t(0, 6.0, 0.5)); +            _tree->create<double>(rx_codec_path / "gains/digital/value") +                .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); +            _tree->create<meta_range_t>(rx_codec_path / "gains/fine/range").set(meta_range_t(0, 0.5, 0.05)); +            _tree->create<double>(rx_codec_path / "gains/fine/value") +                .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); +        }break; + +        case usrp2_iface::USRP2_REV3: +        case usrp2_iface::USRP2_REV4: +            _tree->create<std::string>(rx_codec_path / "name").set("ltc2284"); +            break; + +        case usrp2_iface::USRP_NXXX: +            _tree->create<std::string>(rx_codec_path / "name").set("??????"); +            break; +        } +        _tree->create<std::string>(tx_codec_path / "name").set("ad9777"); + +        //////////////////////////////////////////////////////////////// +        // create gpsdo control objects +        //////////////////////////////////////////////////////////////// +        if (_mbc[mb].iface->mb_eeprom["gpsdo"] == "internal"){ +            _mbc[mb].gps = gps_ctrl::make( +                _mbc[mb].iface->get_gps_write_fn(), +                _mbc[mb].iface->get_gps_read_fn() +            ); +            BOOST_FOREACH(const std::string &name, _mbc[mb].gps->get_sensors()){ +                _tree->create<sensor_value_t>(mb_path / "sensors" / name) +                    .publish(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); +            } +        } + +        //////////////////////////////////////////////////////////////// +        // and do the misc mboard sensors +        //////////////////////////////////////////////////////////////// +        _tree->create<sensor_value_t>(mb_path / "sensors/mimo_locked") +            .publish(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); +        _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") +            .publish(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); + +        //////////////////////////////////////////////////////////////// +        // create frontend control objects +        //////////////////////////////////////////////////////////////// +        _mbc[mb].rx_fe = rx_frontend_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_FRONT) +        ); +        _mbc[mb].tx_fe = tx_frontend_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_TX_FRONT) +        ); +        //TODO lots of properties to expose here for frontends +        _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") +            .coerce(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); +        _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") +            .coerce(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); + +        //////////////////////////////////////////////////////////////// +        // create rx dsp control objects +        //////////////////////////////////////////////////////////////// +        _mbc[mb].rx_dsps.push_back(rx_dsp_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_DSP0), U2_REG_SR_ADDR(SR_RX_CTRL0), USRP2_RX_SID_BASE + 0, true +        )); +        _mbc[mb].rx_dsps.push_back(rx_dsp_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_RX_DSP1), U2_REG_SR_ADDR(SR_RX_CTRL1), USRP2_RX_SID_BASE + 1, true +        )); +        for (size_t dspno = 0; dspno < _mbc[mb].rx_dsps.size(); dspno++){ +            _mbc[mb].rx_dsps[dspno]->set_link_rate(USRP2_LINK_RATE_BPS); +            _tree->access<double>(mb_path / "tick_rate") +                .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); +            //This is a hack/fix for the lingering packet problem. +            //The dsp core starts streaming briefly... now we flush +            _mbc[mb].rx_dsp_xports[dspno]->get_recv_buff(0.01).get(); //recv with timeout for lingering +            _mbc[mb].rx_dsp_xports[dspno]->get_recv_buff(0.01).get(); //recv with timeout for expected +            fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); +            _tree->create<double>(rx_dsp_path / "rate/value") +                .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) +                .subscribe(boost::bind(&usrp2_impl::update_rx_samp_rate, this, _1)); +            _tree->create<double>(rx_dsp_path / "freq/value") +                .coerce(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); +            _tree->create<meta_range_t>(rx_dsp_path / "freq/range") +                .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); +            _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") +                .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); +        } + +        //////////////////////////////////////////////////////////////// +        // create tx dsp control objects +        //////////////////////////////////////////////////////////////// +        _mbc[mb].tx_dsp = tx_dsp_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_TX_DSP), U2_REG_SR_ADDR(SR_TX_CTRL), USRP2_TX_ASYNC_SID +        ); +        _mbc[mb].tx_dsp->set_link_rate(USRP2_LINK_RATE_BPS); +        _tree->access<double>(mb_path / "tick_rate") +            .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); +        _tree->create<double>(mb_path / "tx_dsps/0/rate/value") +            .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) +            .subscribe(boost::bind(&usrp2_impl::update_tx_samp_rate, this, _1)); +        _tree->create<double>(mb_path / "tx_dsps/0/freq/value") +            .coerce(boost::bind(&usrp2_impl::set_tx_dsp_freq, this, mb, _1)); +        _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") +            .publish(boost::bind(&usrp2_impl::get_tx_dsp_freq_range, this, mb)); + +        //setup dsp flow control +        const double ups_per_sec = device_args_i.cast<double>("ups_per_sec", 20); +        const size_t send_frame_size = _mbc[mb].tx_dsp_xport->get_send_frame_size(); +        const double ups_per_fifo = device_args_i.cast<double>("ups_per_fifo", 8.0); +        _mbc[mb].tx_dsp->set_updates( +            (ups_per_sec > 0.0)? size_t(100e6/*approx tick rate*//ups_per_sec) : 0, +            (ups_per_fifo > 0.0)? size_t(USRP2_SRAM_BYTES/ups_per_fifo/send_frame_size) : 0 +        ); + +        //////////////////////////////////////////////////////////////// +        // create time control objects +        //////////////////////////////////////////////////////////////// +        time64_core_200::readback_bases_type time64_rb_bases; +        time64_rb_bases.rb_secs_now = U2_REG_TIME64_SECS_RB_IMM; +        time64_rb_bases.rb_ticks_now = U2_REG_TIME64_TICKS_RB_IMM; +        time64_rb_bases.rb_secs_pps = U2_REG_TIME64_SECS_RB_PPS; +        time64_rb_bases.rb_ticks_pps = U2_REG_TIME64_TICKS_RB_PPS; +        _mbc[mb].time64 = time64_core_200::make( +            _mbc[mb].iface, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases, mimo_clock_sync_delay_cycles +        ); +        _tree->access<double>(mb_path / "tick_rate") +            .subscribe(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); +        _tree->create<time_spec_t>(mb_path / "time/now") +            .publish(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) +            .subscribe(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); +        _tree->create<time_spec_t>(mb_path / "time/pps") +            .publish(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) +            .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); +        //setup time source props +        _tree->create<std::string>(mb_path / "time_source/value") +            .subscribe(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)); +        _tree->create<std::vector<std::string> >(mb_path / "time_source/options") +            .publish(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); +        //setup reference source props +        _tree->create<std::string>(mb_path / "clock_source/value") +            .subscribe(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)); +        static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("mimo"); +        _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); + +        //////////////////////////////////////////////////////////////// +        // create dboard control objects +        //////////////////////////////////////////////////////////////// + +        //read the dboard eeprom to extract the dboard ids +        dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom; +        rx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_RX_DB); +        tx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB); +        gdb_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5); + +        //create the properties and register subscribers +        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") +            .set(rx_db_eeprom) +            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); +        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") +            .set(tx_db_eeprom) +            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); +        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") +            .set(gdb_eeprom) +            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); + +        //create a new dboard interface and manager +        _mbc[mb].dboard_iface = make_usrp2_dboard_iface(_mbc[mb].iface, _mbc[mb].clock); +        _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_mbc[mb].dboard_iface); +        _mbc[mb].dboard_manager = dboard_manager::make( +            rx_db_eeprom.id, +            ((gdb_eeprom.id == dboard_id_t::none())? tx_db_eeprom : gdb_eeprom).id, +            _mbc[mb].dboard_iface +        ); +        BOOST_FOREACH(const std::string &name, _mbc[mb].dboard_manager->get_rx_subdev_names()){ +            dboard_manager::populate_prop_tree_from_subdev( +                _tree->subtree(mb_path / "dboards/A/rx_frontends" / name), +                _mbc[mb].dboard_manager->get_rx_subdev(name) +            ); +        } +        BOOST_FOREACH(const std::string &name, _mbc[mb].dboard_manager->get_tx_subdev_names()){ +            dboard_manager::populate_prop_tree_from_subdev( +                _tree->subtree(mb_path / "dboards/A/tx_frontends" / name), +                _mbc[mb].dboard_manager->get_tx_subdev(name) +            ); +        } +    } + +    //initialize io handling +    this->io_init(); + +    //do some post-init tasks +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        fs_path root = "/mboards/" + mb; +        _tree->access<double>(root / "tick_rate").update(); + +        //and now that the tick rate is set, init the host rates to something +        BOOST_FOREACH(const std::string &name, _tree->list(root / "rx_dsps")){ +            _tree->access<double>(root / "rx_dsps" / name / "rate" / "value").set(1e6); +        } +        BOOST_FOREACH(const std::string &name, _tree->list(root / "tx_dsps")){ +            _tree->access<double>(root / "tx_dsps" / name / "rate" / "value").set(1e6); +        } + +        _tree->access<subdev_spec_t>(root / "rx_subdev_spec").set(subdev_spec_t("A:"+_mbc[mb].dboard_manager->get_rx_subdev_names()[0])); +        _tree->access<subdev_spec_t>(root / "tx_subdev_spec").set(subdev_spec_t("A:"+_mbc[mb].dboard_manager->get_tx_subdev_names()[0])); +        _tree->access<std::string>(root / "clock_source/value").set("internal"); +        _tree->access<std::string>(root / "time_source/value").set("none"); + +        //GPS installed: use external ref, time, and init time spec +        if (_mbc[mb].gps.get() != NULL){ +            _tree->access<std::string>(root / "time_source/value").set("external"); +            _tree->access<std::string>(root / "clock_source/value").set("external"); +            _mbc[mb].time64->set_time_next_pps(time_spec_t(time_t(_mbc[mb].gps->get_sensor("gps_time").to_int()+1))); +        } +    } + +} + +usrp2_impl::~usrp2_impl(void){UHD_SAFE_CALL( +    BOOST_FOREACH(const std::string &mb, _mbc.keys()){ +        _mbc[mb].tx_dsp->set_updates(0, 0); +    } +)} + +void usrp2_impl::set_mb_eeprom(const std::string &mb, const uhd::usrp::mboard_eeprom_t &mb_eeprom){ +    mb_eeprom.commit(*(_mbc[mb].iface), mboard_eeprom_t::MAP_N100); +} + +void usrp2_impl::set_db_eeprom(const std::string &mb, const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){ +    if (type == "rx") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_RX_DB); +    if (type == "tx") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB); +    if (type == "gdb") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5); +} + +sensor_value_t usrp2_impl::get_mimo_locked(const std::string &mb){ +    const bool lock = (_mbc[mb].iface->peek32(U2_REG_IRQ_RB) & (1<<10)) != 0; +    return sensor_value_t("MIMO", lock, "locked", "unlocked"); +} + +sensor_value_t usrp2_impl::get_ref_locked(const std::string &mb){ +    const bool lock = (_mbc[mb].iface->peek32(U2_REG_IRQ_RB) & (1<<11)) != 0; +    return sensor_value_t("Ref", lock, "locked", "unlocked"); +} + +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> + +double usrp2_impl::set_tx_dsp_freq(const std::string &mb, const double freq_){ +    double new_freq = freq_; +    const double tick_rate = _tree->access<double>("/mboards/"+mb+"/tick_rate").get(); + +    //calculate the DAC shift (multiples of rate) +    const int sign = boost::math::sign(new_freq); +    const int zone = std::min(boost::math::iround(new_freq/tick_rate), 2); +    const double dac_shift = sign*zone*tick_rate; +    new_freq -= dac_shift; //update FPGA DSP target freq + +    //set the DAC shift (modulation mode) +    if (zone == 0) _mbc[mb].codec->set_tx_mod_mode(0); //no shift +    else _mbc[mb].codec->set_tx_mod_mode(sign*4/zone); //DAC interp = 4 + +    return _mbc[mb].tx_dsp->set_freq(new_freq) + dac_shift; //actual freq +} + +meta_range_t usrp2_impl::get_tx_dsp_freq_range(const std::string &mb){ +    const double tick_rate = _tree->access<double>("/mboards/"+mb+"/tick_rate").get(); +    const meta_range_t dsp_range = _mbc[mb].tx_dsp->get_freq_range(); +    return meta_range_t(dsp_range.start() - tick_rate*2, dsp_range.stop() + tick_rate*2, dsp_range.step()); +} + +void usrp2_impl::update_clock_source(const std::string &mb, const std::string &source){ +    //clock source ref 10mhz +    switch(_mbc[mb].iface->get_rev()){ +    case usrp2_iface::USRP_N200: +    case usrp2_iface::USRP_N210: +    case usrp2_iface::USRP_N200_R4: +    case usrp2_iface::USRP_N210_R4: +        if (source == "internal")       _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x12); +        else if (source == "external")  _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C); +        else if (source == "mimo")      _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x15); +        else throw uhd::value_error("unhandled clock configuration reference source: " + source); +        _mbc[mb].clock->enable_external_ref(true); //USRP2P has an internal 10MHz TCXO +        break; + +    case usrp2_iface::USRP2_REV3: +    case usrp2_iface::USRP2_REV4: +        if (source == "internal")       _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x10); +        else if (source == "external")  _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C); +        else if (source == "mimo")      _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x15); +        else throw uhd::value_error("unhandled clock configuration reference source: " + source); +        _mbc[mb].clock->enable_external_ref(source != "internal"); +        break; + +    case usrp2_iface::USRP_NXXX: break; +    } + +    //always drive the clock over serdes if not locking to it +    _mbc[mb].clock->enable_mimo_clock_out(source != "mimo"); + +    //set the mimo clock delay over the serdes +    if (source != "mimo"){ +        switch(_mbc[mb].iface->get_rev()){ +        case usrp2_iface::USRP_N200: +        case usrp2_iface::USRP_N210: +        case usrp2_iface::USRP_N200_R4: +        case usrp2_iface::USRP_N210_R4: +            _mbc[mb].clock->set_mimo_clock_delay(mimo_clock_delay_usrp_n2xx); +            break; + +        case usrp2_iface::USRP2_REV4: +            _mbc[mb].clock->set_mimo_clock_delay(mimo_clock_delay_usrp2_rev4); +            break; + +        default: break; //not handled +        } +    } +} diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp new file mode 100644 index 000000000..c0f4b1e6e --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -0,0 +1,139 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_IMPL_HPP +#define INCLUDED_USRP2_IMPL_HPP + +#include "usrp2_iface.hpp" +#include "clock_ctrl.hpp" +#include "codec_ctrl.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "rx_dsp_core_200.hpp" +#include "tx_dsp_core_200.hpp" +#include "time64_core_200.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <uhd/device.hpp> +#include <uhd/utils/pimpl.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/otw_type.hpp> +#include <uhd/types/stream_cmd.hpp> +#include <uhd/types/clock_config.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/subdev_spec.hpp> + +static const double USRP2_LINK_RATE_BPS = 1000e6/8; +static const double mimo_clock_delay_usrp2_rev4 = 4.18e-9; +static const double mimo_clock_delay_usrp_n2xx = 3.55e-9; +static const size_t mimo_clock_sync_delay_cycles = 138; +static const size_t USRP2_SRAM_BYTES = size_t(1 << 20); +static const boost::uint32_t USRP2_TX_ASYNC_SID = 2; +static const boost::uint32_t USRP2_RX_SID_BASE = 3; + +/*! + * Make a usrp2 dboard interface. + * \param iface the usrp2 interface object + * \param clk_ctrl the clock control object + * \return a sptr to a new dboard interface + */ +uhd::usrp::dboard_iface::sptr make_usrp2_dboard_iface( +    usrp2_iface::sptr iface, +    usrp2_clock_ctrl::sptr clk_ctrl +); + +/*! + * USRP2 implementation guts: + * The implementation details are encapsulated here. + * Handles device properties and streaming... + */ +class usrp2_impl : public uhd::device{ +public: +    usrp2_impl(const uhd::device_addr_t &); +    ~usrp2_impl(void); + +    //the io interface +    size_t send( +        const send_buffs_type &, size_t, +        const uhd::tx_metadata_t &, const uhd::io_type_t &, +        uhd::device::send_mode_t, double +    ); +    size_t recv( +        const recv_buffs_type &, size_t, +        uhd::rx_metadata_t &, const uhd::io_type_t &, +        uhd::device::recv_mode_t, double +    ); +    size_t get_max_send_samps_per_packet(void) const; +    size_t get_max_recv_samps_per_packet(void) const; +    bool recv_async_msg(uhd::async_metadata_t &, double); + +private: +    uhd::property_tree::sptr _tree; +    struct mb_container_type{ +        usrp2_iface::sptr iface; +        usrp2_clock_ctrl::sptr clock; +        usrp2_codec_ctrl::sptr codec; +        gps_ctrl::sptr gps; +        rx_frontend_core_200::sptr rx_fe; +        tx_frontend_core_200::sptr tx_fe; +        std::vector<rx_dsp_core_200::sptr> rx_dsps; +        tx_dsp_core_200::sptr tx_dsp; +        time64_core_200::sptr time64; +        std::vector<uhd::transport::zero_copy_if::sptr> rx_dsp_xports; +        uhd::transport::zero_copy_if::sptr tx_dsp_xport; +        uhd::usrp::dboard_manager::sptr dboard_manager; +        uhd::usrp::dboard_iface::sptr dboard_iface; +        size_t rx_chan_occ, tx_chan_occ; +    }; +    uhd::dict<std::string, mb_container_type> _mbc; + +    void set_mb_eeprom(const std::string &, const uhd::usrp::mboard_eeprom_t &); +    void set_db_eeprom(const std::string &, const std::string &, const uhd::usrp::dboard_eeprom_t &); + +    uhd::sensor_value_t get_mimo_locked(const std::string &); +    uhd::sensor_value_t get_ref_locked(const std::string &); + +    //device properties interface +    void get(const wax::obj &, wax::obj &val){ +        val = _tree; //entry point into property tree +    } +    uhd::property_tree::sptr get_tree(void) const{ +        return _tree; +    } + +    //io impl methods and members +    uhd::otw_type_t _rx_otw_type, _tx_otw_type; +    UHD_PIMPL_DECL(io_impl) _io_impl; +    void io_init(void); +    void update_tick_rate(const double rate); +    void update_rx_samp_rate(const double rate); +    void update_tx_samp_rate(const double rate); +    //update spec methods are coercers until we only accept db_name == A +    uhd::usrp::subdev_spec_t update_rx_subdev_spec(const std::string &, const uhd::usrp::subdev_spec_t &); +    uhd::usrp::subdev_spec_t update_tx_subdev_spec(const std::string &, const uhd::usrp::subdev_spec_t &); +    double set_tx_dsp_freq(const std::string &, const double); +    uhd::meta_range_t get_tx_dsp_freq_range(const std::string &); +    void update_clock_source(const std::string &, const std::string &); +}; + +#endif /* INCLUDED_USRP2_IMPL_HPP */ diff --git a/host/lib/usrp/usrp2/usrp2_regs.hpp b/host/lib/usrp/usrp2/usrp2_regs.hpp new file mode 100644 index 000000000..8839997f1 --- /dev/null +++ b/host/lib/usrp/usrp2/usrp2_regs.hpp @@ -0,0 +1,105 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_USRP2_REGS_HPP +#define INCLUDED_USRP2_REGS_HPP + +//////////////////////////////////////////////////////////////////////// +// Define slave bases +//////////////////////////////////////////////////////////////////////// +#define ROUTER_RAM_BASE     0x4000 +#define SPI_BASE            0x5000 +#define I2C_BASE            0x5400 +#define GPIO_BASE           0x5800 +#define READBACK_BASE       0x5C00 +#define ETH_BASE            0x6000 +#define SETTING_REGS_BASE   0x7000 +#define PIC_BASE            0x8000 +#define UART_BASE           0x8800 +#define ATR_BASE            0x8C00 + +//////////////////////////////////////////////////////////////////////// +// Setting register offsets +//////////////////////////////////////////////////////////////////////// +#define SR_MISC       0   // 7 regs +#define SR_SIMTIMER   8   // 2 +#define SR_TIME64    10   // 6 +#define SR_BUF_POOL  16   // 4 + +#define SR_RX_FRONT  24   // 5 +#define SR_RX_CTRL0  32   // 9 +#define SR_RX_DSP0   48   // 7 +#define SR_RX_CTRL1  80   // 9 +#define SR_RX_DSP1   96   // 7 + +#define SR_TX_FRONT 128   // ? +#define SR_TX_CTRL  144   // 6 +#define SR_TX_DSP   160   // 5 + +#define SR_UDP_SM   192   // 64 + +#define U2_REG_SR_ADDR(sr) (SETTING_REGS_BASE + (4 * (sr))) + +#define U2_REG_ROUTER_CTRL_PORTS U2_REG_SR_ADDR(SR_BUF_POOL) + 8 + +///////////////////////////////////////////////// +// SPI Slave Constants +//////////////////////////////////////////////// +// Masks for controlling different peripherals +#define SPI_SS_AD9510    1 +#define SPI_SS_AD9777    2 +#define SPI_SS_RX_DAC    4 +#define SPI_SS_RX_ADC    8 +#define SPI_SS_RX_DB    16 +#define SPI_SS_TX_DAC   32 +#define SPI_SS_TX_ADC   64 +#define SPI_SS_TX_DB   128 +#define SPI_SS_ADS62P44 256 //for usrp2p + +///////////////////////////////////////////////// +// Misc Control +//////////////////////////////////////////////// +#define U2_REG_MISC_CTRL_CLOCK U2_REG_SR_ADDR(0) +#define U2_REG_MISC_CTRL_SERDES U2_REG_SR_ADDR(1) +#define U2_REG_MISC_CTRL_ADC U2_REG_SR_ADDR(2) +#define U2_REG_MISC_CTRL_LEDS U2_REG_SR_ADDR(3) +#define U2_REG_MISC_CTRL_PHY U2_REG_SR_ADDR(4) +#define U2_REG_MISC_CTRL_DBG_MUX U2_REG_SR_ADDR(5) +#define U2_REG_MISC_CTRL_RAM_PAGE U2_REG_SR_ADDR(6) +#define U2_REG_MISC_CTRL_FLUSH_ICACHE U2_REG_SR_ADDR(7) +#define U2_REG_MISC_CTRL_LED_SRC U2_REG_SR_ADDR(8) + +#define U2_FLAG_MISC_CTRL_SERDES_ENABLE 8 +#define U2_FLAG_MISC_CTRL_SERDES_PRBSEN 4 +#define U2_FLAG_MISC_CTRL_SERDES_LOOPEN 2 +#define U2_FLAG_MISC_CTRL_SERDES_RXEN   1 + +#define U2_FLAG_MISC_CTRL_ADC_ON  0x0F +#define U2_FLAG_MISC_CTRL_ADC_OFF 0x00 + +///////////////////////////////////////////////// +// Readback regs +//////////////////////////////////////////////// +#define U2_REG_STATUS READBACK_BASE + 4*8 +#define U2_REG_TIME64_SECS_RB_IMM READBACK_BASE + 4*10 +#define U2_REG_TIME64_TICKS_RB_IMM READBACK_BASE + 4*11 +#define U2_REG_COMPAT_NUM_RB READBACK_BASE + 4*12 +#define U2_REG_IRQ_RB READBACK_BASE + 4*13 +#define U2_REG_TIME64_SECS_RB_PPS READBACK_BASE + 4*14 +#define U2_REG_TIME64_TICKS_RB_PPS READBACK_BASE + 4*15 + +#endif /* INCLUDED_USRP2_REGS_HPP */ diff --git a/host/lib/utils/CMakeLists.txt b/host/lib/utils/CMakeLists.txt new file mode 100644 index 000000000..fd3249099 --- /dev/null +++ b/host/lib/utils/CMakeLists.txt @@ -0,0 +1,141 @@ +# +# 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 +######################################################################## + +######################################################################## +# Setup defines for process scheduling +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring priority scheduling...") +INCLUDE(CheckCXXSourceCompiles) + +CHECK_CXX_SOURCE_COMPILES(" +    #include <pthread.h> +    int main(){ +        struct sched_param sp; +        pthread_setschedparam(pthread_self(), SCHED_RR, &sp); +        return 0; +    } +    " HAVE_PTHREAD_SETSCHEDPARAM +) + +IF(CYGWIN) +    #SCHED_RR non-operational on cygwin +    SET(HAVE_PTHREAD_SETSCHEDPARAM False) +ENDIF(CYGWIN) + +CHECK_CXX_SOURCE_COMPILES(" +    #include <windows.h> +    int main(){ +        SetThreadPriority(GetCurrentThread(), 0); +        SetPriorityClass(GetCurrentProcess(), 0); +        return 0; +    } +    " HAVE_WIN_SETTHREADPRIORITY +) + +IF(HAVE_PTHREAD_SETSCHEDPARAM) +    MESSAGE(STATUS "  Priority scheduling supported through pthread_setschedparam.") +    SET(THREAD_PRIO_DEFS HAVE_PTHREAD_SETSCHEDPARAM) +ELSEIF(HAVE_WIN_SETTHREADPRIORITY) +    MESSAGE(STATUS "  Priority scheduling supported through windows SetThreadPriority.") +    SET(THREAD_PRIO_DEFS HAVE_WIN_SETTHREADPRIORITY) +ELSE() +    MESSAGE(STATUS "  Priority scheduling not supported.") +    SET(THREAD_PRIO_DEFS HAVE_THREAD_PRIO_DUMMY) +ENDIF() + +SET_SOURCE_FILES_PROPERTIES( +    ${CMAKE_CURRENT_SOURCE_DIR}/thread_priority.cpp +    PROPERTIES COMPILE_DEFINITIONS "${THREAD_PRIO_DEFS}" +) + +######################################################################## +# Setup defines for module loading +######################################################################## +MESSAGE(STATUS "") +MESSAGE(STATUS "Configuring module loading...") +INCLUDE(CheckCXXSourceCompiles) + +SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) +CHECK_CXX_SOURCE_COMPILES(" +    #include <dlfcn.h> +    int main(){ +        dlopen(0, 0); +        return 0; +    } +    " HAVE_DLOPEN +) +UNSET(CMAKE_REQUIRED_LIBRARIES) + +CHECK_CXX_SOURCE_COMPILES(" +    #include <windows.h> +    int main(){ +        LoadLibrary(0); +        return 0; +    } +    " HAVE_LOAD_LIBRARY +) + +IF(HAVE_DLOPEN) +    MESSAGE(STATUS "  Module loading supported through dlopen.") +    SET(LOAD_MODULES_DEFS HAVE_DLOPEN) +    LIBUHD_APPEND_LIBS(${CMAKE_DL_LIBS}) +ELSEIF(HAVE_LOAD_LIBRARY) +    MESSAGE(STATUS "  Module loading supported through LoadLibrary.") +    SET(LOAD_MODULES_DEFS HAVE_LOAD_LIBRARY) +ELSE() +    MESSAGE(STATUS "  Module loading not supported.") +    SET(LOAD_MODULES_DEFS HAVE_LOAD_MODULES_DUMMY) +ENDIF() + +SET_SOURCE_FILES_PROPERTIES( +    ${CMAKE_CURRENT_SOURCE_DIR}/load_modules.cpp +    PROPERTIES COMPILE_DEFINITIONS "${LOAD_MODULES_DEFS}" +) + +######################################################################## +# Define UHD_PKG_DATA_PATH for paths.cpp +######################################################################## +FILE(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${PKG_DATA_DIR} UHD_PKG_DATA_PATH) +STRING(REPLACE "\\" "\\\\" UHD_PKG_DATA_PATH ${UHD_PKG_DATA_PATH}) +MESSAGE(STATUS "Full package data directory: ${UHD_PKG_DATA_PATH}") + +SET_SOURCE_FILES_PROPERTIES( +    ${CMAKE_CURRENT_SOURCE_DIR}/paths.cpp +    PROPERTIES COMPILE_DEFINITIONS +    "UHD_PKG_DATA_PATH=\"${UHD_PKG_DATA_PATH}\"" +) + +######################################################################## +# Append sources +######################################################################## +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/gain_group.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/images.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/load_modules.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/log.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/msg.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/paths.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/props.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/static.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/tasks.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/thread_priority.cpp +) diff --git a/host/lib/utils/gain_group.cpp b/host/lib/utils/gain_group.cpp new file mode 100644 index 000000000..85f4977a6 --- /dev/null +++ b/host/lib/utils/gain_group.cpp @@ -0,0 +1,186 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/gain_group.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <algorithm> +#include <vector> + +using namespace uhd; + +static const bool verbose = false; + +static bool compare_by_step_size( +    const size_t &rhs, const size_t &lhs, std::vector<gain_fcns_t> &fcns +){ +    return fcns.at(rhs).get_range().step() > fcns.at(lhs).get_range().step(); +} + +/*! + * Get a multiple of step with the following relation: + *     result = step*floor(num/step) + * + * Due to small doubleing-point inaccuracies: + *     num = n*step + e, where e is a small inaccuracy. + * When e is negative, floor would yeild (n-1)*step, + * despite that n*step is really the desired result. + * This function is designed to mitigate that issue. + * + * \param num the number to approximate + * \param step the step size to round with + * \param e the small inaccuracy to account for + * \return a multiple of step approximating num + */ +template <typename T> static T floor_step(T num, T step, T e = T(0.001)){ +    return step*int(num/step + e); +} + +/*********************************************************************** + * gain group implementation + **********************************************************************/ +class gain_group_impl : public gain_group{ +public: +    gain_group_impl(void){ +        /*NOP*/ +    } + +    gain_range_t get_range(const std::string &name){ +        if (not name.empty()) return _name_to_fcns[name].get_range(); + +        double overall_min = 0, overall_max = 0, overall_step = 0; +        BOOST_FOREACH(const gain_fcns_t &fcns, get_all_fcns()){ +            const gain_range_t range = fcns.get_range(); +            overall_min += range.start(); +            overall_max += range.stop(); +            //the overall step is the min (zero is invalid, first run) +            if (overall_step == 0) overall_step = range.step(); +            overall_step = std::min(overall_step, range.step()); +        } +        return gain_range_t(overall_min, overall_max, overall_step); +    } + +    double get_value(const std::string &name){ +        if (not name.empty()) return _name_to_fcns[name].get_value(); + +        double overall_gain = 0; +        BOOST_FOREACH(const gain_fcns_t &fcns, get_all_fcns()){ +            overall_gain += fcns.get_value(); +        } +        return overall_gain; +    } + +    void set_value(double gain, const std::string &name){ +        if (not name.empty()) return _name_to_fcns[name].set_value(gain); + +        std::vector<gain_fcns_t> all_fcns = get_all_fcns(); +        if (all_fcns.size() == 0) return; //nothing to set! + +        //get the max step size among the gains +        double max_step = 0; +        BOOST_FOREACH(const gain_fcns_t &fcns, all_fcns){ +            max_step = std::max(max_step, fcns.get_range().step()); +        } + +        //create gain bucket to distribute power +        std::vector<double> gain_bucket; + +        //distribute power according to priority (round to max step) +        double gain_left_to_distribute = gain; +        BOOST_FOREACH(const gain_fcns_t &fcns, all_fcns){ +            const gain_range_t range = fcns.get_range(); +            gain_bucket.push_back(floor_step(uhd::clip( +                gain_left_to_distribute, range.start(), range.stop() +            ), max_step)); +            gain_left_to_distribute -= gain_bucket.back(); +        } + +        //get a list of indexes sorted by step size large to small +        std::vector<size_t> indexes_step_size_dec; +        for (size_t i = 0; i < all_fcns.size(); i++){ +            indexes_step_size_dec.push_back(i); +        } +        std::sort( +            indexes_step_size_dec.begin(), indexes_step_size_dec.end(), +            boost::bind(&compare_by_step_size, _1, _2, all_fcns) +        ); +        UHD_ASSERT_THROW( +            all_fcns.at(indexes_step_size_dec.front()).get_range().step() >= +            all_fcns.at(indexes_step_size_dec.back()).get_range().step() +        ); + +        //distribute the remainder (less than max step) +        //fill in the largest step sizes first that are less than the remainder +        BOOST_FOREACH(size_t i, indexes_step_size_dec){ +            const gain_range_t range = all_fcns.at(i).get_range(); +            double additional_gain = floor_step(uhd::clip( +                gain_bucket.at(i) + gain_left_to_distribute, range.start(), range.stop() +            ), range.step()) - gain_bucket.at(i); +            gain_bucket.at(i) += additional_gain; +            gain_left_to_distribute -= additional_gain; +        } +        UHD_LOGV(often) << "gain_left_to_distribute " << gain_left_to_distribute << std::endl; + +        //now write the bucket out to the individual gain values +        for (size_t i = 0; i < gain_bucket.size(); i++){ +            UHD_LOGV(often) << i << ": " << gain_bucket.at(i) << std::endl; +            all_fcns.at(i).set_value(gain_bucket.at(i)); +        } +    } + +    const std::vector<std::string> get_names(void){ +        return _name_to_fcns.keys(); +    } + +    void register_fcns( +        const std::string &name, +        const gain_fcns_t &gain_fcns, +        size_t priority +    ){ +        if (name.empty() or _name_to_fcns.has_key(name)){ +            //ensure the name name is unique and non-empty +            return register_fcns(name + "_", gain_fcns, priority); +        } +        _registry[priority].push_back(gain_fcns); +        _name_to_fcns[name] = gain_fcns; +    } + +private: +    //! get the gain function sets in order (highest priority first) +    std::vector<gain_fcns_t> get_all_fcns(void){ +        std::vector<gain_fcns_t> all_fcns; +        BOOST_FOREACH(size_t key, uhd::sorted(_registry.keys())){ +            const std::vector<gain_fcns_t> &fcns = _registry[key]; +            all_fcns.insert(all_fcns.begin(), fcns.begin(), fcns.end()); +        } +        return all_fcns; +    } + +    uhd::dict<size_t, std::vector<gain_fcns_t> > _registry; +    uhd::dict<std::string, gain_fcns_t> _name_to_fcns; +}; + +/*********************************************************************** + * gain group factory function + **********************************************************************/ +gain_group::sptr gain_group::make(void){ +    return sptr(new gain_group_impl()); +} diff --git a/host/lib/utils/images.cpp b/host/lib/utils/images.cpp new file mode 100644 index 000000000..a124cc208 --- /dev/null +++ b/host/lib/utils/images.cpp @@ -0,0 +1,40 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/images.hpp> +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> +#include <boost/filesystem.hpp> +#include <vector> + +namespace fs = boost::filesystem; + +std::vector<fs::path> get_image_paths(void); //defined in paths.cpp + +/*********************************************************************** + * Find a image in the image paths + **********************************************************************/ +std::string uhd::find_image_path(const std::string &image_name){ +    if (fs::exists(image_name)){ +        return fs::system_complete(image_name).string(); +    } +    BOOST_FOREACH(const fs::path &path, get_image_paths()){ +        fs::path image_path = path / image_name; +        if (fs::exists(image_path)) return image_path.string(); +    } +    throw uhd::io_error("Could not find path for image: " + image_name); +} diff --git a/host/lib/utils/load_modules.cpp b/host/lib/utils/load_modules.cpp new file mode 100644 index 000000000..bee0d5304 --- /dev/null +++ b/host/lib/utils/load_modules.cpp @@ -0,0 +1,109 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/static.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/filesystem.hpp> +#include <iostream> +#include <string> +#include <vector> + +namespace fs = boost::filesystem; + +/*********************************************************************** + * Module Load Function + **********************************************************************/ +#ifdef HAVE_DLOPEN +#include <dlfcn.h> +static void load_module(const std::string &file_name){ +    if (dlopen(file_name.c_str(), RTLD_LAZY) == NULL){ +        throw uhd::os_error(str( +            boost::format("dlopen failed to load \"%s\"") % file_name +        )); +    } +} +#endif /* HAVE_DLOPEN */ + + +#ifdef HAVE_LOAD_LIBRARY +#include <windows.h> +static void load_module(const std::string &file_name){ +    if (LoadLibrary(file_name.c_str()) == NULL){ +        throw uhd::os_error(str( +            boost::format("LoadLibrary failed to load \"%s\"") % file_name +        )); +    } +} +#endif /* HAVE_LOAD_LIBRARY */ + + +#ifdef HAVE_LOAD_MODULES_DUMMY +static void load_module(const std::string &file_name){ +    throw uhd::not_implemented_error(str( +        boost::format("Module loading not supported: Cannot load \"%s\"") % file_name +    )); +} +#endif /* HAVE_LOAD_MODULES_DUMMY */ + +/*********************************************************************** + * Load Modules + **********************************************************************/ +/*! + * Load all modules in a given path. + * This will recurse into sub-directories. + * Does not throw, prints to std error. + * \param path the filesystem path + */ +static void load_module_path(const fs::path &path){ +    if (not fs::exists(path)){ +        //std::cerr << boost::format("Module path \"%s\" not found.") % path.string() << std::endl; +        return; +    } + +    //try to load the files in this path +    if (fs::is_directory(path)){ +        for( +            fs::directory_iterator dir_itr(path); +            dir_itr != fs::directory_iterator(); +            ++dir_itr +        ){ +            load_module_path(dir_itr->path()); +        } +        return; +    } + +    //its not a directory, try to load it +    try{ +        load_module(path.string()); +    } +    catch(const std::exception &err){ +        std::cerr << boost::format("Error: %s") % err.what() << std::endl; +    } +} + +std::vector<fs::path> get_module_paths(void); //defined in paths.cpp + +/*! + * Load all the modules given in the module paths. + */ +UHD_STATIC_BLOCK(load_modules){ +    BOOST_FOREACH(const fs::path &path, get_module_paths()){ +        load_module_path(path); +    } +} diff --git a/host/lib/utils/log.cpp b/host/lib/utils/log.cpp new file mode 100644 index 000000000..31d11796e --- /dev/null +++ b/host/lib/utils/log.cpp @@ -0,0 +1,221 @@ +// +// 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/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/static.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#ifdef BOOST_MSVC +//whoops! https://svn.boost.org/trac/boost/ticket/5287 +//enjoy this useless dummy class instead +namespace boost{ namespace interprocess{ +    struct file_lock{ +        file_lock(const char * = NULL){} +        void lock(void){} +        void unlock(void){} +    }; +}} //namespace +#else +#include <boost/interprocess/sync/file_lock.hpp> +#endif +#ifdef BOOST_MSVC +#define USE_GET_TEMP_PATH +#include <Windows.h> //GetTempPath +#endif +#include <stdio.h> //P_tmpdir +#include <cstdlib> //getenv +#include <fstream> +#include <sstream> +#include <cctype> + +namespace fs = boost::filesystem; +namespace pt = boost::posix_time; +namespace ip = boost::interprocess; + +/*********************************************************************** + * Helper function to get the system's temporary path + **********************************************************************/ +static fs::path get_temp_path(void){ +    const char *tmp_path = NULL; + +    //try the official uhd temp path environment variable +    tmp_path = std::getenv("UHD_TEMP_PATH"); +    if (tmp_path != NULL) return tmp_path; + +    //try the windows function if available +    #ifdef USE_GET_TEMP_PATH +    char lpBuffer[2048]; +    if (GetTempPath(sizeof(lpBuffer), lpBuffer)) return lpBuffer; +    #endif + +    //try windows environment variables +    tmp_path = std::getenv("TMP"); +    if (tmp_path != NULL) return tmp_path; + +    tmp_path = std::getenv("TEMP"); +    if (tmp_path != NULL) return tmp_path; + +    //try the stdio define if available +    #ifdef P_tmpdir +        return P_tmpdir; +    #endif + +    //try unix environment variables +    tmp_path = std::getenv("TMPDIR"); +    if (tmp_path != NULL) return tmp_path; + +    //give up and use the unix default +    return "/tmp"; +} + +/*********************************************************************** + * Global resources for the logger + **********************************************************************/ +class log_resource_type{ +public: +    uhd::_log::verbosity_t level; + +    log_resource_type(void){ + +        //file lock pointer must be null +        _file_lock = NULL; + +        //set the default log level +        level = uhd::_log::never; + +        //allow override from macro definition +        #ifdef UHD_LOG_LEVEL +        _set_log_level(BOOST_STRINGIZE(UHD_LOG_LEVEL)); +        #endif + +        //allow override from environment variable +        const char * log_level_env = std::getenv("UHD_LOG_LEVEL"); +        if (log_level_env != NULL) _set_log_level(log_level_env); +    } + +    ~log_resource_type(void){ +        boost::mutex::scoped_lock lock(_mutex); +        _file_stream.close(); +        if (_file_lock != NULL) delete _file_lock; +    } + +    void log_to_file(const std::string &log_msg){ +        boost::mutex::scoped_lock lock(_mutex); +        if (_file_lock == NULL){ +            const std::string log_path = (get_temp_path() / "uhd.log").string(); +            _file_stream.open(log_path.c_str(), std::fstream::out | std::fstream::app); +            _file_lock = new ip::file_lock(log_path.c_str()); +        } +        _file_lock->lock(); +        _file_stream << log_msg << std::flush; +        _file_lock->unlock(); +    } + +private: +    //! set the log level from a string that is either a digit or an enum name +    void _set_log_level(const std::string &log_level_str){ +        const uhd::_log::verbosity_t log_level_num = uhd::_log::verbosity_t(log_level_str[0]-'0'); +        if (std::isdigit(log_level_str[0]) and log_level_num >= uhd::_log::always and log_level_num <= uhd::_log::never){ +            this->level = log_level_num; +            return; +        } +        #define if_lls_equal(name) else if(log_level_str == #name) this->level = uhd::_log::name +        if_lls_equal(always); +        if_lls_equal(often); +        if_lls_equal(regularly); +        if_lls_equal(rarely); +        if_lls_equal(very_rarely); +        if_lls_equal(never); +    } + +    //file stream and lock: +    std::ofstream _file_stream; +    ip::file_lock *_file_lock; +    boost::mutex _mutex; +}; + +UHD_SINGLETON_FCN(log_resource_type, log_rs); + +/*********************************************************************** + * The logger object implementation + **********************************************************************/ +//! get the relative file path from the host directory +static std::string get_rel_file_path(const fs::path &file){ +    fs::path abs_path = file.branch_path(); +    fs::path rel_path = file.leaf(); +    while (not abs_path.empty() and abs_path.leaf() != "host"){ +        rel_path = abs_path.leaf() / rel_path; +        abs_path = abs_path.branch_path(); +    } +    return rel_path.string(); +} + +struct uhd::_log::log::impl{ +    std::ostringstream ss; +    verbosity_t verbosity; +}; + +uhd::_log::log::log( +    const verbosity_t verbosity, +    const std::string &file, +    const unsigned int line, +    const std::string &function +){ +    _impl = UHD_PIMPL_MAKE(impl, ()); +    _impl->verbosity = verbosity; +    const std::string time = pt::to_simple_string(pt::microsec_clock::local_time()); +    const std::string header1 = str(boost::format("-- %s - level %d") % time % int(verbosity)); +    const std::string header2 = str(boost::format("-- %s") % function).substr(0, 80); +    const std::string header3 = str(boost::format("-- %s:%u") % get_rel_file_path(file) % line); +    const std::string border = std::string(std::max(std::max(header1.size(), header2.size()), header3.size()), '-'); +    _impl->ss +        << std::endl +        << border << std::endl +        << header1 << std::endl +        << header2 << std::endl +        << header3 << std::endl +        << border << std::endl +    ; +} + +uhd::_log::log::~log(void){ +    if (_impl->verbosity < log_rs().level) return; +    _impl->ss << std::endl; +    try{ +        log_rs().log_to_file(_impl->ss.str()); +    } +    catch(const std::exception &e){ +        /*! +         * Critical behavior below. +         * The following steps must happen in order to avoid a lock-up condition. +         * This is because the message facility will call into the logging facility. +         * Therefore we must disable the logger (level = never) before messaging. +         */ +        log_rs().level = never; +        UHD_MSG(error) +            << "Logging failed: " << e.what() << std::endl +            << "Logging has been disabled for this process" << std::endl +        ; +    } +} + +std::ostream & uhd::_log::log::operator()(void){ +    return _impl->ss; +} diff --git a/host/lib/utils/msg.cpp b/host/lib/utils/msg.cpp new file mode 100644 index 000000000..de98ada64 --- /dev/null +++ b/host/lib/utils/msg.cpp @@ -0,0 +1,128 @@ +// +// 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/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/foreach.hpp> +#include <boost/tokenizer.hpp> +#include <sstream> +#include <iostream> + +/*********************************************************************** + * Helper functions + **********************************************************************/ +#define tokenizer(inp, sep) \ +    boost::tokenizer<boost::char_separator<char> > \ +    (inp, boost::char_separator<char>(sep)) + +static void msg_to_cout(const std::string &msg){ +    std::stringstream ss; + +    static bool just_had_a_newline = true; +    BOOST_FOREACH(char ch, msg){ +        if (just_had_a_newline){ +            just_had_a_newline = false; +            ss << "-- "; +        } +        if (ch == '\n'){ +            just_had_a_newline = true; +        } +        ss << ch; +    } + +    std::cout << ss.str() << std::flush; +} + +static void msg_to_cerr(const std::string &title, const std::string &msg){ +    std::stringstream ss; + +    ss << std::endl << title << ":" << std::endl; +    BOOST_FOREACH(const std::string &line, tokenizer(msg, "\n")){ +        ss << "    " << line << std::endl; +    } + +    std::cerr << ss.str() << std::flush; +} + +/*********************************************************************** + * Global resources for the messenger + **********************************************************************/ +struct msg_resource_type{ +    boost::mutex mutex; +    uhd::msg::handler_t handler; +}; + +UHD_SINGLETON_FCN(msg_resource_type, msg_rs); + +/*********************************************************************** + * Setup the message handlers + **********************************************************************/ +void uhd::msg::register_handler(const handler_t &handler){ +    boost::mutex::scoped_lock lock(msg_rs().mutex); +    msg_rs().handler = handler; +} + +static void default_msg_handler(uhd::msg::type_t type, const std::string &msg){ +    switch(type){ +    case uhd::msg::fastpath: +        std::cerr << msg << std::flush; +        break; + +    case uhd::msg::status: +        msg_to_cout(msg); +        UHD_LOG << "Status message" << std::endl << msg; +        break; + +    case uhd::msg::warning: +        msg_to_cerr("UHD Warning", msg); +        UHD_LOG << "Warning message" << std::endl << msg; +        break; + +    case uhd::msg::error: +        msg_to_cerr("UHD Error", msg); +        UHD_LOG << "Error message" << std::endl << msg; +        break; +    } +} + +UHD_STATIC_BLOCK(msg_register_default_handler){ +    uhd::msg::register_handler(&default_msg_handler); +} + +/*********************************************************************** + * The message object implementation + **********************************************************************/ +struct uhd::msg::_msg::impl{ +    std::ostringstream ss; +    type_t type; +}; + +uhd::msg::_msg::_msg(const type_t type){ +    _impl = UHD_PIMPL_MAKE(impl, ()); +    _impl->type = type; +} + +uhd::msg::_msg::~_msg(void){ +    boost::mutex::scoped_lock lock(msg_rs().mutex); +    msg_rs().handler(_impl->type, _impl->ss.str()); +} + +std::ostream & uhd::msg::_msg::operator()(void){ +    return _impl->ss; +} diff --git a/host/lib/utils/paths.cpp b/host/lib/utils/paths.cpp new file mode 100644 index 000000000..a3dd377e5 --- /dev/null +++ b/host/lib/utils/paths.cpp @@ -0,0 +1,81 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/config.hpp> +#include <boost/tokenizer.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.hpp> +#include <cstdlib> +#include <string> +#include <vector> + +namespace fs = boost::filesystem; + +/*********************************************************************** + * Determine the paths separator + **********************************************************************/ +#ifdef UHD_PLATFORM_WIN32 +    static const std::string env_path_sep = ";"; +#else +    static const std::string env_path_sep = ":"; +#endif /*UHD_PLATFORM_WIN32*/ + +#define path_tokenizer(inp) \ +    boost::tokenizer<boost::char_separator<char> > \ +    (inp, boost::char_separator<char>(env_path_sep.c_str())) + +/*********************************************************************** + * Get a list of paths for an environment variable + **********************************************************************/ +static std::string get_env_var(const std::string &var_name, const std::string &def_val = ""){ +    const char *var_value_ptr = std::getenv(var_name.c_str()); +    return (var_value_ptr == NULL)? def_val : var_value_ptr; +} + +static std::vector<fs::path> get_env_paths(const std::string &var_name){ + +    std::string var_value = get_env_var(var_name); + +    //convert to filesystem path, filter blank paths +    std::vector<fs::path> paths; +    if (var_value.empty()) return paths; //FIXME boost tokenizer throws w/ blank strings on some platforms +    BOOST_FOREACH(const std::string &path_string, path_tokenizer(var_value)){ +        if (path_string.empty()) continue; +        paths.push_back(fs::system_complete(path_string)); +    } +    return paths; +} + +/*********************************************************************** + * Get a list of special purpose paths + **********************************************************************/ +static fs::path get_uhd_pkg_data_path(void){ +    return fs::path(get_env_var("UHD_PKG_DATA_PATH", UHD_PKG_DATA_PATH)); +} + +std::vector<fs::path> get_image_paths(void){ +    std::vector<fs::path> paths = get_env_paths("UHD_IMAGE_PATH"); +    paths.push_back(get_uhd_pkg_data_path() / "images"); +    return paths; +} + +std::vector<fs::path> get_module_paths(void){ +    std::vector<fs::path> paths = get_env_paths("UHD_MODULE_PATH"); +    paths.push_back(get_uhd_pkg_data_path() / "modules"); +    return paths; +} diff --git a/host/lib/utils/props.cpp b/host/lib/utils/props.cpp new file mode 100644 index 000000000..fc9f8e63f --- /dev/null +++ b/host/lib/utils/props.cpp @@ -0,0 +1,40 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/props.hpp> + +using namespace uhd; + +named_prop_t::named_prop_t( +    const wax::obj &key, +    const std::string &name +): +    key(key), +    name(name) +{ +    /* NOP */ +} + +named_prop_t named_prop_t::extract( +    const wax::obj &key, +    const std::string &name +){ +    if (key.type() == typeid(named_prop_t)){ +        return key.as<named_prop_t>(); +    } +    return named_prop_t(key, name); +} diff --git a/host/lib/utils/static.cpp b/host/lib/utils/static.cpp new file mode 100644 index 000000000..a0dea3372 --- /dev/null +++ b/host/lib/utils/static.cpp @@ -0,0 +1,32 @@ +// +// 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/utils/static.hpp> +#include <stdexcept> +#include <iostream> +_uhd_static_fixture::_uhd_static_fixture(void (*fcn)(void), const char *name){ +    try{ +        fcn(); +    } +    catch(const std::exception &e){ +        std::cerr << "Exception in static block " << name << std::endl; +        std::cerr << "  " << e.what() << std::endl; +    } +    catch(...){ +        std::cerr << "Exception in static block " << name << std::endl; +    } +} diff --git a/host/lib/utils/tasks.cpp b/host/lib/utils/tasks.cpp new file mode 100644 index 000000000..1f735de06 --- /dev/null +++ b/host/lib/utils/tasks.cpp @@ -0,0 +1,82 @@ +// +// 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/utils/tasks.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/barrier.hpp> +#include <exception> +#include <iostream> + +using namespace uhd; + +class task_impl : public task{ +public: + +    task_impl(const task_fcn_type &task_fcn): +        _spawn_barrier(2) +    { +        _thread_group.create_thread(boost::bind(&task_impl::task_loop, this, task_fcn)); +        _spawn_barrier.wait(); +    } + +    ~task_impl(void){ +        _running = false; +        _thread_group.interrupt_all(); +        _thread_group.join_all(); +    } + +private: + +    void task_loop(const task_fcn_type &task_fcn){ +        _running = true; +        _spawn_barrier.wait(); + +        try{ +            while (_running){ +                task_fcn(); +            } +        } +        catch(const boost::thread_interrupted &){ +            //this is an ok way to exit the task loop +        } +        catch(const std::exception &e){ +            do_error_msg(e.what()); +        } +        catch(...){ +            //FIXME +            //Unfortunately, this is also an ok way to end a task, +            //because on some systems boost throws uncatchables. +        } +    } + +    void do_error_msg(const std::string &msg){ +        UHD_MSG(error) +            << "An unexpected exception was caught in a task loop." << std::endl +            << "The task loop will now exit, things may not work." << std::endl +            << msg << std::endl +        ; +    } + +    boost::thread_group _thread_group; +    boost::barrier _spawn_barrier; +    bool _running; +}; + +task::sptr task::make(const task_fcn_type &task_fcn){ +    return task::sptr(new task_impl(task_fcn)); +} diff --git a/host/lib/utils/thread_priority.cpp b/host/lib/utils/thread_priority.cpp new file mode 100644 index 000000000..6d6ca5630 --- /dev/null +++ b/host/lib/utils/thread_priority.cpp @@ -0,0 +1,106 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <iostream> + +bool uhd::set_thread_priority_safe(float priority, bool realtime){ +    try{ +        set_thread_priority(priority, realtime); +        return true; +    }catch(const std::exception &e){ +        UHD_MSG(warning) << boost::format( +            "Unable to set the thread priority. Performance may be negatively affected.\n" +            "Please see the general application notes in the manual for instructions.\n" +            "%s\n" +        ) % e.what(); +        return false; +    } +} + +static void check_priority_range(float priority){ +    if (priority > +1.0 or priority < -1.0) +        throw uhd::value_error("priority out of range [-1.0, +1.0]"); +} + +/*********************************************************************** + * Pthread API to set priority + **********************************************************************/ +#ifdef HAVE_PTHREAD_SETSCHEDPARAM +    #include <pthread.h> + +    void uhd::set_thread_priority(float priority, bool realtime){ +        check_priority_range(priority); + +        //when realtime is not enabled, use sched other +        int policy = (realtime)? SCHED_RR : SCHED_OTHER; + +        //we cannot have below normal priority, set to zero +        if (priority < 0) priority = 0; + +        //get the priority bounds for the selected policy +        int min_pri = sched_get_priority_min(policy); +        int max_pri = sched_get_priority_max(policy); +        if (min_pri == -1 or max_pri == -1) throw uhd::os_error("error in sched_get_priority_min/max"); + +        //set the new priority and policy +        sched_param sp; +        sp.sched_priority = int(priority*(max_pri - min_pri)) + min_pri; +        int ret = pthread_setschedparam(pthread_self(), policy, &sp); +        if (ret != 0) throw uhd::os_error("error in pthread_setschedparam"); +    } +#endif /* HAVE_PTHREAD_SETSCHEDPARAM */ + +/*********************************************************************** + * Windows API to set priority + **********************************************************************/ +#ifdef HAVE_WIN_SETTHREADPRIORITY +    #include <windows.h> + +    void uhd::set_thread_priority(float priority, bool realtime){ +        check_priority_range(priority); + +        //set the priority class on the process +        int pri_class = (realtime)? REALTIME_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS; +        if (SetPriorityClass(GetCurrentProcess(), pri_class) == 0) +            throw uhd::os_error("error in SetPriorityClass"); + +        //scale the priority value to the constants +        int priorities[] = { +            THREAD_PRIORITY_IDLE, THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_NORMAL, +            THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST, THREAD_PRIORITY_TIME_CRITICAL +        }; +        size_t pri_index = size_t((priority+1.0)*6/2.0); // -1 -> 0, +1 -> 6 + +        //set the thread priority on the thread +        if (SetThreadPriority(GetCurrentThread(), priorities[pri_index]) == 0) +            throw uhd::os_error("error in SetThreadPriority"); +    } +#endif /* HAVE_WIN_SETTHREADPRIORITY */ + +/*********************************************************************** + * Unimplemented API to set priority + **********************************************************************/ +#ifdef HAVE_LOAD_MODULES_DUMMY +    void uhd::set_thread_priority(float, bool){ +        throw uhd::not_implemented_error("set thread priority not implemented"); +    } + +#endif /* HAVE_LOAD_MODULES_DUMMY */ diff --git a/host/lib/version.cpp b/host/lib/version.cpp new file mode 100644 index 000000000..cfd198a08 --- /dev/null +++ b/host/lib/version.cpp @@ -0,0 +1,37 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/version.hpp> +#include <uhd/utils/static.hpp> +#include <boost/version.hpp> +#include <iostream> + +#ifndef UHD_DONT_PRINT_SYSTEM_INFO +UHD_STATIC_BLOCK(print_system_info){ +    std::cout +        << BOOST_PLATFORM << "; " +        << BOOST_COMPILER << "; " +        << "Boost_" << BOOST_VERSION << "; " +        << "UHD_" << UHD_VERSION_STRING +        << std::endl << std::endl +    ; +} +#endif + +std::string uhd::get_version_string(void){ +    return UHD_VERSION_STRING; +} diff --git a/host/lib/wax.cpp b/host/lib/wax.cpp new file mode 100644 index 000000000..5f658acd8 --- /dev/null +++ b/host/lib/wax.cpp @@ -0,0 +1,151 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/wax.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <stdexcept> + +/*! + * The link args for internal use within this cpp file: + * + * It contains a link (in this case a pointer) to a wax object. + * Only the methods in this file may create or parse link args. + * The get_link method is the creator of a link args object. + * The [] operator will resolve the link and make the [] call. + * + * TODO: register the link args with the wax obj that it links to. + * That way, if the obj destructs, the link can be invalidated. + * The operator() will throw, rather than dereferencing bad memory. + */ +class link_args_t{ +public: +    link_args_t(const wax::obj *obj_ptr) : _obj_ptr(obj_ptr){ +        /* NOP */ +    } +    wax::obj & operator()(void) const{ +        //recursively resolve link args to get at original pointer +        if (_obj_ptr->type() == typeid(link_args_t)){ +            return _obj_ptr->as<link_args_t>()(); +        } +        return *const_cast<wax::obj *>(_obj_ptr); +    } +private: +    const wax::obj *_obj_ptr; +}; + +/*! + * The proxy args for internal use within this cpp file: + * + * It contains a link and a key for setting/getting a property. + * Only the methods in this file may create or parse proxy args. + * Class methods have special handling for the case when the + * wax obj contains an instance of the proxy args. + */ +class proxy_args_t{ +public: +    proxy_args_t(const wax::obj *obj_ptr, const wax::obj &key) : _key(key){ +        _obj_link = obj_ptr->get_link(); +    } +    wax::obj & operator()(void) const{ +        return _obj_link.as<link_args_t>()(); +    } +    const wax::obj & key(void) const{ +        return _key; +    } +private: +    wax::obj _obj_link; +    const wax::obj _key; +}; + +/*********************************************************************** + * Structors + **********************************************************************/ +wax::obj::obj(void){ +    /* NOP */ +} + +wax::obj::obj(const obj &o){ +    _contents = o._contents; +} + +wax::obj::~obj(void){ +    /* NOP */ +} + +/*********************************************************************** + * Special Operators + **********************************************************************/ +wax::obj wax::obj::operator[](const obj &key){ +    if (_contents.type() == typeid(proxy_args_t)){ +        obj val = resolve(); +        //check if its a special link and call +        if (val.type() == typeid(link_args_t)){ +            return val.as<link_args_t>()()[key]; +        } +        //unknown obj +        throw uhd::type_error("cannot use [] on non wax::obj link"); +    } +    else{ +        return proxy_args_t(this, key); +    } +} + +wax::obj & wax::obj::operator=(const obj &val){ +    if (_contents.type() == typeid(proxy_args_t)){ +        proxy_args_t proxy_args = boost::any_cast<proxy_args_t>(_contents); +        proxy_args().set(proxy_args.key(), val); +    } +    else{ +        _contents = val._contents; +    } +    return *this; +} + +/*********************************************************************** + * Public Methods + **********************************************************************/ +wax::obj wax::obj::get_link(void) const{ +    return link_args_t(this); +} + +const std::type_info & wax::obj::type(void) const{ +    return resolve().type(); +} + +/*********************************************************************** + * Private Methods + **********************************************************************/ +boost::any wax::obj::resolve(void) const{ +    if (_contents.type() == typeid(proxy_args_t)){ +        obj val; +        proxy_args_t proxy_args = boost::any_cast<proxy_args_t>(_contents); +        proxy_args().get(proxy_args.key(), val); +        return val.resolve(); +    } +    else{ +        return _contents; +    } +} + +void wax::obj::get(const obj &, obj &){ +    throw uhd::type_error("Cannot call get on wax obj base class"); +} + +void wax::obj::set(const obj &, const obj &){ +    throw uhd::type_error("Cannot call set on wax obj base class"); +}  | 
