diff options
Diffstat (limited to 'host/lib')
| -rw-r--r-- | host/lib/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/lib/usrp/common/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | host/lib/usrp/common/ad9361_ctrl.cpp | 8 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/CMakeLists.txt | 15 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp | 162 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp | 26 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/neon_bands.cpp | 157 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/neon_constants.hpp | 51 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp | 621 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp | 311 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp | 495 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/neon/neon_regs.hpp | 26 | ||||
| -rw-r--r-- | host/lib/usrp/mpmd/mpmd_devices.hpp | 3 | ||||
| -rw-r--r-- | host/lib/usrp/mpmd/mpmd_image_loader.cpp | 5 | 
15 files changed, 1875 insertions, 12 deletions
| diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index 984f16f96..cbc022533 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -75,6 +75,7 @@ LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF)  LIBUHD_REGISTER_COMPONENT("N230" ENABLE_N230 ON "ENABLE_LIBUHD" OFF OFF)  LIBUHD_REGISTER_COMPONENT("MPMD" ENABLE_MPMD ON "ENABLE_LIBUHD" OFF OFF)  LIBUHD_REGISTER_COMPONENT("N300" ENABLE_N300 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("E320" ENABLE_E320 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF)  LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF)  ######################################################################## diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 082a047e7..feb4390e3 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -14,14 +14,14 @@ IF(ENABLE_USB)      )  ENDIF(ENABLE_USB) -IF(ENABLE_E300 OR ENABLE_B200 OR ENABLE_N230) +IF(ENABLE_E300 OR ENABLE_B200 OR ENABLE_N230 OR ENABLE_E320)      INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver")      LIBUHD_APPEND_SOURCES(          ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp          ${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp          ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp      ) -ENDIF(ENABLE_E300 OR ENABLE_B200 OR ENABLE_N230) +ENDIF(ENABLE_E300 OR ENABLE_B200 OR ENABLE_N230 OR ENABLE_E320)  INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/host/lib/usrp/common/ad9361_ctrl.cpp b/host/lib/usrp/common/ad9361_ctrl.cpp index 3cf89ad7a..003711d52 100644 --- a/host/lib/usrp/common/ad9361_ctrl.cpp +++ b/host/lib/usrp/common/ad9361_ctrl.cpp @@ -212,14 +212,6 @@ public:          _device.set_dc_offset_auto(direction,on);      } -    std::string get_lo_source(const std::string &which) -    { -        const auto dir = _get_direction_from_antenna(which); -        //TODO: Implement this -        boost::lock_guard<boost::mutex> lock(_mutex); -        return "internal"; -    } -      void set_iq_balance_auto(const std::string &which, const bool on)      {          boost::lock_guard<boost::mutex> lock(_mutex); diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 83bb0a96c..8ebe8e642 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -52,3 +52,5 @@ ENDIF(ENABLE_N300)  IF(ENABLE_MPMD AND ENABLE_EISCAT)      INCLUDE_SUBDIRECTORY(eiscat)  ENDIF(ENABLE_MPMD AND ENABLE_EISCAT) + +INCLUDE_SUBDIRECTORY(neon) diff --git a/host/lib/usrp/dboard/neon/CMakeLists.txt b/host/lib/usrp/dboard/neon/CMakeLists.txt new file mode 100644 index 000000000..e1ee0fba4 --- /dev/null +++ b/host/lib/usrp/dboard/neon/CMakeLists.txt @@ -0,0 +1,15 @@ +# +# Copyright 2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +IF(ENABLE_E320) +    LIST(APPEND NEON_SOURCES +        ${CMAKE_CURRENT_SOURCE_DIR}/neon_radio_ctrl_impl.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/neon_radio_ctrl_init.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/neon_ad9361_iface.cpp +        ${CMAKE_CURRENT_SOURCE_DIR}/neon_bands.cpp +    ) +    LIBUHD_APPEND_SOURCES(${NEON_SOURCES}) +ENDIF(ENABLE_E320) diff --git a/host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp b/host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp new file mode 100644 index 000000000..e19f890ca --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_ad9361_iface.cpp @@ -0,0 +1,162 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "neon_ad9361_iface.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <boost/format.hpp> + +using namespace uhd; + +class neon_ad9361_iface : public ad9361_ctrl +{ +public: +    neon_ad9361_iface( +        rpc_client::sptr rpcc +    ) : _rpcc(rpcc) +    , _rpc_prefix("db_0_") +    , _log_prefix("AD9361") +    { +        UHD_LOG_TRACE(_log_prefix, +            "Initialized controls with RPC prefix " << _rpc_prefix); +    } + +    double set_bw_filter(const std::string &which, const double bw) +    { +        return _rpcc->request_with_token<double>( +            this->_rpc_prefix + "set_bw_filter", which, bw); +    } + +    double set_gain(const std::string &which, const double value) +    { +        return _rpcc->request_with_token<double>( +            this->_rpc_prefix + "set_gain", which, value); +    } + +    void set_agc(const std::string &which, bool enable) +    { +        _rpcc->request_with_token<void>( +            this->_rpc_prefix + "set_agc", which, enable); +    } + +    void set_agc_mode(const std::string &which, const std::string &mode) +    { +        _rpcc->request_with_token<void>( +            this->_rpc_prefix + "set_agc_mode", which, mode); +    } + +    double set_clock_rate(const double rate) +    { +        return _rpcc->request_with_token<double>( +            this->_rpc_prefix + "set_clock_rate", rate); +    } + +    void set_active_chains(bool tx1, bool tx2, bool rx1, bool rx2) +    { +        _rpcc->request_with_token<void>( +            this->_rpc_prefix + "set_active_chains", tx1, tx2, rx1, rx2); +    } + +    double tune(const std::string &which, const double value) +    { +        return _rpcc->request_with_token<double>( +            this->_rpc_prefix + "tune", which, value); +    } + +    void set_dc_offset_auto(const std::string &which, const bool on) +    { +        _rpcc->request_with_token<void>( +            this->_rpc_prefix + "set_dc_offset_auto", which, on); +    } + +    void set_timing_mode(const std::string &timing_mode) +    { +        _rpcc->request_with_token<void>( +            this->_rpc_prefix + "set_timing_mode", timing_mode); +    } + +    void set_iq_balance_auto(const std::string &which, const bool on) +    { +        _rpcc->request_with_token<void>( +            this->_rpc_prefix + "set_iq_balance_auto", which, on); +    } + +    double get_freq(const std::string &which) +    { +        return _rpcc->request_with_token<double>( +            this->_rpc_prefix + "get_freq", which); +    } + +    void data_port_loopback(const bool on) +    { +        _rpcc->request_with_token<void>( +            this->_rpc_prefix + "data_port_loopback", on); +    } + +    sensor_value_t get_rssi(const std::string &which) +    { +        return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( +            this->_rpc_prefix + "get_rssi", which)); +    } + +    sensor_value_t get_temperature() +    { +        return sensor_value_t(_rpcc->request_with_token<sensor_value_t::sensor_map_t>( +            this->_rpc_prefix + "get_temperature")); +    } + +    std::vector<std::string> get_filter_names(const std::string &which) +    { +        return _rpcc->request_with_token<std::vector<std::string>>( +            this->_rpc_prefix + "get_filter_names", which); +    } + +    filter_info_base::sptr get_filter(const std::string &/*which*/, +                                                        const std::string &/*filter_name*/) +    { +        throw uhd::runtime_error("ad9361_ctrl::get_filter is not supported over an RPC connection"); +    } + +    void set_filter(const std::string &/*which*/, +                                    const std::string &/*filter_name*/, +                                    const filter_info_base::sptr /*filter*/) +    { +        throw uhd::runtime_error("ad9361_ctrl::set_filter is not supported over an RPC connection"); +    } + +    void output_digital_test_tone(bool enb) +    { +        _rpcc->request_with_token<void>( +            this->_rpc_prefix + "output_digital_test_tone", enb); +    } + +private: +    //! Reference to the RPC client +    rpc_client::sptr _rpcc; + +    //! Stores the prefix to RPC calls +    const std::string _rpc_prefix; + +    //! Logger prefix +    const std::string _log_prefix; +}; + +//! Factory function for Neon's AD9361 RPC Controller +ad9361_ctrl::sptr make_rpc(rpc_client::sptr rpcc) +{ +    return ad9361_ctrl::sptr(new neon_ad9361_iface(rpcc)); +} + +/*! Helper function to convert direction and channel to the 'which' required by most Catalina +    driver functions */ +std::string get_which_ad9361_chain(const direction_t dir, const size_t chan) +{ +    UHD_ASSERT_THROW(dir == RX_DIRECTION or dir == TX_DIRECTION); +    UHD_ASSERT_THROW(chan == 0 or chan == 1); +    return str(boost::format("%s%d") +            % (dir == RX_DIRECTION ? "RX" : "TX") +            % (chan + 1)); +} diff --git a/host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp b/host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp new file mode 100644 index 000000000..1f055aca4 --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_ad9361_iface.hpp @@ -0,0 +1,26 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_NEON_AD9361_IFACE_HPP +#define INCLUDED_LIBUHD_RFNOC_NEON_AD9361_IFACE_HPP + +#include <uhd/types/direction.hpp> +#include <uhd/types/filters.hpp> +#include <uhd/types/sensors.hpp> +#include <uhdlib/usrp/common/ad9361_ctrl.hpp> +#include <uhdlib/utils/rpc.hpp> +#include <memory> +#include <string> +#include <vector> + +using namespace uhd; +using namespace uhd::usrp; + +ad9361_ctrl::sptr make_rpc(rpc_client::sptr rpcc); +std::string get_which_ad9361_chain(const direction_t dir, const size_t chan); + +#endif /* INCLUDED_LIBUHD_RFNOC_NEON_AD9361_IFACE_HPP */ +// vim: sw=4 et: diff --git a/host/lib/usrp/dboard/neon/neon_bands.cpp b/host/lib/usrp/dboard/neon/neon_bands.cpp new file mode 100644 index 000000000..3b0ab2ca1 --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_bands.cpp @@ -0,0 +1,157 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "neon_radio_ctrl_impl.hpp" +#include "neon_constants.hpp" +#include <uhd/utils/math.hpp> + +/* + * Neon frequency bands: + * + * For RX: + *     Band   Freq      BSEL-210 BSEL-543         TX/RX                RX2 + *                                         SEL_10   |   FE_SEL_210    SEL_10 + *                                                   chan 1 | chan 2 + *     ------------------------------------------------------------------------- + *     LB_B2: < 450     RF5 100  RF6 101    01       RF3 100  RF1 001   10 + *     LB_B3: 450-700   RF6 101  RF5 100    01       RF3 100  RF1 001   10 + *     LB_B4: 700-1200  RF3 010  RF4 011    01       RF3 100  RF1 001   10 + *     LB_B5: 1200-1800 RF4 011  RF3 010    01       RF3 100  RF1 001   10 + *     LB_B6: 1800-2350 RF1 000  RF2 001    01       RF3 100  RF1 001   10 + *     LB_B7: 2350-2600 RF2 001  RF1 000    01       RF3 100  RF1 001   10 + *     HB:    2600+     --- 111  --- 111    10       RF3 100  RF1 001   01 + * + *     SEL_1 SEL_0 + *      1    0    ANT1(RX_HB)--TX/RX, ANT2(RX_LB)--RX + *      0    1    ANT1(RX_HB)--RX, ANT2(RX_LB)--TX/RX + * + * For TX: + *     Band-Freq              BSEL-210 BSEL-543       TX/RX + *                                                 FE_SEL_210 + *                                               chan 1 | chan 2 + *     ------------------------------------------------------------ + *     LB_80   < 117.7        RF7 011  RF8 111   RF1 001   RF3 100 + *     LB_160  117.7-178.2    RF8 111  RF7 011   RF1 001   RF3 100 + *     LB_225  178.2-284.3    RF5 001  RF6 101   RF1 001   RF3 100 + *     LB_400  284.3-453.7    RF6 101  RF5 001   RF1 001   RF3 100 + *     LB_575  453.7-723.8    RF3 010  RF4 110   RF1 001   RF3 100 + *     LB_1000 723.8-1154.9   RF4 110  RF3 010   RF1 001   RF3 100 + *     LB_1700 1154.9-1842.6  RF1 000  RF2 100   RF1 001   RF3 100 + *     LB_2750 1842.6-2940.0  RF2 100  RF1 000   RF1 001   RF3 100 + *     HB_5850 > 2490.0       --- ---  --- ---   RF2 010   RF2 010 + * + */ + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +namespace { +    /* Note on the RX filter bank: +     * +     * The RX path has 7 bands, which we call LB_B2, B3, .. HB same as +     * the schematic. +     * +     * The following constants define lower cutoff frequencies for each band. +     * LB_B2 does not have a lower cutoff frequency, it is implied by +     * AD9361_MIN_FREQ. NEON_RX_BAND1_MIN_FREQ is the cutover frequency +     * for switching from LB_B2 to LB_B3, and so on. +     * +     * Bands 1-6 have both high- and low-pass filters (effectively band +     * passes). Frequencies need to be chosen to allow as much of the full +     * bandwidth through unattenuated. +     */ +    constexpr double NEON_RX_LB_BAND3_MIN_FREQ = 450e6; +    constexpr double NEON_RX_LB_BAND4_MIN_FREQ = 700e6; +    constexpr double NEON_RX_LB_BAND5_MIN_FREQ = 1200e6; +    constexpr double NEON_RX_LB_BAND6_MIN_FREQ = 1800e6; +    constexpr double NEON_RX_LB_BAND7_MIN_FREQ = 2350e6; +    constexpr double NEON_RX_HB_MIN_FREQ = 2600e6; + +    /* Note on the TX filter bank: +     * +     * The TX path has 9 bands, which we name according to the schematic. +     * +     * The following constants define lower cutoff frequencies for each band. +     * LB_80 does not have a lower cutoff frequency, it is implied by +     * AD9361_MIN_FREQ. NEON_TX_LB_160_MIN_FREQ is the cutover frequency +     * for switching from LB_80 to LB_160, and so on. +     * +     * On current Neon revisions, all filters on the TX filter bank are +     * low pass filters (no high pass filters). +     * Frequencies need to be chosen to allow as much of the full bandwidth +     * through unattenuated (so don't go all the way up to the cutoff frequency +     * of that filter). +     */ +    constexpr double NEON_TX_LB_160_MIN_FREQ = 117.7e6; +    constexpr double NEON_TX_LB_225_MIN_FREQ = 178.2e6; +    constexpr double NEON_TX_LB_400_MIN_FREQ = 284.3e6; +    constexpr double NEON_TX_LB_575_MIN_FREQ = 453.7e6; +    constexpr double NEON_TX_LB_1000_MIN_FREQ = 723.8e6; +    constexpr double NEON_TX_LB_1700_MIN_FREQ = 1154.9e6; +    constexpr double NEON_TX_LB_2750_MIN_FREQ = 1842.6e6; +    constexpr double NEON_TX_HB_MIN_FREQ = 2940.0e6; +} + +neon_radio_ctrl_impl::rx_band +neon_radio_ctrl_impl::_map_freq_to_rx_band(const double freq) { +    neon_radio_ctrl_impl::rx_band band; + +    if (fp_compare_epsilon<double>(freq) < AD9361_MIN_FREQ) { +        band = rx_band::INVALID_BAND; +    } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND3_MIN_FREQ) { +        band = rx_band::LB_B2; +    } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND4_MIN_FREQ) { +        band = rx_band::LB_B3; +    } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND5_MIN_FREQ) { +        band = rx_band::LB_B4; +    } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND6_MIN_FREQ) { +        band = rx_band::LB_B5; +    } else if (fp_compare_epsilon<double>(freq) < NEON_RX_LB_BAND7_MIN_FREQ) { +        band = rx_band::LB_B6; +    } else if (fp_compare_epsilon<double>(freq) < NEON_RX_HB_MIN_FREQ) { +        band = rx_band::LB_B7; +    } else if (fp_compare_epsilon<double>(freq) <= AD9361_MAX_FREQ) { +        band = rx_band::HB; +    } else { +        band = rx_band::INVALID_BAND; +    } + +    return band; +} + +neon_radio_ctrl_impl::tx_band +neon_radio_ctrl_impl::_map_freq_to_tx_band(const double freq) { +    neon_radio_ctrl_impl::tx_band band; + +    if (fp_compare_epsilon<double>(freq) < AD9361_MIN_FREQ) { +        band = tx_band::INVALID_BAND; +    } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_160_MIN_FREQ) { +        band = tx_band::LB_80; +    } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_225_MIN_FREQ) { +        band = tx_band::LB_160; +    } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_400_MIN_FREQ) { +        band = tx_band::LB_225; +    } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_575_MIN_FREQ) { +        band = tx_band::LB_400; +    } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_1000_MIN_FREQ) { +        band = tx_band::LB_575; +    } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_1700_MIN_FREQ) { +        band = tx_band::LB_1000; +    } else if (fp_compare_epsilon<double>(freq) < NEON_TX_LB_2750_MIN_FREQ) { +        band = tx_band::LB_1700; +    } else if (fp_compare_epsilon<double>(freq) < NEON_TX_HB_MIN_FREQ) { +        band = tx_band::LB_2750; +    } else if (fp_compare_epsilon<double>(freq) <= AD9361_MAX_FREQ) { +        band = tx_band::HB; +    } else { +        band = tx_band::INVALID_BAND; +    } + +    return band; +} + diff --git a/host/lib/usrp/dboard/neon/neon_constants.hpp b/host/lib/usrp/dboard/neon/neon_constants.hpp new file mode 100644 index 000000000..dc560ca93 --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_constants.hpp @@ -0,0 +1,51 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_NEON_CONSTANTS_HPP +#define INCLUDED_LIBUHD_NEON_CONSTANTS_HPP + +#include <vector> +#include <string> +#include <cstddef> + +static constexpr size_t FPGPIO_MASTER_RADIO = 0; +static constexpr size_t TOTAL_RADIO_PORTS = 2; +static constexpr double AD9361_RX_MIN_BANDWIDTH = 20.0e6;  // HZ +static constexpr double AD9361_RX_MAX_BANDWIDTH = 40.0e6; // HZ + +static constexpr double AD9361_TX_MIN_BANDWIDTH = 20.0e6;  // HZ +static constexpr double AD9361_TX_MAX_BANDWIDTH = 40.0e6; // HZ + +static constexpr double AD9361_MIN_FREQ = 70.0e6; // Hz +static constexpr double AD9361_MAX_FREQ = 6.0e9; // Hz + +static constexpr double NEON_RADIO_RATE = 16e6; // Hz + +static constexpr double AD9361_MIN_RX_GAIN = 0.0; // dB +static constexpr double AD9361_MAX_RX_GAIN = 76; // dB +static constexpr double AD9361_RX_GAIN_STEP = 1.0; // dB +static constexpr double AD9361_MIN_TX_GAIN = 0.0; // dB +static constexpr double AD9361_MAX_TX_GAIN = 89.75; // dB +static constexpr double AD9361_TX_GAIN_STEP = 0.25; // dB + + +static constexpr bool NEON_DEFAULT_AUTO_DC_OFFSET = true; +static constexpr bool NEON_DEFAULT_AUTO_IQ_BALANCE = true; +static constexpr bool NEON_DEFAULT_AGC_ENABLE = false; + +static constexpr double NEON_DEFAULT_GAIN = 0.0; +static constexpr double NEON_DEFAULT_FREQ = 2.4e9; // Hz +static constexpr double NEON_DEFAULT_BANDWIDTH = 40e6;  // Hz +static constexpr char   NEON_DEFAULT_RX_ANTENNA[] = "RX2"; +static constexpr char   NEON_DEFAULT_TX_ANTENNA[] = "TX/RX"; + +static const std::vector<std::string> NEON_RX_ANTENNAS = { +    "TX/RX", "RX2" +}; + +static constexpr size_t NEON_NUM_CHANS = 2; + +#endif /* INCLUDED_LIBUHD_NEON_CONSTANTS_HPP */ diff --git a/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp b/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp new file mode 100644 index 000000000..a753a447c --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.cpp @@ -0,0 +1,621 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "neon_radio_ctrl_impl.hpp" +#include "neon_constants.hpp" +#include "neon_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/math.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/exception.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/make_shared.hpp> +#include <boost/format.hpp> +#include <sstream> +#include <cmath> +#include <cstdlib> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::math::fp_compare; + +/****************************************************************************** + * Structors + *****************************************************************************/ +UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(neon_radio_ctrl) +{ +    UHD_LOG_TRACE(unique_id(), "Entering neon_radio_ctrl_impl ctor..."); +    const char radio_slot_name[1] = {'A'}; +    _radio_slot = radio_slot_name[get_block_id().get_block_count()]; +    UHD_LOG_TRACE(unique_id(), "Radio slot: " << _radio_slot); +    _rpc_prefix = "db_0_"; + +    _init_defaults(); +    _init_peripherals(); +    _init_prop_tree(); +} + +neon_radio_ctrl_impl::~neon_radio_ctrl_impl() +{ +    UHD_LOG_TRACE(unique_id(), "neon_radio_ctrl_impl::dtor() "); +} + + +/****************************************************************************** + * API Calls + *****************************************************************************/ + +bool neon_radio_ctrl_impl::check_radio_config() +{ +    // mapping of frontend to radio perif index +    static const size_t FE0 = 0; +    static const size_t FE1 = 1; +    const size_t num_rx = _is_streamer_active(RX_DIRECTION, FE0) + _is_streamer_active(RX_DIRECTION, FE1); +    const size_t num_tx = _is_streamer_active(TX_DIRECTION, FE0) + _is_streamer_active(TX_DIRECTION, FE1); + +    //setup the active chains in the codec +    if ((num_rx + num_tx) == 0) { +        // Ensure at least one RX chain is enabled so AD9361 outputs a sample clock +        this->set_streaming_mode(false, false, true, false); +    } else { +        this->set_streaming_mode( +                _is_streamer_active(TX_DIRECTION, FE0), +                _is_streamer_active(TX_DIRECTION, FE1), +                _is_streamer_active(RX_DIRECTION, FE0), +                _is_streamer_active(RX_DIRECTION, FE1) +        ); +    } +    return true; +} + +void neon_radio_ctrl_impl::set_streaming_mode(const bool tx1, const bool tx2, const bool rx1, const bool rx2) +{ +    UHD_LOG_TRACE(unique_id(), "Setting up streaming ...") +    const size_t num_rx = rx1 + rx2; +    const size_t num_tx = tx1 + tx2; + +    //setup the active chains in the codec +    _ad9361->set_active_chains(tx1, tx2, rx1, rx2); + +    const std::string TIMING_MODE_2R2T = "2R2T"; +    const std::string TIMING_MODE_1R1T = "1R1T"; +    const std::string MIMO = "MIMO";         // 2R2T +    const std::string SISO_TX1 = "SISO_TX1"; // 1R1T +    const std::string SISO_TX0 = "SISO_TX0"; // 1R1T +    // setup 1R1T/2R2T mode in catalina and fpga +    // The Catalina interface in the fpga needs to know which TX channel to use for +    // the data on the LVDS lines. +    if ((num_rx == 2) or (num_tx == 2)) { +        // AD9361 is in 2R2T mode +        _ad9361->set_timing_mode(TIMING_MODE_2R2T); +        this->set_channel_mode(MIMO); +    } else { +        // AD9361 is in 1R1T mode +        _ad9361->set_timing_mode(TIMING_MODE_1R1T); + +        // Set to SIS0_TX1 if we're using the second TX antenna, otherwise +        // default to SISO_TX0 +        this->set_channel_mode(tx2 ? SISO_TX1 : SISO_TX0); +    } +} + +void neon_radio_ctrl_impl::set_channel_mode(const std::string &channel_mode) +{ +    //MIMO for 2R2T mode for 2 channels +    //SISO_TX1 for 1R1T mode for 1 channel - TX1 +    //SISO_TX0 for 1R1T mode for 1 channel - TX0 + +    _rpcc->request_with_token<void>("set_channel_mode", channel_mode); +} + +/*  loopback_self_test checks the integrity of the FPGA->AD936x->FPGA sample interface. +    The AD936x is put in loopback mode that sends the TX data unchanged to the RX side. +    A test value is written to the codec_idle register in the TX side of the radio. +    The readback register is then used to capture the values on the TX and RX sides +    simultaneously for comparison. It is a reasonably effective test for AC timing +    since I/Q Ch0/Ch1 alternate over the same wires. Note, however, that it uses +    whatever timing is configured at the time the test is called rather than select +    worst case conditions to stress the interface. +    Note: This currently only tests 2R2T mode +*/ +void neon_radio_ctrl_impl::loopback_self_test( +        std::function<void(uint32_t)> poker_functor, +        std::function<uint64_t()> peeker_functor +) { +    // Set 2R2T mode, stream on all channels +    this->set_streaming_mode(true, true, true, true); +    // Set maximum rate for 2R2T mode +    this->set_rate(30.72e6); +    // Put AD936x in loopback mode +    _ad9361->data_port_loopback(true); +    UHD_LOG_INFO(unique_id(), "Performing CODEC loopback test... "); +    size_t hash = size_t(time(NULL)); +    constexpr size_t loopback_count = 100; + +    // Allow some time for AD936x to enter loopback mode. +    // There is no clear statement in the documentation of how long it takes, +    // but UG-570 does say to "allow six ADC_CLK/64 clock cycles of flush time" +    // when leaving the TX or RX states.  That works out to ~75us at the +    // minimum clock rate of 5 MHz, which lines up with test results. +    // Sleeping 1ms is far more than enough. +    std::this_thread::sleep_for(std::chrono::milliseconds(1)); + +    for (size_t i = 0; i < loopback_count; i++) +    { +        // Create test word +        boost::hash_combine(hash, i); +        const uint32_t word32 = uint32_t(hash) & 0xfff0fff0; +        //const uint32_t word32 = 0xCA00C100; +        // Write test word to codec_idle idle register (on TX side) +        poker_functor(word32); + +        // Read back values - TX is lower 32-bits and RX is upper 32-bits +        const uint64_t rb_word64 = peeker_functor(); +        const uint32_t rb_tx = uint32_t(rb_word64 >> 32); +        const uint32_t rb_rx = uint32_t(rb_word64 & 0xffffffff); + +        // Compare TX and RX values to test word +        bool test_fail = word32 != rb_tx or word32 != rb_rx; +        if(test_fail) +        { +            UHD_LOG_WARNING(unique_id(), "CODEC loopback test failed! " +              << boost::format("Expected: 0x%08X Received (TX/RX): 0x%08X/0x%08X") +                  % word32 % rb_tx % rb_rx); +            throw uhd::runtime_error("CODEC loopback test failed."); +        } +    } +    UHD_LOG_INFO(unique_id(), "CODEC loopback test passed"); + +    // Zero out the idle data. +    poker_functor(0); + +    // Take AD936x out of loopback mode +    _ad9361->data_port_loopback(false); +    this->set_streaming_mode(true, false, true, false); +} + +double neon_radio_ctrl_impl::set_rate(const double rate) +{ +    std::lock_guard<std::mutex> l(_set_lock); +    UHD_LOG_DEBUG(unique_id(), "Asking for clock rate " << rate/1e6 << " MHz\n"); +    double actual_tick_rate = _ad9361->set_clock_rate(rate); +    UHD_LOG_DEBUG(unique_id(), "Actual clock rate " << actual_tick_rate/1e6 << " MHz\n"); + +    radio_ctrl_impl::set_rate(rate); +    return rate; +} + +void neon_radio_ctrl_impl::set_tx_antenna( +        const std::string &ant, +        const size_t chan +) { +    if (ant != get_tx_antenna(chan)) { +        throw uhd::value_error(str( +            boost::format("[%s] Requesting invalid TX antenna value: %s") +            % unique_id() +            % ant +        )); +    } +    // We can't actually set the TX antenna, so let's stop here. +} + +void neon_radio_ctrl_impl::set_rx_antenna( +        const std::string &ant, +        const size_t chan +) { +    UHD_ASSERT_THROW(chan <= NEON_NUM_CHANS); +    if (std::find(NEON_RX_ANTENNAS.begin(), +                  NEON_RX_ANTENNAS.end(), +                  ant) == NEON_RX_ANTENNAS.end()) { +        throw uhd::value_error(str( +            boost::format("[%s] Requesting invalid RX antenna value: %s") +            % unique_id() +            % ant +        )); +    } +    UHD_LOG_TRACE(unique_id(), +        "Setting RX antenna to " << ant << " for chan " << chan); + +    _set_atr_bits(chan, radio_ctrl_impl::get_rx_frequency(chan), ant); +    radio_ctrl_impl::set_rx_antenna(ant, chan); +} + +double neon_radio_ctrl_impl::set_tx_frequency( +        const double freq, +        const size_t chan +) { +    UHD_LOG_TRACE(unique_id(), +        "set_tx_frequency(f=" << freq << ", chan=" << chan << ")"); +    std::lock_guard<std::mutex> l(_set_lock); + +    double coerced_freq = _ad9361->tune( +        get_which_ad9361_chain(TX_DIRECTION, chan), +        freq); +    radio_ctrl_impl::set_tx_frequency(coerced_freq, chan); +    // Antenna settings +    auto ant = get_tx_antenna(chan); +    // Front-end switching +    _set_atr_bits(chan, freq, ant); + +    return coerced_freq; +} + +double neon_radio_ctrl_impl::set_rx_frequency( +        const double freq, +        const size_t chan +) { +    UHD_LOG_TRACE(unique_id(), +        "set_rx_frequency(f=" << freq << ", chan=" << chan << ")"); +    std::lock_guard<std::mutex> l(_set_lock); + +    double coerced_freq = _ad9361->tune( +        get_which_ad9361_chain(RX_DIRECTION, chan), +        freq); +    radio_ctrl_impl::set_rx_frequency(coerced_freq, chan); +    // Antenna settings +    auto ant = get_rx_antenna(chan); +    // Front-end switching +    _set_atr_bits(chan, coerced_freq, ant); + +    return coerced_freq; +} + +double neon_radio_ctrl_impl::set_rx_bandwidth( +        const double bandwidth, +        const size_t chan +) { +    std::lock_guard<std::mutex> l(_set_lock); +    double clipped_bw = _ad9361->set_bw_filter( +        get_which_ad9361_chain(RX_DIRECTION, chan), +        bandwidth); +    return radio_ctrl_impl::set_rx_bandwidth(clipped_bw, chan); +} + +double neon_radio_ctrl_impl::set_tx_bandwidth( +        const double bandwidth, +        const size_t chan +) { +    std::lock_guard<std::mutex> l(_set_lock); +    double clipped_bw = _ad9361->set_bw_filter( +        get_which_ad9361_chain(TX_DIRECTION, chan), +        bandwidth); +    return radio_ctrl_impl::set_tx_bandwidth(clipped_bw, chan); +} + +double neon_radio_ctrl_impl::set_tx_gain( +        const double gain, +        const size_t chan +) { +    std::lock_guard<std::mutex> l(_set_lock); +    UHD_LOG_TRACE(unique_id(), +        "set_tx_gain(gain=" << gain << ", chan=" << chan << ")"); +    double clip_gain = uhd::clip(gain, AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN); +    _ad9361->set_gain( +        get_which_ad9361_chain(TX_DIRECTION, chan), +        clip_gain); +    radio_ctrl_impl::set_tx_gain(clip_gain, chan); +    return clip_gain; +} + +double neon_radio_ctrl_impl::set_rx_gain( +        const double gain, +        const size_t chan +) { +    std::lock_guard<std::mutex> l(_set_lock); +    UHD_LOG_TRACE(unique_id(), +        "set_rx_gain(gain=" << gain << ", chan=" << chan << ")"); +    double clip_gain = uhd::clip(gain, AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN); +    _ad9361->set_gain( +        get_which_ad9361_chain(RX_DIRECTION, chan), +        clip_gain); +    radio_ctrl_impl::set_rx_gain(clip_gain, chan); +    return clip_gain; +} + +size_t neon_radio_ctrl_impl::get_chan_from_dboard_fe( +    const std::string &fe, const direction_t /* dir */ +) { +    const size_t chan = boost::lexical_cast<size_t>(fe); +    if (chan > _get_num_radios()-1) { +        UHD_LOG_WARNING(unique_id(), +            boost::format("Invalid channel determined from dboard frontend %s.") % fe); +    } +    return chan; +} + +std::string neon_radio_ctrl_impl::get_dboard_fe_from_chan( +    const size_t chan, +    const direction_t /* dir */ +) { +    return std::to_string(chan); +} + +void neon_radio_ctrl_impl::set_rpc_client( +    uhd::rpc_client::sptr rpcc, +    const uhd::device_addr_t &block_args +) { +    _rpcc = rpcc; +    _block_args = block_args; +    UHD_LOG_TRACE(unique_id(), "Instantiating AD9361 control object..."); +    _ad9361 = make_rpc(_rpcc); + +    UHD_LOG_TRACE(unique_id(), "Setting Catalina Defaults... "); +    //Initialize catalina +    this->_init_codec(); + +    if (block_args.has_key("identify")) { +        const std::string identify_val = block_args.get("identify"); +        int identify_duration = std::atoi(identify_val.c_str()); +        if (identify_duration == 0) { +            identify_duration = 5; +        } +        UHD_LOG_INFO(unique_id(), +            "Running LED identification process for " << identify_duration +            << " seconds."); +        _identify_with_leds(identify_duration); +    } +    // Note: MCR gets set during the init() call (prior to this), which takes +    // in arguments from the device args. So if block_args contains a +    // master_clock_rate key, then it should better be whatever the device is +    // configured to do. +    _master_clock_rate = _rpcc->request_with_token<double>( +            _rpc_prefix + "get_master_clock_rate"); +    if (block_args.cast<double>("master_clock_rate", _master_clock_rate) +            != _master_clock_rate) { +        throw uhd::runtime_error(str( +            boost::format("Master clock rate mismatch. Device returns %f MHz, " +                          "but should have been %f MHz.") +            % (_master_clock_rate / 1e6) +            % (block_args.cast<double>( +                    "master_clock_rate", _master_clock_rate) / 1e6) +        )); + +    } +    UHD_LOG_DEBUG(unique_id(), +        "Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); +    this->set_rate(_master_clock_rate); + +    // Loopback test +    for (size_t chan = 0; chan < _get_num_radios(); chan++) { +        loopback_self_test( +            [this, chan](const uint32_t value){ +                this->sr_write(regs::CODEC_IDLE, value, chan); +            }, +            [this, chan](){ +                return this->user_reg_read64(regs::RB_CODEC_READBACK, chan); +            } +        ); +    } + +    const size_t db_idx = get_block_id().get_block_count(); +    _tree->access<eeprom_map_t>(_root_path / "eeprom") +        .add_coerced_subscriber([this, db_idx](const eeprom_map_t& db_eeprom){ +            this->_rpcc->notify_with_token("set_db_eeprom", db_idx, db_eeprom); +        }) +        .set_publisher([this, db_idx](){ +            return this->_rpcc->request_with_token<eeprom_map_t>( +                "get_db_eeprom", db_idx +            ); +        }) +    ; + +    // Init sensors +    for (const auto &dir : std::vector<direction_t>{RX_DIRECTION, TX_DIRECTION}) { +        for (size_t chan_idx = 0; chan_idx < NEON_NUM_CHANS; chan_idx++) { +            _init_mpm_sensors(dir, chan_idx); +        } +    } +} + +bool neon_radio_ctrl_impl::get_lo_lock_status( +    const direction_t dir +) { +    if (not (bool(_rpcc))) { +        UHD_LOG_DEBUG(unique_id(), +            "Reported no LO lock due to lack of RPC connection."); +        return false; +    } + +    const std::string trx = (dir == RX_DIRECTION) ? "rx" : "tx"; +    bool lo_lock = _rpcc->request_with_token<bool>( +        _rpc_prefix + "get_ad9361_lo_lock", trx); +    UHD_LOG_TRACE(unique_id(), +        "AD9361 " << trx << " LO reports lock: " << (lo_lock ? "Yes" : "No")); + +    return lo_lock; +} + +void neon_radio_ctrl_impl::_set_atr_bits( +    const size_t chan, +    const double freq, +    const std::string &ant +) { + +    const uint32_t rx_regs = _get_rx_switches(chan, freq, ant); +    const uint32_t tx_regs = _get_tx_switches(chan, freq); +    const uint32_t idle_regs = TX_AMP_OFF << TX_AMP_SHIFT | +                        TRX1_SW_TX_HB << TRX_SW_SHIFT | +                        TX_SW2_LB_80 << TX_SW2_SHIFT | +                        TX_SW1_LB_80 << TX_SW1_SHIFT | +                        RX_SW3_OFF << RX_SW3_SHIFT | +                        RX_SW2_OFF << RX_SW2_SHIFT | +                        RX_SW1_OFF << RX_SW1_SHIFT; + +    _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_IDLE, idle_regs); +    _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_RX_ONLY, rx_regs); +    _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_TX_ONLY, tx_regs); +    _db_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_FULL_DUPLEX, rx_regs | tx_regs); + +    // The LED signal names are reversed, but are consistent with the schematic +    const int idle_led = 0; +    const bool is_txrx = ant == "TX/RX"; +    const int rx_led = 1 << TRX_LED_GRN_SHIFT; +    const int tx_led = 1 << TX_LED_RED_SHIFT; +    const int txrx_led = 1 << RX_LED_GRN_SHIFT; + +    _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_IDLE, idle_led); +    _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_RX_ONLY, is_txrx ? txrx_led : rx_led); +    _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_TX_ONLY, tx_led); +    _leds_gpio[chan]->set_atr_reg(usrp::gpio_atr::ATR_REG_FULL_DUPLEX, rx_led | tx_led); +} + +void neon_radio_ctrl_impl::_identify_with_leds( +    const int identify_duration +) { +    auto end_time = std::chrono::steady_clock::now() +                        + std::chrono::seconds(identify_duration); +    bool led_state = true; +    while (std::chrono::steady_clock::now() < end_time) { +        // Add update_leds +        led_state = !led_state; +        std::this_thread::sleep_for(std::chrono::milliseconds(500)); +    } +} + +uint32_t neon_radio_ctrl_impl::_get_tx_switches( +    const size_t chan, +    const double freq +) { + +    UHD_LOG_TRACE(unique_id(), +        "Update all TX freq related switches. f=" << freq << " Hz, " +    ); +    auto tx_sw1 = TX_SW1_LB_160; +    auto tx_sw2 = TX_SW2_LB_160; +    auto trx_sw = (chan == 0) ? TRX1_SW_TX_LB: TRX2_SW_TX_LB; +    auto tx_amp = TX_AMP_LF_ON; + +    const auto band = _map_freq_to_tx_band(freq); +    switch(band) { +    case tx_band::LB_80: +        tx_sw1 = TX_SW1_LB_80; +        tx_sw2 = TX_SW2_LB_80; +        break; +    case tx_band::LB_160: +        tx_sw1 = TX_SW1_LB_160; +        tx_sw2 = TX_SW2_LB_160; +        break; +    case tx_band::LB_225: +        tx_sw1 = TX_SW1_LB_225; +        tx_sw2 = TX_SW2_LB_225; +        break; +    case tx_band::LB_400: +        tx_sw1 = TX_SW1_LB_400; +        tx_sw2 = TX_SW2_LB_400; +        break; +    case tx_band::LB_575: +        tx_sw1 = TX_SW1_LB_575; +        tx_sw2 = TX_SW2_LB_575; +        break; +    case tx_band::LB_1000: +        tx_sw1 = TX_SW1_LB_1000; +        tx_sw2 = TX_SW2_LB_1000; +        break; +    case tx_band::LB_1700: +        tx_sw1 = TX_SW1_LB_1700; +        tx_sw2 = TX_SW2_LB_1700; +        break; +    case tx_band::LB_2750: +        tx_sw1 = TX_SW1_LB_2750; +        tx_sw2 = TX_SW2_LB_2750; +        break; +    case tx_band::HB: +        trx_sw = (chan == 0) ? TRX1_SW_TX_HB: TRX2_SW_TX_HB; +        tx_amp = TX_AMP_HF_ON; +        break; +    case tx_band::INVALID_BAND: +        UHD_LOG_ERROR(unique_id(), +            "Cannot map TX frequency to band: " << freq); +        UHD_THROW_INVALID_CODE_PATH(); +        break; +    } + +    auto tx_regs = tx_amp << TX_AMP_SHIFT | +             trx_sw << TRX_SW_SHIFT | +             tx_sw2 << TX_SW2_SHIFT | +             tx_sw1 << TX_SW1_SHIFT; +    return tx_regs; +} + +uint32_t neon_radio_ctrl_impl::_get_rx_switches( +    const size_t chan, +    const double freq, +    const std::string &ant +){ +    UHD_LOG_TRACE(unique_id(), +        "Update all RX freq related switches. f=" << freq << " Hz, " +    ); +    // Default to OFF +    auto rx_sw1 = RX_SW1_OFF; +    auto rx_sw2 = RX_SW2_OFF; +    auto rx_sw3 = RX_SW3_OFF; +    auto trx_sw = (chan == 0) ? TRX1_SW_RX: TRX2_SW_RX; +    if (ant == "TX/RX") { +        rx_sw3 = RX_SW3_HBRX_LBTRX; +        trx_sw = (chan == 0) ? TRX1_SW_RX: TRX2_SW_RX; +    } +    else if (ant == "RX2") { +        rx_sw3 = RX_SW3_HBTRX_LBRX; +        // Set TRX switch to TX when receiving on RX2 +        trx_sw = TRX1_SW_TX_HB; +    } + +    const auto band = _map_freq_to_rx_band(freq); +    switch(band) { +    case rx_band::LB_B2: +        rx_sw1 = RX_SW1_LB_B2; +        rx_sw2 = RX_SW2_LB_B2; +        break; +    case rx_band::LB_B3: +        rx_sw1 = RX_SW1_LB_B3; +        rx_sw2 = RX_SW2_LB_B3; +        break; +    case rx_band::LB_B4: +        rx_sw1 = RX_SW1_LB_B4; +        rx_sw2 = RX_SW2_LB_B4; +        break; +    case rx_band::LB_B5: +        rx_sw1 = RX_SW1_LB_B5; +        rx_sw2 = RX_SW2_LB_B5; +        break; +    case rx_band::LB_B6: +        rx_sw1 = RX_SW1_LB_B6; +        rx_sw2 = RX_SW2_LB_B6; +        break; +    case rx_band::LB_B7: +        rx_sw1 = RX_SW1_LB_B7; +        rx_sw2 = RX_SW2_LB_B7; +        break; +    case rx_band::HB: +        rx_sw1 = RX_SW1_OFF; +        rx_sw2 = RX_SW2_OFF; +        if (ant == "TX/RX") { +            rx_sw3 = RX_SW3_HBTRX_LBRX; +        } +        else if (ant == "RX2") { +            rx_sw3 = RX_SW3_HBRX_LBTRX; +        } +        break; +    case rx_band::INVALID_BAND: +        UHD_LOG_ERROR(unique_id(), +            "Cannot map RX frequency to band: " << freq); +        UHD_THROW_INVALID_CODE_PATH(); +        break; +    } + +    auto rx_regs = trx_sw << TRX_SW_SHIFT | +             rx_sw3 << RX_SW3_SHIFT | +             rx_sw2 << RX_SW2_SHIFT | +             rx_sw1 << RX_SW1_SHIFT; +    return rx_regs; +} + +UHD_RFNOC_BLOCK_REGISTER(neon_radio_ctrl, "NeonRadio"); diff --git a/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp b/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp new file mode 100644 index 000000000..64b970d54 --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_radio_ctrl_impl.hpp @@ -0,0 +1,311 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_NEON_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_NEON_RADIO_CTRL_IMPL_HPP + +#include "neon_ad9361_iface.hpp" +#include <uhd/types/serial.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <uhdlib/usrp/cores/gpio_atr_3000.hpp> +#include <uhdlib/rfnoc/rpc_block_ctrl.hpp> +#include <uhdlib/rfnoc/radio_ctrl_impl.hpp> +#include <mutex> + +namespace uhd { +    namespace rfnoc { + +/*! \brief Provide access to an Magnesium radio. + */ +class neon_radio_ctrl_impl : public radio_ctrl_impl, public rpc_block_ctrl +{ +public: +    typedef boost::shared_ptr<neon_radio_ctrl_impl> sptr; + +    //! Frequency bands for RX. Bands are a function of the analog filter banks +    enum class rx_band { +        INVALID_BAND, +        LB_B2, +        LB_B3, +        LB_B4, +        LB_B5, +        LB_B6, +        LB_B7, +        HB +    }; + +    //! Frequency bands for TX. Bands are a function of the analog filter banks +    enum class tx_band { +        INVALID_BAND, +        LB_80, +        LB_160, +        LB_225, +        LB_400, +        LB_575, +        LB_1000, +        LB_1700, +        LB_2750, +        HB +    }; + +    /************************************************************************** +     * ATR/ Switches Types +     *************************************************************************/ +    //! ATR state +    enum atr_state_t { +        IDLE, +        RX_ONLY, +        TX_ONLY, +        FULL_DUPLEX +    }; + +    //! Channel select: +    enum chan_sel_t { +        CHAN1, +        CHAN2, +        BOTH +    }; + +    enum tx_sw1_t { +        TX_SW1_LB_80 = 3, +        TX_SW1_LB_160 = 7, +        TX_SW1_LB_225 = 1, +        TX_SW1_LB_400 = 5, +        TX_SW1_LB_575 = 2, +        TX_SW1_LB_1000 = 6, +        TX_SW1_LB_1700 = 0, +        TX_SW1_LB_2750 = 4 +    }; + +    enum tx_sw2_t { +        TX_SW2_LB_80 = 7, +        TX_SW2_LB_160 = 3, +        TX_SW2_LB_225 = 5, +        TX_SW2_LB_400 = 1, +        TX_SW2_LB_575 = 6, +        TX_SW2_LB_1000 = 2, +        TX_SW2_LB_1700 = 4, +        TX_SW2_LB_2750 = 0 +    }; + +    enum trx_sw_t { +        TRX1_SW_TX_HB = 2, +        TRX1_SW_TX_LB = 1, +        TRX1_SW_RX = 4, +        TRX2_SW_TX_HB = 2, +        TRX2_SW_TX_LB = 4, +        TRX2_SW_RX = 1 +    }; + +    enum rx_sw1_t { +        RX_SW1_LB_B2 = 4, +        RX_SW1_LB_B3 = 5, +        RX_SW1_LB_B4 = 2, +        RX_SW1_LB_B5 = 3, +        RX_SW1_LB_B6 = 0, +        RX_SW1_LB_B7 = 1, +        RX_SW1_OFF = 7 + +    }; + +    enum rx_sw2_t { +        RX_SW2_LB_B2 = 5, +        RX_SW2_LB_B3 = 4, +        RX_SW2_LB_B4 = 3, +        RX_SW2_LB_B5 = 2, +        RX_SW2_LB_B6 = 1, +        RX_SW2_LB_B7 = 0, +        RX_SW2_OFF = 7 +    }; + +    enum rx_sw3_t { +        RX_SW3_HBRX_LBTRX = 1, +        RX_SW3_HBTRX_LBRX = 2, +        RX_SW3_OFF = 0 //or 3 +    }; + +    enum tx_amp_t { +        TX_AMP_HF_ON = 1, +        TX_AMP_LF_ON = 2, +        TX_AMP_OFF = 3 +    }; + +    /************************************************************************ +     * Structors +     ***********************************************************************/ +    UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(neon_radio_ctrl) +    virtual ~neon_radio_ctrl_impl(); + +    /************************************************************************ +     * API calls +     ***********************************************************************/ +    // Note: We use the cached values in radio_ctrl_impl, so most getters are +    // not reimplemented here + +    virtual bool check_radio_config(); + +    //! Set which channel mode is used +    void set_channel_mode(const std::string &channel_mode); + +    //! Set streaming mode - active chains, channel_mode, timing_mode +    void set_streaming_mode(const bool tx1, const bool tx2, const bool rx1, const bool rx2); + +    /*! Run a loopback self test. +     * +     * This will write data to the AD936x and read it back again. +     * If this test fails, it generally means the interface is broken, +     * so we assume it passes and throw otherwise. Running this requires +     * a core that we can peek and poke the loopback values into. +     * +     * \param iface An interface to the associated radio control core +     * \param iface The radio control core's address to write the loopback value +     * \param iface The radio control core's readback address to read back the returned value +     * +     * \throws a uhd::runtime_error if the loopback value didn't match. +     */ +    void loopback_self_test( +            std::function<void(uint32_t)> poker_functor, +            std::function<uint64_t()> peeker_functor +    ); + +    double set_rate(const double rate); + +    void set_tx_antenna(const std::string &ant, const size_t chan); +    void set_rx_antenna(const std::string &ant, const size_t chan); + +    double set_tx_frequency(const double freq, const size_t chan); +    double set_rx_frequency(const double freq, const size_t chan); +    double set_tx_bandwidth(const double bandwidth, const size_t chan); +    double set_rx_bandwidth(const double bandwidth, const size_t chan); + +    // gain +    double set_tx_gain(const double gain, const size_t chan); +    double set_rx_gain(const double gain, const size_t chan); + +    size_t get_chan_from_dboard_fe(const std::string &fe, const direction_t dir); +    std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); + +    void set_rpc_client( +        uhd::rpc_client::sptr rpcc, +        const uhd::device_addr_t &block_args +    ); + +private: +    /************************************************************************** +     * Helpers +     *************************************************************************/ +    //! Initialize all the peripherals connected to this block +    void _init_peripherals(); + +    //! Set state of this class to sensible defaults +    void _init_defaults(); + +    //! Init a subtree for the RF frontends +    void _init_frontend_subtree( +        uhd::property_tree::sptr subtree, +        const size_t chan_idx +    ); + +    //! Initialize Catalina defaults +    void _init_codec(); + +    //! Initialize property tree +    void _init_prop_tree(); + +    void _init_mpm_sensors( +        const direction_t dir, +        const size_t chan_idx +    ); + +    //! Map a frequency in Hz to an rx_band value. Will return +    //  rx_band::INVALID_BAND if the frequency is out of range. +    rx_band _map_freq_to_rx_band(const double freq); +    //! Map a frequency in Hz to an tx_band value. Will return +    //  tx_band::INVALID_BAND if the frequency is out of range. +    tx_band _map_freq_to_tx_band(const double freq); + +    /************************************************************************* +     * Sensors +     *************************************************************************/ +    //! Return LO lock status. Factors in current band (low/high) and +    // direction (TX/RX) +    bool get_lo_lock_status( +            const direction_t dir +    ); + +    /************************************************************************** +     * Misc Controls +     *************************************************************************/ +    //! Blink the front-panel LEDs for \p identify_duration, +    //  and resume normal operation. +    void _identify_with_leds( +        const int identify_duration +    ); + +    uint32_t _get_rx_switches( +        const size_t chan, +        const double freq, +        const std::string &ant +    ); + +    uint32_t _get_tx_switches( +        const size_t chan, +        const double freq +    ); + +    void _set_atr_bits( +        const size_t chan, +        const double freq, +        const std::string &ant +    ); + +    /************************************************************************** +     * Private attributes +     *************************************************************************/ +    //! Locks access to setter APIs +    std::mutex _set_lock; + +    //! Letter representation of the radio we're currently running +    std::string _radio_slot; + +    //! Prepended for all dboard RPC calls +    std::string _rpc_prefix; + +    //! Additional block args; gets set during set_rpc_client() +    uhd::device_addr_t _block_args; + +    //! Reference to the RPC client +    uhd::rpc_client::sptr _rpcc; + +    //! Reference to the SPI core +    uhd::spi_iface::sptr _spi; + +    //! Reference to the AD9361 controls +    //neon_ad9361_iface::uptr _ad9361; +    ad9361_ctrl::sptr _ad9361; + +    //! ATR controls. These control the AD9361 gain +    //  up/down bits. +    //  Every radio channel gets its own ATR state register. +    std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _db_gpio; + +    // ATR controls for LEDs +    std::vector<usrp::gpio_atr::gpio_atr_3000::sptr> _leds_gpio; + +    //! Front panel GPIO controller. Note that only one radio block per +    //  module can be the FP-GPIO master. +    usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + +    //! Sampling rate +    double _master_clock_rate = 1.0; +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NEON_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: + diff --git a/host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp b/host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp new file mode 100644 index 000000000..9c2396f2d --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_radio_ctrl_init.cpp @@ -0,0 +1,495 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "neon_radio_ctrl_impl.hpp" +#include "neon_constants.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/algorithm/string/case_conv.hpp> +#include <vector> +#include <string> + +using namespace uhd; +using namespace uhd::rfnoc; + +//! Helper function to extract single value of port number. +// +// Each GPIO pins can be controlled by each radio output ports. +// This function convert the format of attribute "Radio_N_M" +// to a single value port number = N*number_of_port_per_radio + M + +uint32_t _extract_port_number(std::string radio_src_string, uhd::property_tree::sptr ptree){ +    std::string s_val = "0"; +    std::vector<std::string> radio_strings; +    boost::algorithm::split( +        radio_strings, +        radio_src_string, +        boost::is_any_of("_/"), +        boost::token_compress_on); +    boost::to_lower(radio_strings[0]); +    if (radio_strings.size()<3) { +        throw uhd::runtime_error(str(boost::format("%s is an invalid GPIO source string.") % radio_src_string)); +    } +    size_t radio_num = std::stoi(radio_strings[1]); +    size_t port_num = std::stoi(radio_strings[2]); +    if (radio_strings[0] != "radio") { +        throw uhd::runtime_error("Front panel GPIO bank can only accept a radio block as its driver."); +    } +    std::string radio_port_out = "Radio_"+  radio_strings[1] + "/ports/out"; +    std::string radio_port_path = radio_port_out + "/"+ radio_strings[2]; +    auto found = ptree->exists(fs_path("xbar")/ radio_port_path); +    if (not found){ +        throw uhd::runtime_error(str(boost::format( +                    "Could not find radio port %s.\n") % radio_port_path)); +    } +    size_t port_size = ptree->list(fs_path("xbar")/ radio_port_out).size(); +    return radio_num*port_size + port_num; +} + +void neon_radio_ctrl_impl::_init_defaults() +{ +    UHD_LOG_TRACE(unique_id(), "Initializing defaults..."); +    const size_t num_rx_chans = get_output_ports().size(); +    const size_t num_tx_chans = get_input_ports().size(); + +    UHD_LOG_TRACE(unique_id(), +            "Num TX chans: " << num_tx_chans +            << " Num RX chans: " << num_rx_chans); + +    for (size_t chan = 0; chan < num_rx_chans; chan++) { +        radio_ctrl_impl::set_rx_frequency(NEON_DEFAULT_FREQ, chan); +        radio_ctrl_impl::set_rx_gain(NEON_DEFAULT_GAIN, chan); +        radio_ctrl_impl::set_rx_antenna(NEON_DEFAULT_RX_ANTENNA, chan); +        radio_ctrl_impl::set_rx_bandwidth(NEON_DEFAULT_BANDWIDTH, chan); +    } + +    for (size_t chan = 0; chan < num_tx_chans; chan++) { +        radio_ctrl_impl::set_tx_frequency(NEON_DEFAULT_FREQ, chan); +        radio_ctrl_impl::set_tx_gain(NEON_DEFAULT_GAIN, chan); +        radio_ctrl_impl::set_tx_antenna(NEON_DEFAULT_TX_ANTENNA, chan); +    } + +    /** Update default SPP (overwrites the default value from the XML file) **/ +    const size_t max_bytes_header = +        uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); +    const size_t default_spp = +        (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) +        / (2 * sizeof(int16_t)); +    UHD_LOG_DEBUG(unique_id(), +        "Setting default spp to " << default_spp); +    _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); +} + +void neon_radio_ctrl_impl::_init_peripherals() +{ +    UHD_LOG_TRACE(unique_id(), "Initializing peripherals..."); + +   _db_gpio.clear(); // Following the as-if rule, this can get optimized out +    for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { +        UHD_LOG_TRACE(unique_id(), +            "Initializing GPIOs for channel " << radio_idx); +        _db_gpio.emplace_back( +            usrp::gpio_atr::gpio_atr_3000::make_write_only( +                _get_ctrl(radio_idx), +                regs::sr_addr(regs::GPIO) +            ) +        ); +        _db_gpio[radio_idx]->set_atr_mode(usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); +    } +   _leds_gpio.clear(); // Following the as-if rule, this can get optimized out +    for (size_t radio_idx = 0; radio_idx < _get_num_radios(); radio_idx++) { +        UHD_LOG_TRACE(unique_id(), +            "Initializing GPIOs for channel " << radio_idx); +        _leds_gpio.emplace_back( +            usrp::gpio_atr::gpio_atr_3000::make_write_only( +                _get_ctrl(radio_idx), +                regs::sr_addr(regs::LEDS) +            ) +        ); + +        _leds_gpio[radio_idx]->set_atr_mode(usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); +    } +    UHD_LOG_TRACE(unique_id(), "Initializing front-panel GPIO control...") +    _fp_gpio = usrp::gpio_atr::gpio_atr_3000::make( +            _get_ctrl(0), regs::sr_addr(regs::FP_GPIO), regs::RB_FP_GPIO); +} + +void neon_radio_ctrl_impl::_init_frontend_subtree( +    uhd::property_tree::sptr subtree, +    const size_t chan_idx +) { +    const fs_path tx_fe_path = fs_path("tx_frontends") / chan_idx; +    const fs_path rx_fe_path = fs_path("rx_frontends") / chan_idx; +    UHD_LOG_TRACE(unique_id(), +        "Adding non-RFNoC block properties for channel " << chan_idx << +        " to prop tree path " << tx_fe_path << " and " << rx_fe_path); +    // TX Standard attributes +    subtree->create<std::string>(tx_fe_path / "name") +        .set(str(boost::format("Neon"))) +    ; +    subtree->create<std::string>(tx_fe_path / "connection") +        .set("IQ") +    ; +    // RX Standard attributes +    subtree->create<std::string>(rx_fe_path / "name") +        .set(str(boost::format("Neon"))) +    ; +    subtree->create<std::string>(rx_fe_path / "connection") +        .set("IQ") +    ; +    // TX Antenna +    subtree->create<std::string>(tx_fe_path / "antenna" / "value") +        .add_coerced_subscriber([this, chan_idx](const std::string &ant){ +            this->set_tx_antenna(ant, chan_idx); +        }) +        .set_publisher([this, chan_idx](){ +            return this->get_tx_antenna(chan_idx); +        }) +    ; +    subtree->create<std::vector<std::string>>(tx_fe_path / "antenna" / "options") +        .set({NEON_DEFAULT_TX_ANTENNA}) +        .add_coerced_subscriber([](const std::vector<std::string> &){ +            throw uhd::runtime_error( +                    "Attempting to update antenna options!"); +        }) +    ; +    // RX Antenna +    subtree->create<std::string>(rx_fe_path / "antenna" / "value") +        .add_coerced_subscriber([this, chan_idx](const std::string &ant){ +            this->set_rx_antenna(ant, chan_idx); +        }) +        .set_publisher([this, chan_idx](){ +            return this->get_rx_antenna(chan_idx); +        }) +    ; +    subtree->create<std::vector<std::string>>(rx_fe_path / "antenna" / "options") +        .set(NEON_RX_ANTENNAS) +        .add_coerced_subscriber([](const std::vector<std::string> &){ +            throw uhd::runtime_error( +                "Attempting to update antenna options!"); +        }) +    ; +    // TX frequency +    subtree->create<double>(tx_fe_path / "freq" / "value") +        .set_coercer([this, chan_idx](const double freq){ +            return this->set_tx_frequency(freq, chan_idx); +        }) +        .set_publisher([this, chan_idx](){ +            return this->get_tx_frequency(chan_idx); +        }) +    ; +    subtree->create<meta_range_t>(tx_fe_path / "freq" / "range") +        .set(meta_range_t(AD9361_MIN_FREQ, AD9361_MAX_FREQ, 1.0)) +        .add_coerced_subscriber([](const meta_range_t &){ +            throw uhd::runtime_error( +                "Attempting to update freq range!"); +        }) +    ; +    // RX frequency +    subtree->create<double>(rx_fe_path / "freq" / "value") +        .set_coercer([this, chan_idx](const double freq){ +            return this->set_rx_frequency(freq, chan_idx); +        }) +        .set_publisher([this, chan_idx](){ +            return this->get_rx_frequency(chan_idx); +        }) +    ; +    subtree->create<meta_range_t>(rx_fe_path / "freq" / "range") +        .set(meta_range_t(AD9361_MIN_FREQ, AD9361_MAX_FREQ, 1.0)) +        .add_coerced_subscriber([](const meta_range_t &){ +            throw uhd::runtime_error( +                "Attempting to update freq range!"); +        }) +    ; +    // TX bandwidth +    subtree->create<double>(tx_fe_path / "bandwidth" / "value") +        .set(AD9361_TX_MAX_BANDWIDTH) +        .set_coercer([this, chan_idx](const double bw){ +            return this->set_tx_bandwidth(bw, chan_idx); +        }) +        .set_publisher([this, chan_idx](){ +            return this->get_tx_bandwidth(chan_idx); +        }) +    ; +    subtree->create<meta_range_t>(tx_fe_path / "bandwidth" / "range") +        .set(meta_range_t(AD9361_TX_MIN_BANDWIDTH, AD9361_TX_MAX_BANDWIDTH)) +        .add_coerced_subscriber([](const meta_range_t &){ +            throw uhd::runtime_error( +                "Attempting to update bandwidth range!"); +        }) +    ; +    // RX bandwidth +    subtree->create<double>(rx_fe_path / "bandwidth" / "value") +        .set(AD9361_RX_MAX_BANDWIDTH) +        .set_coercer([this, chan_idx](const double bw){ +            return this->set_rx_bandwidth(bw, chan_idx); +        }) +    ; +    subtree->create<meta_range_t>(rx_fe_path / "bandwidth" / "range") +        .set(meta_range_t(AD9361_RX_MIN_BANDWIDTH, AD9361_RX_MAX_BANDWIDTH)) +        .add_coerced_subscriber([](const meta_range_t &){ +            throw uhd::runtime_error( +                "Attempting to update bandwidth range!"); +        }) +    ; + +    // TX gains +    const std::vector<std::string> tx_gain_names = ad9361_ctrl::get_gain_names("TX1"); +    for (auto tx_gain_name : tx_gain_names) { +        subtree->create<double>(tx_fe_path / "gains" / tx_gain_name / "value") +            .set_coercer([this, chan_idx](const double gain){ +                return this->set_tx_gain(gain, chan_idx); +            }) +            .set_publisher([this, chan_idx](){ +                return radio_ctrl_impl::get_tx_gain(chan_idx); +            }) +        ; +        subtree->create<meta_range_t>(tx_fe_path / "gains" / tx_gain_name / "range") +            .add_coerced_subscriber([](const meta_range_t &){ +                throw uhd::runtime_error( +                    "Attempting to update gain range!"); +            }) +            .set_publisher([this](){ +            return meta_range_t( +                AD9361_MIN_TX_GAIN, +                AD9361_MAX_TX_GAIN, +                AD9361_TX_GAIN_STEP +            ); +            }) +        ; +    } + +    // RX gains +    const std::vector<std::string> rx_gain_names = ad9361_ctrl::get_gain_names("RX1"); +    for (auto rx_gain_name : rx_gain_names) { +        subtree->create<double>(rx_fe_path / "gains" / rx_gain_name / "value") +            .set_coercer([this, chan_idx](const double gain){ +                return this->set_rx_gain(gain, chan_idx); +            }) +            .set_publisher([this, chan_idx](){ +                return radio_ctrl_impl::get_rx_gain(chan_idx); +            }) +        ; + +        subtree->create<meta_range_t>(rx_fe_path / "gains" / rx_gain_name / "range") +            .add_coerced_subscriber([](const meta_range_t &){ +                throw uhd::runtime_error( +                    "Attempting to update gain range!"); +            }) +            .set_publisher([this](){ +                return meta_range_t( +                    AD9361_MIN_RX_GAIN, +                    AD9361_MAX_RX_GAIN, +                    AD9361_RX_GAIN_STEP +                ); +            }) +        ; +    } + +    // TX LO lock sensor ////////////////////////////////////////////////////// +    // Note: The AD9361 LO lock sensors are generated programmatically in +    // set_rpc_client(). The actual lo_locked publisher is also set there. +    subtree->create<sensor_value_t>(tx_fe_path / "sensors" / "lo_locked") +        .set(sensor_value_t("all_los", false,  "locked", "unlocked")) +        .add_coerced_subscriber([](const sensor_value_t &){ +            throw uhd::runtime_error( +                "Attempting to write to sensor!"); +        }) +        .set_publisher([this](){ +            return sensor_value_t( +                "all_los", +                this->get_lo_lock_status(TX_DIRECTION), +                "locked", "unlocked" +            ); +        }) +    ; +    // RX LO lock sensor (see not on TX LO lock sensor) +    subtree->create<sensor_value_t>(rx_fe_path / "sensors" / "lo_locked") +        .set(sensor_value_t("all_los", false,  "locked", "unlocked")) +        .add_coerced_subscriber([](const sensor_value_t &){ +            throw uhd::runtime_error( +                "Attempting to write to sensor!"); +        }) +        .set_publisher([this](){ +            return sensor_value_t( +                "all_los", +                this->get_lo_lock_status(RX_DIRECTION), +                "locked", "unlocked" +            ); +        }) +    ; +} + +void neon_radio_ctrl_impl::_init_prop_tree() +{ +    const fs_path fe_base = fs_path("dboards") / _radio_slot; +    for (size_t chan_idx = 0; chan_idx < NEON_NUM_CHANS; chan_idx++) { +        this->_init_frontend_subtree( +            _tree->subtree(fe_base), chan_idx); +    } + +    _tree->create<eeprom_map_t>(_root_path / "eeprom") +        .set(eeprom_map_t()); + +    _tree->create<int>("rx_codecs" / _radio_slot / "gains"); +    _tree->create<int>("tx_codecs" / _radio_slot / "gains"); +    _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("AD9361 Dual ADC"); +    _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("AD9361 Dual DAC"); + +    if (not _tree->exists("tick_rate")) { +        _tree->create<double>("tick_rate") +            .set_coercer([this](double tick_rate){ +                return this->set_rate(tick_rate); +            }) +            .set_publisher([this](){ +                return this->get_rate(); +            }) +        ; +    } else { +        UHD_LOG_WARNING(unique_id(), "Cannot set tick_rate again"); +    } + +    // *****FP_GPIO************************ +    for(const auto& attr:  usrp::gpio_atr::gpio_attr_map) { +        if (not _tree->exists(fs_path("gpio") / "FP0" / attr.second)){ +            switch (attr.first){ +                case usrp::gpio_atr::GPIO_SRC: +                    //FIXME:  move this creation of this branch of ptree out side of radio impl; +                    // since there's no data dependency between radio and SRC setting for FP0 +                    _tree->create<std::vector<std::string>>(fs_path("gpio") / "FP0" / attr.second) +                         .set(std::vector<std::string>( +                            32, +                            usrp::gpio_atr::default_attr_value_map.at(attr.first))) +                         .add_coerced_subscriber([this, attr]( +                            const std::vector<std::string> str_val){ +                            uint32_t radio_src_value = 0; +                            uint32_t master_value = 0; +                            for(size_t i = 0 ; i<str_val.size(); i++){ +                                if(str_val[i] == "PS"){ +                                    master_value += 1<<i;; +                                }else{ +                                    auto port_num = _extract_port_number(str_val[i],_tree); +                                    radio_src_value =(1<<(2*i))*port_num + radio_src_value; +                                } +                            } +                            _rpcc->notify_with_token("set_fp_gpio_master", master_value); +                            _rpcc->notify_with_token("set_fp_gpio_radio_src", radio_src_value); +                         }); +                         break; +                case usrp::gpio_atr::GPIO_CTRL: +                case usrp::gpio_atr::GPIO_DDR: +                    _tree->create<std::vector<std::string>>(fs_path("gpio") / "FP0" / attr.second) +                         .set(std::vector<std::string>( +                            32, +                            usrp::gpio_atr::default_attr_value_map.at(attr.first))) +                         .add_coerced_subscriber([this, attr]( +                             const std::vector<std::string> str_val){ +                            uint32_t val = 0; +                            for(size_t i = 0 ; i < str_val.size() ; i++){ +                                val += usrp::gpio_atr::gpio_attr_value_pair.at(attr.second).at(str_val[i])<<i; +                            } +                            _fp_gpio->set_gpio_attr(attr.first, val); +                         }); +                    break; +                case usrp::gpio_atr::GPIO_READBACK:{ +                    _tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second) +                        .set_publisher([this](){ +                            return _fp_gpio->read_gpio(); +                        } +                    ); +                } +                    break; +                default: +                    _tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second) +                         .set(0) +                         .add_coerced_subscriber([this, attr](const uint32_t val){ +                             _fp_gpio->set_gpio_attr(attr.first, val); +                         }); +            } +        }else{ +            switch (attr.first){ +                case usrp::gpio_atr::GPIO_SRC: +                break; +                case usrp::gpio_atr::GPIO_CTRL: +                case usrp::gpio_atr::GPIO_DDR: +                    _tree->access<std::vector<std::string>>(fs_path("gpio") / "FP0" / attr.second) +                         .set(std::vector<std::string>(32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) +                         .add_coerced_subscriber([this, attr](const std::vector<std::string> str_val){ +                            uint32_t val = 0; +                            for(size_t i = 0 ; i < str_val.size() ; i++){ +                                val += usrp::gpio_atr::gpio_attr_value_pair.at(attr.second).at(str_val[i])<<i; +                            } +                            _fp_gpio->set_gpio_attr(attr.first, val); +                         }); +                    break; +                case usrp::gpio_atr::GPIO_READBACK: +                    break; +                default: +                    _tree->access<uint32_t>(fs_path("gpio") / "FP0" / attr.second) +                         .set(0) +                         .add_coerced_subscriber([this, attr](const uint32_t val){ +                             _fp_gpio->set_gpio_attr(attr.first, val); +                         }); +            } +        } +    } +} + +void neon_radio_ctrl_impl::_init_codec() +{ +    for (size_t chan = 0; chan < _get_num_radios(); chan++) { +        std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); +        this->set_rx_gain(NEON_DEFAULT_GAIN, chan); +        this->set_rx_frequency(NEON_DEFAULT_FREQ, chan); +        this->set_rx_antenna(NEON_DEFAULT_RX_ANTENNA, chan); +        this->set_rx_bandwidth(NEON_DEFAULT_BANDWIDTH, chan); +        _ad9361->set_dc_offset_auto(rx_fe, NEON_DEFAULT_AUTO_DC_OFFSET); +        _ad9361->set_iq_balance_auto(rx_fe, NEON_DEFAULT_AUTO_IQ_BALANCE); +        _ad9361->set_agc(rx_fe, NEON_DEFAULT_AGC_ENABLE); +        std::string tx_fe = get_which_ad9361_chain(TX_DIRECTION, chan); +        this->set_tx_gain(NEON_DEFAULT_GAIN, chan); +        this->set_tx_frequency(NEON_DEFAULT_FREQ, chan); +        this->set_tx_bandwidth(NEON_DEFAULT_BANDWIDTH, chan); +    } +} + +void neon_radio_ctrl_impl::_init_mpm_sensors( +        const direction_t dir, +        const size_t chan_idx +) { +    const std::string trx = (dir == RX_DIRECTION) ? "RX" : "TX"; +    const fs_path fe_path = +        fs_path("dboards") / _radio_slot / +        (dir == RX_DIRECTION ? "rx_frontends" : "tx_frontends") / chan_idx; +    auto sensor_list = +        _rpcc->request_with_token<std::vector<std::string>>( +                this->_rpc_prefix + "get_sensors", trx); +    UHD_LOG_TRACE(unique_id(), +        "Chan " << chan_idx << ": Found " +        << sensor_list.size() << " " << trx << " sensors."); +    for (const auto &sensor_name : sensor_list) { +        UHD_LOG_TRACE(unique_id(), +            "Adding " << trx << " sensor " << sensor_name); +        _tree->create<sensor_value_t>(fe_path / "sensors" / sensor_name) +            .add_coerced_subscriber([](const sensor_value_t &){ +                throw uhd::runtime_error( +                    "Attempting to write to sensor!"); +            }) +            .set_publisher([this, trx, sensor_name, chan_idx](){ +                return sensor_value_t( +                    this->_rpcc->request_with_token<sensor_value_t::sensor_map_t>( +                        this->_rpc_prefix + "get_sensor", +                            trx, sensor_name, chan_idx) +                ); +            }) +        ; +    } +} + diff --git a/host/lib/usrp/dboard/neon/neon_regs.hpp b/host/lib/usrp/dboard/neon/neon_regs.hpp new file mode 100644 index 000000000..39ceca01c --- /dev/null +++ b/host/lib/usrp/dboard/neon/neon_regs.hpp @@ -0,0 +1,26 @@ +// +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_NEON_REGS_HPP +#define INCLUDED_NEON_REGS_HPP + +#include <uhd/config.hpp> +#include <cstdint> + +static const uint32_t TX_AMP_SHIFT  = 17; +static const uint32_t TRX_SW_SHIFT  = 14; +static const uint32_t RX_SW1_SHIFT  = 0; +static const uint32_t RX_SW2_SHIFT  = 3; +static const uint32_t RX_SW3_SHIFT  = 6; +static const uint32_t TX_SW1_SHIFT  = 8; +static const uint32_t TX_SW2_SHIFT  = 11; + +static const uint32_t TRX_LED_GRN_SHIFT  = 0; +static const uint32_t TX_LED_RED_SHIFT  = 1; +static const uint32_t RX_LED_GRN_SHIFT  = 2; + +#endif /* INCLUDED_NEON_REGS_HPP */ + diff --git a/host/lib/usrp/mpmd/mpmd_devices.hpp b/host/lib/usrp/mpmd/mpmd_devices.hpp index 7e12fe31a..ab0cc1271 100644 --- a/host/lib/usrp/mpmd/mpmd_devices.hpp +++ b/host/lib/usrp/mpmd/mpmd_devices.hpp @@ -17,7 +17,8 @@ static constexpr char MPM_CATCHALL_DEVICE_TYPE[] = "mpm";  // to make decisions. This vector is for filtering purposes.  static const std::vector<std::string> MPM_DEVICE_TYPES = {      MPM_CATCHALL_DEVICE_TYPE, -    "n3xx" +    "n3xx", +    "e3xx",  };  #endif /* INCLUDED_MPMD_DEVICES_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_image_loader.cpp b/host/lib/usrp/mpmd/mpmd_image_loader.cpp index fa09dc08b..e0b85f897 100644 --- a/host/lib/usrp/mpmd/mpmd_image_loader.cpp +++ b/host/lib/usrp/mpmd/mpmd_image_loader.cpp @@ -81,7 +81,9 @@ uhd::usrp::component_file_t generate_component(   */  static bool mpmd_image_loader(const image_loader::image_loader_args_t &image_loader_args){      // See if any MPM devices with the given args are found -    device_addrs_t devs = mpmd_find(image_loader_args.args); +    device_addr_t find_hint = image_loader_args.args; +    find_hint.set("find_all", "1"); // We need to find all devices +    device_addrs_t devs = mpmd_find(find_hint);      if (devs.size() != 1) {          // TODO: Do we want to handle multiple devices here? @@ -189,4 +191,5 @@ UHD_STATIC_BLOCK(register_mpm_image_loader){      //TODO: 'n3xx' doesn't really fit the MPM abstraction, but this is simpler for the time being      image_loader::register_image_loader("n3xx", mpmd_image_loader, recovery_instructions); +    image_loader::register_image_loader("e3xx", mpmd_image_loader, recovery_instructions);  } | 
