mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
mpm: initial commit of E320 code
Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com>
This commit is contained in:
parent
a1d6530ce5
commit
300a5e3f6e
@ -50,6 +50,12 @@ public:
|
||||
uhd::spi_iface::sptr spi_iface,
|
||||
uint32_t slave_num
|
||||
);
|
||||
|
||||
static sptr make_spi(
|
||||
ad9361_params::sptr client_settings,
|
||||
ad9361_io::sptr spi_io_iface
|
||||
);
|
||||
|
||||
//! Get a list of gain names for RX or TX
|
||||
static std::vector<std::string> get_gain_names(const std::string &/*which*/)
|
||||
{
|
||||
|
||||
@ -212,6 +212,14 @@ 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);
|
||||
@ -315,3 +323,10 @@ ad9361_ctrl::sptr ad9361_ctrl::make_spi(
|
||||
boost::shared_ptr<ad9361_io_spi> spi_io_iface = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num);
|
||||
return sptr(new ad9361_ctrl_impl(client_settings, spi_io_iface));
|
||||
}
|
||||
|
||||
ad9361_ctrl::sptr ad9361_ctrl::make_spi(
|
||||
ad9361_params::sptr client_settings,
|
||||
ad9361_io::sptr spi_io_iface
|
||||
) {
|
||||
return sptr(new ad9361_ctrl_impl(client_settings, spi_io_iface));
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ if(_has_no_psabi)
|
||||
MESSAGE(STATUS "Disabling psABI warnings.")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi")
|
||||
endif(_has_no_psabi)
|
||||
SET(MPM_ALL_DEVICES n3xx tests)
|
||||
SET(MPM_ALL_DEVICES n3xx e320 tests)
|
||||
SET(MPM_DEVICE "n3xx" CACHE STRING "Choose an MPM device to build")
|
||||
SET_PROPERTY(CACHE MPM_DEVICE PROPERTY STRINGS ${MPM_ALL_DEVICES})
|
||||
# Validate MPM_DEVICE
|
||||
@ -146,11 +146,14 @@ endif()
|
||||
IF(MPM_DEVICE STREQUAL "n3xx")
|
||||
SET(ENABLE_MYKONOS ON)
|
||||
SET(ENABLE_MAGNESIUM ON)
|
||||
ELSEIF(MPM_DEVICE STREQUAL "e320")
|
||||
SET(ENABLE_E320 ON)
|
||||
ENDIF()
|
||||
|
||||
MPM_REGISTER_COMPONENT("LibMPM" ENABLE_LIBMPM ON "Boost_FOUND" OFF ON)
|
||||
MPM_REGISTER_COMPONENT("Mykonos" ENABLE_MYKONOS ON "ENABLE_LIBMPM" OFF OFF)
|
||||
MPM_REGISTER_COMPONENT("Magnesium" ENABLE_MAGNESIUM ON "ENABLE_MYKONOS" OFF OFF)
|
||||
MPM_REGISTER_COMPONENT("E320" ENABLE_E320 ON "ENABLE_LIBMPM" OFF OFF)
|
||||
|
||||
ADD_SUBDIRECTORY(include)
|
||||
INCLUDE_DIRECTORIES(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, National Instruments Company
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
#
|
||||
@ -11,6 +11,8 @@ INSTALL(FILES
|
||||
|
||||
IF(ENABLE_MYKONOS)
|
||||
ADD_SUBDIRECTORY(ad937x)
|
||||
ELSEIF(ENABLE_E320)
|
||||
ADD_SUBDIRECTORY(ad9361)
|
||||
ENDIF(ENABLE_MYKONOS)
|
||||
|
||||
ADD_SUBDIRECTORY(chips)
|
||||
|
||||
12
mpm/include/mpm/ad9361/CMakeLists.txt
Normal file
12
mpm/include/mpm/ad9361/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
#
|
||||
# Copyright 2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
|
||||
INSTALL(FILES
|
||||
ad9361_ctrl.hpp
|
||||
e320_defaults.hpp
|
||||
DESTINATION ${INCLUDE_DIR}/mpm/catalina
|
||||
)
|
||||
69
mpm/include/mpm/ad9361/ad9361_ctrl.hpp
Normal file
69
mpm/include/mpm/ad9361/ad9361_ctrl.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
//
|
||||
// Copyright 2018 Ettus Research, a National Instruments Company
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
// Relative to uhd/host/lib/usrp/common/ad9361_driver/
|
||||
#include "../../../include/uhdlib/usrp/common/ad9361_ctrl.hpp"
|
||||
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mpm { namespace chips {
|
||||
using uhd::usrp::ad9361_ctrl;
|
||||
}};
|
||||
|
||||
//TODO: pull in filter_info_base
|
||||
#ifdef LIBMPM_PYTHON
|
||||
void export_catalina(){
|
||||
LIBMPM_BOOST_PREAMBLE("ad9361")
|
||||
using namespace mpm::chips;
|
||||
bp::class_<ad9361_ctrl, boost::noncopyable, boost::shared_ptr<ad9361_ctrl>>("ad9361_ctrl", bp::no_init)
|
||||
.def("get_gain_names", &ad9361_ctrl::get_gain_names)
|
||||
.staticmethod("get_gain_names")
|
||||
// Make this "Python private" because the return value can't be serialized
|
||||
.def("_get_gain_range", &ad9361_ctrl::get_gain_range)
|
||||
.staticmethod("_get_gain_range")
|
||||
.def("get_rf_freq_range", &ad9361_ctrl::get_rf_freq_range)
|
||||
.staticmethod("get_rf_freq_range")
|
||||
.def("get_bw_filter_range", &ad9361_ctrl::get_bw_filter_range)
|
||||
.staticmethod("get_bw_filter_range")
|
||||
.def("get_clock_rate_range", &ad9361_ctrl::get_clock_rate_range)
|
||||
.staticmethod("get_clock_rate_range")
|
||||
.def("set_bw_filter", &ad9361_ctrl::set_bw_filter)
|
||||
.def("set_gain", &ad9361_ctrl::set_gain)
|
||||
.def("set_agc", &ad9361_ctrl::set_agc)
|
||||
.def("set_agc_mode", &ad9361_ctrl::set_agc_mode)
|
||||
.def("set_clock_rate", &ad9361_ctrl::set_clock_rate)
|
||||
.def("set_active_chains", &ad9361_ctrl::set_active_chains)
|
||||
.def("set_timing_mode", &ad9361_ctrl::set_timing_mode)
|
||||
.def("tune", &ad9361_ctrl::tune)
|
||||
.def("set_dc_offset", &ad9361_ctrl::set_dc_offset)
|
||||
.def("set_dc_offset_auto", &ad9361_ctrl::set_dc_offset_auto)
|
||||
.def("set_iq_balance", &ad9361_ctrl::set_iq_balance)
|
||||
.def("set_iq_balance_auto", &ad9361_ctrl::set_iq_balance_auto)
|
||||
.def("get_freq", &ad9361_ctrl::get_freq)
|
||||
.def("data_port_loopback", &ad9361_ctrl::data_port_loopback)
|
||||
.def("get_rssi", +[](ad9361_ctrl& self, std::string which) {
|
||||
return self.get_rssi(which).to_real();
|
||||
})
|
||||
.def("get_temperature", +[](ad9361_ctrl& self) {
|
||||
return self.get_temperature().to_real();
|
||||
})
|
||||
.def("get_filter_names", &ad9361_ctrl::get_filter_names)
|
||||
// Make this "Python private" because the return value can't be serialized.
|
||||
.def("_get_filter", &ad9361_ctrl::get_filter)
|
||||
.def("set_filter", &ad9361_ctrl::set_filter)
|
||||
.def("output_digital_test_tone", &ad9361_ctrl::output_digital_test_tone)
|
||||
;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
45
mpm/include/mpm/ad9361/e320_defaults.hpp
Normal file
45
mpm/include/mpm/ad9361/e320_defaults.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright 2018 Ettus Research, a National Instruments Company
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#ifndef INCLUDED_E320_DEFAULTS_HPP
|
||||
#define INCLUDED_E320_DEFAULTS_HPP
|
||||
|
||||
#include "ad9361_ctrl.hpp"
|
||||
|
||||
namespace mpm { namespace types { namespace e320 {
|
||||
|
||||
using namespace uhd::usrp;
|
||||
|
||||
class e320_ad9361_client_t : public uhd::usrp::ad9361_params {
|
||||
public:
|
||||
~e320_ad9361_client_t() {}
|
||||
double get_band_edge(frequency_band_t band) {
|
||||
switch (band) {
|
||||
case AD9361_RX_BAND0: return 0.0;
|
||||
case AD9361_RX_BAND1: return 2.6e9;
|
||||
case AD9361_TX_BAND0: return 2940.0e6;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
clocking_mode_t get_clocking_mode() {
|
||||
return clocking_mode_t::AD9361_XTAL_N_CLK_PATH;
|
||||
}
|
||||
digital_interface_mode_t get_digital_interface_mode() {
|
||||
return AD9361_DDR_FDD_LVDS;
|
||||
}
|
||||
digital_interface_delays_t get_digital_interface_timing() {
|
||||
digital_interface_delays_t delays;
|
||||
delays.rx_clk_delay = 0;
|
||||
delays.rx_data_delay = 0;
|
||||
delays.tx_clk_delay = 0;
|
||||
delays.tx_data_delay = 0;
|
||||
return delays;
|
||||
}
|
||||
};
|
||||
|
||||
}}} // namespace
|
||||
|
||||
#endif // INCLUDED_E320_DEFAULTS_HPP
|
||||
@ -1,9 +1,16 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, National Instruments Company
|
||||
# Copyright 2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
INSTALL(FILES
|
||||
magnesium_manager.hpp
|
||||
DESTINATION ${INCLUDE_DIR}/mpm/dboards
|
||||
)
|
||||
IF(ENABLE_MAGNESIUM)
|
||||
INSTALL(FILES
|
||||
magnesium_manager.hpp
|
||||
DESTINATION ${INCLUDE_DIR}/mpm/dboards
|
||||
)
|
||||
ELSEIF(ENABLE_E320)
|
||||
INSTALL(FILES
|
||||
neon_manager.hpp
|
||||
DESTINATION ${INCLUDE_DIR}/mpm/dboards
|
||||
)
|
||||
ENDIF(ENABLE_MAGNESIUM)
|
||||
|
||||
40
mpm/include/mpm/dboards/neon_manager.hpp
Normal file
40
mpm/include/mpm/dboards/neon_manager.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright 2018 Ettus Research, a National Instruments Company
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mpm/ad9361/ad9361_ctrl.hpp>
|
||||
#include <mpm/types/lockable.hpp>
|
||||
#include <mpm/types/regs_iface.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace mpm { namespace dboards {
|
||||
class neon_manager// : public dboard_periph_manager
|
||||
{
|
||||
public:
|
||||
neon_manager(const std::string &catalina_spidev);
|
||||
|
||||
/*! Return a reference to the radio chip controls
|
||||
*/
|
||||
mpm::chips::ad9361_ctrl::sptr get_radio_ctrl(){ return _catalina_ctrl; }
|
||||
|
||||
private:
|
||||
mpm::chips::ad9361_ctrl::sptr _catalina_ctrl;
|
||||
};
|
||||
|
||||
}}; /* namespace mpm::dboards */
|
||||
|
||||
#ifdef LIBMPM_PYTHON
|
||||
void export_neon(){
|
||||
LIBMPM_BOOST_PREAMBLE("dboards")
|
||||
using namespace mpm::dboards;
|
||||
bp::class_<mpm::dboards::neon_manager>("neon_manager", bp::init<std::string>())
|
||||
.def("get_radio_ctrl", &mpm::dboards::neon_manager::get_radio_ctrl)
|
||||
;
|
||||
}
|
||||
#endif
|
||||
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, National Instruments Company
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
#
|
||||
@ -13,6 +13,8 @@ ADD_SUBDIRECTORY(types)
|
||||
|
||||
if(ENABLE_MYKONOS)
|
||||
ADD_SUBDIRECTORY(mykonos)
|
||||
elseif(ENABLE_E320)
|
||||
ADD_SUBDIRECTORY(catalina)
|
||||
endif(ENABLE_MYKONOS)
|
||||
|
||||
USRP_PERIPHS_ADD_OBJECT(periphs
|
||||
|
||||
50
mpm/lib/catalina/CMakeLists.txt
Normal file
50
mpm/lib/catalina/CMakeLists.txt
Normal file
@ -0,0 +1,50 @@
|
||||
#
|
||||
# Copyright 2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
########################################################################
|
||||
# This file included, use CMake directory variables
|
||||
########################################################################
|
||||
|
||||
MACRO(CATALINA_APPEND_SOURCES)
|
||||
SET(catalina_sources ${catalina_sources})
|
||||
LIST(APPEND catalina_sources ${ARGV})
|
||||
SET(catalina_sources ${catalina_sources} PARENT_SCOPE)
|
||||
ENDMACRO(CATALINA_APPEND_SOURCES)
|
||||
|
||||
SET (CMAKE_CATALINA_SOURCE_DIR
|
||||
${UHD_HOST_ROOT}/lib/usrp/common
|
||||
)
|
||||
|
||||
CONFIGURE_FILE(
|
||||
${UHD_HOST_ROOT}/include/uhd/version.hpp.in
|
||||
${CMAKE_BINARY_DIR}/include/uhd/version.hpp
|
||||
)
|
||||
|
||||
# TODO: Do something better than listing all the files individually
|
||||
SET(catalina_sources
|
||||
${CMAKE_CATALINA_SOURCE_DIR}/ad9361_ctrl.cpp
|
||||
${CMAKE_CATALINA_SOURCE_DIR}/ad936x_manager.cpp
|
||||
${CMAKE_CATALINA_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp
|
||||
${UHD_HOST_ROOT}/lib/types/filters.cpp
|
||||
${UHD_HOST_ROOT}/lib/types/ranges.cpp
|
||||
${UHD_HOST_ROOT}/lib/types/sensors.cpp
|
||||
${UHD_HOST_ROOT}/lib/types/serial.cpp
|
||||
${UHD_HOST_ROOT}/lib/property_tree.cpp
|
||||
${UHD_HOST_ROOT}/lib/utils/log.cpp
|
||||
${UHD_HOST_ROOT}/lib/version.cpp
|
||||
)
|
||||
|
||||
USRP_PERIPHS_ADD_OBJECT(catalina ${catalina_sources})
|
||||
|
||||
TARGET_INCLUDE_DIRECTORIES(catalina PUBLIC
|
||||
$<BUILD_INTERFACE:${UHD_HOST_ROOT}/lib/include>)
|
||||
TARGET_INCLUDE_DIRECTORIES(catalina PUBLIC
|
||||
$<BUILD_INTERFACE:${UHD_HOST_ROOT}/lib/usrp/common/ad9361_driver>)
|
||||
|
||||
TARGET_INCLUDE_DIRECTORIES(catalina PUBLIC
|
||||
$<BUILD_INTERFACE:${UHD_HOST_ROOT}/lib/include>)
|
||||
TARGET_INCLUDE_DIRECTORIES(dboards PUBLIC
|
||||
$<BUILD_INTERFACE:${UHD_HOST_ROOT}/lib/usrp/common/ad9361_driver>)
|
||||
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, National Instruments Company
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
#
|
||||
@ -12,4 +12,8 @@ if(ENABLE_MAGNESIUM)
|
||||
USRP_PERIPHS_ADD_OBJECT(dboards
|
||||
magnesium_manager.cpp
|
||||
)
|
||||
elseif(ENABLE_E320)
|
||||
USRP_PERIPHS_ADD_OBJECT(dboards
|
||||
neon_manager.cpp
|
||||
)
|
||||
endif(ENABLE_MAGNESIUM)
|
||||
|
||||
79
mpm/lib/dboards/neon_manager.cpp
Normal file
79
mpm/lib/dboards/neon_manager.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright 2018 Ettus Research, a National Instruments Company
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
#include <mpm/ad9361/e320_defaults.hpp>
|
||||
#include <mpm/dboards/neon_manager.hpp>
|
||||
#include <mpm/spi/spi_iface.hpp>
|
||||
#include <mpm/spi/spi_regs_iface.hpp>
|
||||
#include <mpm/types/regs_iface.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <memory>
|
||||
|
||||
using namespace mpm::dboards;
|
||||
using namespace mpm::chips;
|
||||
using namespace mpm::types;
|
||||
using namespace mpm::types::e320;
|
||||
|
||||
namespace { /*anon*/
|
||||
|
||||
constexpr uint32_t AD9361_SPI_WRITE_CMD = 0x00800000;
|
||||
constexpr uint32_t AD9361_SPI_READ_CMD = 0x00000000;
|
||||
constexpr uint32_t AD9361_SPI_ADDR_MASK = 0x003FFF00;
|
||||
constexpr uint32_t AD9361_SPI_ADDR_SHIFT = 8;
|
||||
constexpr uint32_t AD9361_SPI_DATA_MASK = 0x000000FF;
|
||||
constexpr uint32_t AD9361_SPI_DATA_SHIFT = 0;
|
||||
constexpr uint32_t AD9361_SPI_NUM_BITS = 24;
|
||||
constexpr uint32_t AD9361_SPI_SPEED_HZ = 2000000;
|
||||
constexpr int AD9361_SPI_MODE = 1;
|
||||
|
||||
} // namespace /*anon*/
|
||||
|
||||
/*! MPM-style E320 SPI Iface for AD9361 CTRL
|
||||
*
|
||||
*/
|
||||
class e320_ad9361_io_spi : public ad9361_io
|
||||
{
|
||||
public:
|
||||
e320_ad9361_io_spi(regs_iface::sptr regs_iface, uint32_t slave_num) :
|
||||
_regs_iface(regs_iface), _slave_num(slave_num) { }
|
||||
|
||||
~e320_ad9361_io_spi() {/*nop*/}
|
||||
|
||||
uint8_t peek8(uint32_t reg)
|
||||
{
|
||||
return _regs_iface->peek8(reg);
|
||||
}
|
||||
|
||||
void poke8(uint32_t reg, uint8_t val)
|
||||
{
|
||||
_regs_iface->poke8(reg, val);
|
||||
}
|
||||
|
||||
private:
|
||||
regs_iface::sptr _regs_iface;
|
||||
uint32_t _slave_num;
|
||||
};
|
||||
|
||||
neon_manager::neon_manager(const std::string &catalina_spidev)
|
||||
{
|
||||
// Make the MPM-style low level SPI Regs iface
|
||||
auto spi_iface = mpm::spi::make_spi_regs_iface(
|
||||
mpm::spi::spi_iface::make_spidev(catalina_spidev, AD9361_SPI_SPEED_HZ, AD9361_SPI_MODE),
|
||||
AD9361_SPI_ADDR_SHIFT,
|
||||
AD9361_SPI_DATA_SHIFT,
|
||||
AD9361_SPI_READ_CMD,
|
||||
AD9361_SPI_WRITE_CMD);
|
||||
// Make the SPI interface
|
||||
auto spi_io_iface = std::make_shared<e320_ad9361_io_spi>(spi_iface, 0);
|
||||
// Translate from a std shared_ptr to Boost (for legacy compatability)
|
||||
auto spi_io_iface_boost = boost::shared_ptr<e320_ad9361_io_spi>(
|
||||
spi_io_iface.get(),
|
||||
[spi_io_iface](...) mutable { spi_io_iface.reset(); });
|
||||
// Make the actual Catalina Ctrl object
|
||||
_catalina_ctrl = ad9361_ctrl::make_spi(
|
||||
boost::make_shared<e320_ad9361_client_t>(),
|
||||
spi_io_iface_boost);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, National Instruments Company
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
########################################################################
|
||||
@ -10,12 +10,15 @@
|
||||
|
||||
if(MPM_DEVICE STREQUAL "n3xx")
|
||||
ADD_LIBRARY(pyusrp_periphs SHARED pyusrp_periphs/n3xx/pyusrp_periphs.cpp)
|
||||
elseif(MPM_DEVICE STREQUAL "e320")
|
||||
ADD_LIBRARY(pyusrp_periphs SHARED pyusrp_periphs/e320/pyusrp_periphs.cpp)
|
||||
endif(MPM_DEVICE STREQUAL "n3xx")
|
||||
|
||||
TARGET_INCLUDE_DIRECTORIES(pyusrp_periphs PUBLIC
|
||||
${PYTHON_INCLUDE_DIRS}
|
||||
${CMAKE_SOURCE_DIR}/lib/
|
||||
${UHD_HOST_ROOT}/lib/usrp/common
|
||||
${UHD_HOST_ROOT}/lib/usrp/common/ad9361_driver
|
||||
)
|
||||
TARGET_LINK_LIBRARIES(pyusrp_periphs ${Boost_PYTHON_LIBRARY} ${Boost_LIBRARIES} usrp-periphs)
|
||||
ADD_CUSTOM_COMMAND(TARGET pyusrp_periphs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libpyusrp_periphs.so ${CMAKE_CURRENT_BINARY_DIR}/usrp_mpm/libpyusrp_periphs.so)
|
||||
@ -47,7 +50,18 @@ EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -c
|
||||
INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/build/lib/usrp_mpm DESTINATION ${CMAKE_INSTALL_PREFIX}/${USRP_MPM_PYTHON_DIR})
|
||||
INSTALL(PROGRAMS
|
||||
aurora_bist_test.py
|
||||
n3xx_bist
|
||||
usrp_hwd.py
|
||||
DESTINATION ${RUNTIME_DIR}
|
||||
)
|
||||
|
||||
IF (ENABLE_MYKONOS)
|
||||
INSTALL(PROGRAMS
|
||||
n3xx_bist
|
||||
DESTINATION ${RUNTIME_DIR}
|
||||
)
|
||||
ELSEIF (ENABLE_E320)
|
||||
INSTALL(PROGRAMS
|
||||
e320_bist
|
||||
DESTINATION ${RUNTIME_DIR}
|
||||
)
|
||||
ENDIF (ENABLE_MYKONOS)
|
||||
|
||||
37
mpm/python/pyusrp_periphs/e320/pyusrp_periphs.cpp
Normal file
37
mpm/python/pyusrp_periphs/e320/pyusrp_periphs.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2018 Ettus Research, a National Instruments Company
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
|
||||
// include hackery to only include boost python and define the macro here
|
||||
#include <boost/python.hpp>
|
||||
#define LIBMPM_PYTHON
|
||||
#define LIBMPM_BOOST_PREAMBLE(module) \
|
||||
/* Register submodule types */ \
|
||||
namespace bp = boost::python; \
|
||||
bp::object py_module(bp::handle<>(bp::borrowed(PyImport_AddModule("libpyusrp_periphs." module)))); \
|
||||
bp::scope().attr(module) = py_module; \
|
||||
bp::scope io_scope = py_module;
|
||||
|
||||
#include "../converters.hpp"
|
||||
#include <mpm/xbar_iface.hpp>
|
||||
#include <mpm/types/types_python.hpp>
|
||||
#include <mpm/spi/spi_python.hpp>
|
||||
#include <mpm/ad9361/ad9361_ctrl.hpp>
|
||||
#include <mpm/dboards/neon_manager.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace bp = boost::python;
|
||||
|
||||
BOOST_PYTHON_MODULE(libpyusrp_periphs)
|
||||
{
|
||||
bp::object package = bp::scope();
|
||||
package.attr("__path__") = "libpyusrp_periphs";
|
||||
export_converter();
|
||||
export_types();
|
||||
export_spi();
|
||||
export_xbar();
|
||||
export_catalina();
|
||||
export_neon();
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, a National Instruments Company
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
########################################################################
|
||||
@ -12,6 +12,7 @@ SET(USRP_MPM_DBMGR_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/base.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/eiscat.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/neon.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lmk_eiscat.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lmk_mg.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/magnesium.py
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, a National Instruments Company
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
@ -8,7 +8,7 @@ dboards module __init__.py
|
||||
"""
|
||||
from .base import DboardManagerBase
|
||||
from .magnesium import Magnesium
|
||||
from .neon import Neon
|
||||
from .eiscat import EISCAT
|
||||
from .test import test
|
||||
from .unknown import unknown
|
||||
|
||||
|
||||
281
mpm/python/usrp_mpm/dboard_manager/neon.py
Normal file
281
mpm/python/usrp_mpm/dboard_manager/neon.py
Normal file
@ -0,0 +1,281 @@
|
||||
#
|
||||
# Copyright 2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
"""
|
||||
E320 dboard (RF and control) implementation module
|
||||
"""
|
||||
|
||||
import threading
|
||||
import time
|
||||
from six import iterkeys, iteritems
|
||||
from usrp_mpm import lib # Pulls in everything from C++-land
|
||||
from usrp_mpm.bfrfs import BufferFS
|
||||
from usrp_mpm.chips import ADF400x
|
||||
from usrp_mpm.dboard_manager import DboardManagerBase
|
||||
from usrp_mpm.mpmlog import get_logger
|
||||
from usrp_mpm.sys_utils.udev import get_eeprom_paths
|
||||
from usrp_mpm.sys_utils.uio import UIO
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Main dboard control class
|
||||
###############################################################################
|
||||
class Neon(DboardManagerBase):
|
||||
"""
|
||||
Holds all dboard specific information and methods of the neon dboard
|
||||
"""
|
||||
#########################################################################
|
||||
# Overridables
|
||||
#
|
||||
# See DboardManagerBase for documentation on these fields
|
||||
#########################################################################
|
||||
pids = [0xe320]
|
||||
rx_sensor_callback_map = {
|
||||
'ad9361_temperature': 'get_catalina_temp_sensor',
|
||||
}
|
||||
tx_sensor_callback_map = {
|
||||
'ad9361_temperature': 'get_catalina_temp_sensor',
|
||||
}
|
||||
# Maps the chipselects to the corresponding devices:
|
||||
spi_chipselect = {"catalina": 0,
|
||||
"adf4002": 1}
|
||||
### End of overridables #################################################
|
||||
# This map describes how the user data is stored in EEPROM. If a dboard rev
|
||||
# changes the way the EEPROM is used, we add a new entry. If a dboard rev
|
||||
# is not found in the map, then we go backward until we find a suitable rev
|
||||
user_eeprom = {
|
||||
0: {
|
||||
'label': "e0004000.i2c",
|
||||
'offset': 1024,
|
||||
'max_size': 32786 - 1024,
|
||||
'alignment': 1024,
|
||||
},
|
||||
}
|
||||
|
||||
default_master_clock_rate = 16e6
|
||||
MIN_MASTER_CLK_RATE = 220e3
|
||||
MAX_MASTER_CLK_RATE = 61.44e6
|
||||
|
||||
def __init__(self, slot_idx, **kwargs):
|
||||
super(Neon, self).__init__(slot_idx, **kwargs)
|
||||
self.log = get_logger("Neon-{}".format(slot_idx))
|
||||
self.log.trace("Initializing Neon daughterboard, slot index %d",
|
||||
self.slot_idx)
|
||||
self.rev = int(self.device_info['rev'])
|
||||
self.log.trace("This is a rev: {}".format(chr(65 + self.rev)))
|
||||
# These will get updated during init()
|
||||
self.master_clock_rate = None
|
||||
# Predeclare some attributes to make linter happy:
|
||||
self.catalina = None
|
||||
self.eeprom_fs = None
|
||||
self.eeprom_path = None
|
||||
# Now initialize all peripherals. If that doesn't work, put this class
|
||||
# into a non-functional state (but don't crash, or we can't talk to it
|
||||
# any more):
|
||||
try:
|
||||
self._init_periphs()
|
||||
self._periphs_initialized = True
|
||||
except Exception as ex:
|
||||
self.log.error("Failed to initialize peripherals: %s",
|
||||
str(ex))
|
||||
self._periphs_initialized = False
|
||||
|
||||
def _init_periphs(self):
|
||||
"""
|
||||
Initialize power and peripherals that don't need user-settings
|
||||
"""
|
||||
self.log.debug("Loading C++ drivers...")
|
||||
# Setup the ADF4002
|
||||
adf4002_spi = lib.spi.make_spidev(
|
||||
str(self._spi_nodes['adf4002']),
|
||||
1000000, # Speed (Hz)
|
||||
0 # SPI mode
|
||||
)
|
||||
self.log.trace("Initializing ADF4002.")
|
||||
from usrp_mpm.periph_manager.e320 import E320_DEFAULT_INT_CLOCK_FREQ
|
||||
self.adf4002 = ADF400x(adf4002_spi,
|
||||
freq=E320_DEFAULT_INT_CLOCK_FREQ,
|
||||
parent_log=self.log)
|
||||
# Setup Catalina / the Neon Manager
|
||||
self._device = lib.dboards.neon_manager(
|
||||
self._spi_nodes['catalina']
|
||||
)
|
||||
self.catalina = self._device.get_radio_ctrl()
|
||||
self.log.trace("Loaded C++ drivers.")
|
||||
self._init_cat_api(self.catalina)
|
||||
self.eeprom_fs, self.eeprom_path = self._init_user_eeprom(
|
||||
self._get_user_eeprom_info(self.rev)
|
||||
)
|
||||
|
||||
def _init_cat_api(self, cat):
|
||||
"""
|
||||
Propagate the C++ Catalina API into Python land.
|
||||
"""
|
||||
def export_method(obj, method):
|
||||
" Export a method object, including docstring "
|
||||
meth_obj = getattr(obj, method)
|
||||
def func(*args):
|
||||
" Functor for storing docstring too "
|
||||
return meth_obj(*args)
|
||||
func.__doc__ = meth_obj.__doc__
|
||||
return func
|
||||
self.log.trace("Forwarding AD9361 methods to Neon class...")
|
||||
for method in [
|
||||
x for x in dir(self.catalina)
|
||||
if not x.startswith("_") and \
|
||||
callable(getattr(self.catalina, x))]:
|
||||
self.log.trace("adding {}".format(method))
|
||||
setattr(self, method, export_method(cat, method))
|
||||
|
||||
def _get_user_eeprom_info(self, rev):
|
||||
"""
|
||||
Return an EEPROM access map (from self.user_eeprom) based on the rev.
|
||||
"""
|
||||
rev_for_lookup = rev
|
||||
while rev_for_lookup not in self.user_eeprom:
|
||||
if rev_for_lookup < 0:
|
||||
raise RuntimeError("Could not find a user EEPROM map for "
|
||||
"revision %d!", rev)
|
||||
rev_for_lookup -= 1
|
||||
assert rev_for_lookup in self.user_eeprom, \
|
||||
"Invalid EEPROM lookup rev!"
|
||||
return self.user_eeprom[rev_for_lookup]
|
||||
|
||||
def _init_user_eeprom(self, eeprom_info):
|
||||
"""
|
||||
Reads out user-data EEPROM, and intializes a BufferFS object from that.
|
||||
"""
|
||||
self.log.trace("Initializing EEPROM user data...")
|
||||
eeprom_paths = get_eeprom_paths(eeprom_info.get('label'))
|
||||
self.log.trace("Found the following EEPROM paths: `{}'".format(
|
||||
eeprom_paths))
|
||||
eeprom_path = eeprom_paths[self.slot_idx]
|
||||
self.log.trace("Selected EEPROM path: `{}'".format(eeprom_path))
|
||||
user_eeprom_offset = eeprom_info.get('offset', 0)
|
||||
self.log.trace("Selected EEPROM offset: %d", user_eeprom_offset)
|
||||
user_eeprom_data = open(eeprom_path, 'rb').read()[user_eeprom_offset:]
|
||||
self.log.trace("Total EEPROM size is: %d bytes", len(user_eeprom_data))
|
||||
return BufferFS(
|
||||
user_eeprom_data,
|
||||
max_size=eeprom_info.get('max_size'),
|
||||
alignment=eeprom_info.get('alignment', 1024),
|
||||
log=self.log
|
||||
), eeprom_path
|
||||
|
||||
def init(self, args):
|
||||
if not self._periphs_initialized:
|
||||
error_msg = "Cannot run init(), peripherals are not initialized!"
|
||||
self.log.error(error_msg)
|
||||
raise RuntimeError(error_msg)
|
||||
master_clock_rate = \
|
||||
float(args.get('master_clock_rate',
|
||||
self.default_master_clock_rate))
|
||||
assert self.MIN_MASTER_CLK_RATE <= master_clock_rate <= self.MAX_MASTER_CLK_RATE, \
|
||||
"Invalid master clock rate: {:.02f} MHz".format(
|
||||
master_clock_rate / 1e6)
|
||||
master_clock_rate_changed = master_clock_rate != self.master_clock_rate
|
||||
if master_clock_rate_changed:
|
||||
self.master_clock_rate = master_clock_rate
|
||||
self.log.debug("Updating master clock rate to {:.02f} MHz!".format(
|
||||
self.master_clock_rate / 1e6
|
||||
))
|
||||
# Some default chains on -- needed for setup purposes
|
||||
self.catalina.set_active_chains(True, False, True, False)
|
||||
self.catalina.set_clock_rate(self.master_clock_rate)
|
||||
|
||||
return True
|
||||
|
||||
def get_user_eeprom_data(self):
|
||||
"""
|
||||
Return a dict of blobs stored in the user data section of the EEPROM.
|
||||
"""
|
||||
return {
|
||||
blob_id: self.eeprom_fs.get_blob(blob_id)
|
||||
for blob_id in iterkeys(self.eeprom_fs.entries)
|
||||
}
|
||||
|
||||
def set_user_eeprom_data(self, eeprom_data):
|
||||
"""
|
||||
Update the local EEPROM with the data from eeprom_data.
|
||||
|
||||
The actual writing to EEPROM can take some time, and is thus kicked
|
||||
into a background task. Don't call set_user_eeprom_data() quickly in
|
||||
succession. Also, while the background task is running, reading the
|
||||
EEPROM is unavailable and MPM won't be able to reboot until it's
|
||||
completed.
|
||||
However, get_user_eeprom_data() will immediately return the correct
|
||||
data after this method returns.
|
||||
"""
|
||||
for blob_id, blob in iteritems(eeprom_data):
|
||||
self.eeprom_fs.set_blob(blob_id, blob)
|
||||
self.log.trace("Writing EEPROM info to `{}'".format(self.eeprom_path))
|
||||
eeprom_offset = self.user_eeprom[self.rev]['offset']
|
||||
def _write_to_eeprom_task(path, offset, data, log):
|
||||
" Writer task: Actually write to file "
|
||||
# Note: This can be sped up by only writing sectors that actually
|
||||
# changed. To do so, this function would need to read out the
|
||||
# current state of the file, do some kind of diff, and then seek()
|
||||
# to the different sectors. When very large blobs are being
|
||||
# written, it doesn't actually help all that much, of course,
|
||||
# because in that case, we'd anyway be changing most of the EEPROM.
|
||||
with open(path, 'r+b') as eeprom_file:
|
||||
log.trace("Seeking forward to `{}'".format(offset))
|
||||
eeprom_file.seek(eeprom_offset)
|
||||
log.trace("Writing a total of {} bytes.".format(
|
||||
len(self.eeprom_fs.buffer)))
|
||||
eeprom_file.write(data)
|
||||
log.trace("EEPROM write complete.")
|
||||
thread_id = "eeprom_writer_task_{}".format(self.slot_idx)
|
||||
if any([x.name == thread_id for x in threading.enumerate()]):
|
||||
# Should this be fatal?
|
||||
self.log.warn("Another EEPROM writer thread is already active!")
|
||||
writer_task = threading.Thread(
|
||||
target=_write_to_eeprom_task,
|
||||
args=(
|
||||
self.eeprom_path,
|
||||
eeprom_offset,
|
||||
self.eeprom_fs.buffer,
|
||||
self.log
|
||||
),
|
||||
name=thread_id,
|
||||
)
|
||||
writer_task.start()
|
||||
# Now return and let the copy finish on its own. The thread will detach
|
||||
# and MPM won't terminate this process until the thread is complete.
|
||||
# This does not stop anyone from killing this process (and the thread)
|
||||
# while the EEPROM write is happening, though.
|
||||
|
||||
def get_master_clock_rate(self):
|
||||
" Return master clock rate (== sampling rate) "
|
||||
return self.master_clock_rate
|
||||
|
||||
def update_ref_clock_freq(self, freq):
|
||||
"""Update the reference clock frequency"""
|
||||
self.adf4002.set_ref_freq(freq)
|
||||
|
||||
##########################################################################
|
||||
# Sensors
|
||||
##########################################################################
|
||||
# TODO add sensors
|
||||
def get_ad9361_lo_lock(self, which):
|
||||
"""
|
||||
Return LO lock status (Boolean!) of AD9361. 'which' must be
|
||||
either 'tx' or 'rx'
|
||||
"""
|
||||
# return self.catalina.
|
||||
|
||||
# uhd::sensor_value_t e300_impl::_get_fe_pll_lock(const bool is_tx)
|
||||
# {
|
||||
# const uint32_t st =
|
||||
# _global_regs->peek32(global_regs::RB32_CORE_PLL);
|
||||
# const bool locked = is_tx ? ((st & 0x1) > 0) : ((st & 0x2) > 0);
|
||||
# return sensor_value_t("LO", locked, "locked", "unlocked");
|
||||
# }
|
||||
#return self.mykonos.get_lo_locked(which.upper())
|
||||
#FIXME: Implement on RevB
|
||||
time.sleep(5)
|
||||
return True
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, National Instruments Company
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
########################################################################
|
||||
@ -13,6 +13,8 @@ SET(USRP_MPM_PERIPHMGR_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/base.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/n3xx.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/n3xx_periphs.py
|
||||
)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/e320.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/e320_periphs.py
|
||||
)
|
||||
LIST(APPEND USRP_MPM_FILES ${USRP_MPM_PERIPHMGR_FILES})
|
||||
SET(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE)
|
||||
|
||||
671
mpm/python/usrp_mpm/periph_manager/e320.py
Normal file
671
mpm/python/usrp_mpm/periph_manager/e320.py
Normal file
@ -0,0 +1,671 @@
|
||||
#
|
||||
# Copyright 2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
"""
|
||||
E320 implementation module
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import bisect
|
||||
import copy
|
||||
import threading
|
||||
from six import iteritems, itervalues
|
||||
from usrp_mpm.components import ZynqComponents
|
||||
from usrp_mpm.dboard_manager import Neon
|
||||
from usrp_mpm.mpmtypes import SID
|
||||
from usrp_mpm.mpmutils import assert_compat_number, str2bool
|
||||
from usrp_mpm.periph_manager import PeriphManagerBase
|
||||
from usrp_mpm.rpc_server import no_rpc
|
||||
from usrp_mpm.sys_utils import dtoverlay
|
||||
from usrp_mpm.sys_utils.udev import get_spidev_nodes
|
||||
from usrp_mpm.xports import XportMgrUDP, XportMgrLiberio
|
||||
from usrp_mpm.periph_manager.e320_periphs import MboardRegsControl
|
||||
|
||||
E320_DEFAULT_INT_CLOCK_FREQ = 20e6
|
||||
E320_DEFAULT_EXT_CLOCK_FREQ = 10e6
|
||||
E320_DEFAULT_CLOCK_SOURCE = 'internal'
|
||||
E320_DEFAULT_TIME_SOURCE = 'internal'
|
||||
E320_DEFAULT_ENABLE_GPS = True
|
||||
E320_DEFAULT_FPGPIO_VOLTAGE = 0
|
||||
E320_FPGA_COMPAT = (3, 0)
|
||||
E320_MONITOR_THREAD_INTERVAL = 1.0 # seconds # TODO Verify this
|
||||
E320_DBOARD_SLOT_IDX = 0
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Transport managers
|
||||
###############################################################################
|
||||
class E320XportMgrUDP(XportMgrUDP):
|
||||
"E320-specific UDP configuration"
|
||||
xbar_dev = "/dev/crossbar0"
|
||||
iface_config = {
|
||||
'sfp0': {
|
||||
'label': 'misc-enet-regs',
|
||||
'xbar': 0,
|
||||
'xbar_port': 0,
|
||||
'ctrl_src_addr': 0,
|
||||
}
|
||||
}
|
||||
|
||||
class E320XportMgrLiberio(XportMgrLiberio):
|
||||
" E320-specific Liberio configuration "
|
||||
max_chan = 6
|
||||
xbar_dev = "/dev/crossbar0"
|
||||
xbar_port = 1
|
||||
|
||||
###############################################################################
|
||||
# Main Class
|
||||
###############################################################################
|
||||
class e320(ZynqComponents, PeriphManagerBase):
|
||||
"""
|
||||
Holds E320 specific attributes and methods
|
||||
"""
|
||||
#########################################################################
|
||||
# Overridables
|
||||
#
|
||||
# See PeriphManagerBase for documentation on these fields
|
||||
#########################################################################
|
||||
description = "E300-Series Device"
|
||||
pids = {0xe320: 'e320'}
|
||||
mboard_eeprom_addr = "e0004000.i2c"
|
||||
mboard_eeprom_offset = 0
|
||||
mboard_eeprom_max_len = 256
|
||||
mboard_info = {"type": "e3xx",
|
||||
"product": "e320"
|
||||
}
|
||||
mboard_max_rev = 2 # RevB
|
||||
mboard_sensor_callback_map = {
|
||||
# FIXME add sensors
|
||||
}
|
||||
max_num_dboards = 1
|
||||
crossbar_base_port = 2 # It's 2 because 0,1 are SFP,DMA
|
||||
|
||||
# We're on a Zynq target, so the following two come from the Zynq standard
|
||||
# device tree overlay (tree/arch/arm/boot/dts/zynq-7000.dtsi)
|
||||
dboard_spimaster_addrs = ["e0006000.spi", "e0007000.spi"]
|
||||
# E320-specific settings
|
||||
# Label for the mboard UIO
|
||||
mboard_regs_label = "mboard-regs"
|
||||
# Override the list of updateable components
|
||||
updateable_components = {
|
||||
'fpga': {
|
||||
'callback': "update_fpga",
|
||||
'path': '/lib/firmware/{}.bin',
|
||||
'reset': True,
|
||||
},
|
||||
'dts': {
|
||||
'callback': "update_dts",
|
||||
'path': '/lib/firmware/{}.dts',
|
||||
'output': '/lib/firmware/{}.dtbo',
|
||||
'reset': False,
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def list_required_dt_overlays(device_info):
|
||||
"""
|
||||
Lists device tree overlays that need to be applied before this class can
|
||||
be used. List of strings.
|
||||
Are applied in order.
|
||||
|
||||
eeprom_md -- Dictionary of info read out from the mboard EEPROM
|
||||
device_args -- Arbitrary dictionary of info, typically user-defined
|
||||
"""
|
||||
return [device_info['product']]
|
||||
|
||||
###########################################################################
|
||||
# Ctor and device initialization tasks
|
||||
###########################################################################
|
||||
def __init__(self, args):
|
||||
super(e320, self).__init__(args)
|
||||
if not self._device_initialized:
|
||||
# Don't try and figure out what's going on. Just give up.
|
||||
return
|
||||
self._tear_down = False
|
||||
self._status_monitor_thread = None
|
||||
self._ext_clock_freq = E320_DEFAULT_EXT_CLOCK_FREQ
|
||||
self._clock_source = None
|
||||
self._time_source = None
|
||||
self._available_endpoints = list(range(256))
|
||||
self.dboard = self.dboards[E320_DBOARD_SLOT_IDX]
|
||||
try:
|
||||
self._init_peripherals(args)
|
||||
except Exception as ex:
|
||||
self.log.error("Failed to initialize motherboard: %s", str(ex))
|
||||
self._initialization_status = str(ex)
|
||||
self._device_initialized = False
|
||||
|
||||
def _init_dboards(self, _, override_dboard_pids, default_args):
|
||||
"""
|
||||
Initialize all the daughterboards
|
||||
|
||||
(dboard_infos) -- N/A
|
||||
override_dboard_pids -- List of dboard PIDs to force
|
||||
default_args -- Default args
|
||||
"""
|
||||
# Override the base class's implementation in order to avoid initializing our one "dboard"
|
||||
# in the same way that, for example, N310's dboards are initialized. Specifically,
|
||||
# - skip dboard EEPROM setup (we don't have one)
|
||||
# - change the way we handle SPI devices
|
||||
if override_dboard_pids:
|
||||
self.log.warning("Overriding daughterboard PIDs with: {}"
|
||||
.format(override_dboard_pids))
|
||||
raise NotImplementedError("Can't override dboard pids")
|
||||
# The DBoard PID is the same as the MBoard PID
|
||||
db_pid = list(self.pids.keys())[0]
|
||||
# Set up the SPI nodes
|
||||
spi_nodes = []
|
||||
for spi_addr in self.dboard_spimaster_addrs:
|
||||
for spi_node in get_spidev_nodes(spi_addr):
|
||||
bisect.insort(spi_nodes, spi_node)
|
||||
self.log.trace("Found spidev nodes: {0}".format(spi_nodes))
|
||||
|
||||
if not spi_nodes:
|
||||
self.log.warning("No SPI nodes for dboard %d.", E320_DBOARD_SLOT_IDX)
|
||||
dboard_info = {
|
||||
'eeprom_md': self.mboard_info,
|
||||
'eeprom_rawdata': self._eeprom_rawdata,
|
||||
'pid': db_pid,
|
||||
'spi_nodes': spi_nodes,
|
||||
'default_args': default_args,
|
||||
}
|
||||
# This will actually instantiate the dboard class:
|
||||
self.dboards.append(Neon(E320_DBOARD_SLOT_IDX, **dboard_info))
|
||||
self.log.info("Found %d daughterboard(s).", len(self.dboards))
|
||||
|
||||
def _check_fpga_compat(self):
|
||||
" Throw an exception if the compat numbers don't match up "
|
||||
actual_compat = self.mboard_regs_control.get_compat_number()
|
||||
self.log.debug("Actual FPGA compat number: {:d}.{:d}".format(
|
||||
actual_compat[0], actual_compat[1]
|
||||
))
|
||||
assert_compat_number(
|
||||
E320_FPGA_COMPAT,
|
||||
self.mboard_regs_control.get_compat_number(),
|
||||
component="FPGA",
|
||||
fail_on_old_minor=True,
|
||||
log=self.log
|
||||
)
|
||||
|
||||
def _init_ref_clock_and_time(self, default_args):
|
||||
"""
|
||||
Initialize clock and time sources. After this function returns, the
|
||||
reference signals going to the FPGA are valid.
|
||||
"""
|
||||
self._ext_clock_freq = float(
|
||||
default_args.get('ext_clock_freq', E320_DEFAULT_EXT_CLOCK_FREQ)
|
||||
)
|
||||
if not self.dboards:
|
||||
self.log.warning(
|
||||
"No dboards found, skipping setting clock and time source "
|
||||
"configuration."
|
||||
)
|
||||
self._clock_source = E320_DEFAULT_CLOCK_SOURCE
|
||||
self._time_source = E320_DEFAULT_TIME_SOURCE
|
||||
else:
|
||||
self.set_clock_source(
|
||||
default_args.get('clock_source', E320_DEFAULT_CLOCK_SOURCE)
|
||||
)
|
||||
self.set_time_source(
|
||||
default_args.get('time_source', E320_DEFAULT_TIME_SOURCE)
|
||||
)
|
||||
|
||||
def _monitor_status(self):
|
||||
"""
|
||||
Status monitoring thread: This should be executed in a thread. It will
|
||||
continuously monitor status of the following peripherals:
|
||||
|
||||
- GPS lock
|
||||
"""
|
||||
self.log.trace("Launching monitor loop...")
|
||||
cond = threading.Condition()
|
||||
cond.acquire()
|
||||
while not self._tear_down:
|
||||
gps_locked = self.get_gps_lock_sensor()['value'] == 'true'
|
||||
# Now wait
|
||||
if cond.wait_for(
|
||||
lambda: self._tear_down,
|
||||
E320_MONITOR_THREAD_INTERVAL):
|
||||
break
|
||||
cond.release()
|
||||
self.log.trace("Terminating monitor loop.")
|
||||
|
||||
def _init_peripherals(self, args):
|
||||
"""
|
||||
Turn on all peripherals. This may throw an error on failure, so make
|
||||
sure to catch it.
|
||||
|
||||
Peripherals are initialized in the order of least likely to fail, to most
|
||||
likely.
|
||||
"""
|
||||
# Sanity checks
|
||||
assert self.mboard_info.get('product') in self.pids.values(), \
|
||||
"Device product could not be determined!"
|
||||
# Init Mboard Regs
|
||||
self.mboard_regs_control = MboardRegsControl(
|
||||
self.mboard_regs_label, self.log)
|
||||
self.mboard_regs_control.get_git_hash()
|
||||
self.mboard_regs_control.get_build_timestamp()
|
||||
self._check_fpga_compat()
|
||||
self._update_fpga_type()
|
||||
# Init peripherals
|
||||
self.enable_gps(
|
||||
enable=str2bool(
|
||||
args.get('enable_gps', E320_DEFAULT_ENABLE_GPS)
|
||||
)
|
||||
)
|
||||
self.enable_fp_gpio(
|
||||
voltage=args.get(
|
||||
'fp_gpio_voltage',
|
||||
E320_DEFAULT_FPGPIO_VOLTAGE
|
||||
)
|
||||
)
|
||||
# Init clocking
|
||||
self._init_ref_clock_and_time(args)
|
||||
# Init CHDR transports
|
||||
self._xport_mgrs = {
|
||||
'udp': E320XportMgrUDP(self.log.getChild('UDP')),
|
||||
'liberio': E320XportMgrLiberio(self.log.getChild('liberio')),
|
||||
}
|
||||
# Spawn status monitoring thread
|
||||
self.log.trace("Spawning status monitor thread...")
|
||||
self._status_monitor_thread = threading.Thread(
|
||||
target=self._monitor_status,
|
||||
name="E320StatusMonitorThread",
|
||||
daemon=True,
|
||||
)
|
||||
self._status_monitor_thread.start()
|
||||
# Init complete.
|
||||
self.log.debug("mboard info: {}".format(self.mboard_info))
|
||||
|
||||
###########################################################################
|
||||
# Session init and deinit
|
||||
###########################################################################
|
||||
def init(self, args):
|
||||
"""
|
||||
Calls init() on the parent class, and then programs the Ethernet
|
||||
dispatchers accordingly.
|
||||
"""
|
||||
if not self._device_initialized:
|
||||
self.log.warning(
|
||||
"Cannot run init(), device was never fully initialized!")
|
||||
return False
|
||||
if args.get("clock_source", "") != "":
|
||||
self.set_clock_source(args.get("clock_source"))
|
||||
if args.get("time_source", "") != "":
|
||||
self.set_time_source(args.get("time_source"))
|
||||
result = super(e320, self).init(args)
|
||||
for xport_mgr in itervalues(self._xport_mgrs):
|
||||
xport_mgr.init(args)
|
||||
return result
|
||||
|
||||
def deinit(self):
|
||||
"""
|
||||
Clean up after a UHD session terminates.
|
||||
"""
|
||||
if not self._device_initialized:
|
||||
self.log.warning(
|
||||
"Cannot run deinit(), device was never fully initialized!")
|
||||
return
|
||||
super(e320, self).deinit()
|
||||
for xport_mgr in itervalues(self._xport_mgrs):
|
||||
xport_mgr.deinit()
|
||||
self.log.trace("Resetting SID pool...")
|
||||
self._available_endpoints = list(range(256))
|
||||
|
||||
def tear_down(self):
|
||||
"""
|
||||
Tear down all members that need to be specially handled before
|
||||
deconstruction.
|
||||
For E320, this means the overlay.
|
||||
"""
|
||||
self.log.trace("Tearing down E320 device...")
|
||||
self._tear_down = True
|
||||
if self._device_initialized:
|
||||
self._status_monitor_thread.join(3 * E320_MONITOR_THREAD_INTERVAL)
|
||||
if self._status_monitor_thread.is_alive():
|
||||
self.log.error("Could not terminate monitor thread! This could result in resource leaks.")
|
||||
active_overlays = self.list_active_overlays()
|
||||
self.log.trace("E320 has active device tree overlays: {}".format(
|
||||
active_overlays
|
||||
))
|
||||
for overlay in active_overlays:
|
||||
dtoverlay.rm_overlay(overlay)
|
||||
|
||||
###########################################################################
|
||||
# Transport API
|
||||
###########################################################################
|
||||
def request_xport(
|
||||
self,
|
||||
dst_address,
|
||||
suggested_src_address,
|
||||
xport_type
|
||||
):
|
||||
"""
|
||||
See PeriphManagerBase.request_xport() for docs.
|
||||
"""
|
||||
# Try suggested address first, then just pick the first available one:
|
||||
src_address = suggested_src_address
|
||||
if src_address not in self._available_endpoints:
|
||||
if not self._available_endpoints:
|
||||
raise RuntimeError(
|
||||
"Depleted pool of SID endpoints for this device!")
|
||||
else:
|
||||
src_address = self._available_endpoints[0]
|
||||
sid = SID(src_address << 16 | dst_address)
|
||||
# Note: This SID may change its source address!
|
||||
self.log.trace(
|
||||
"request_xport(dst=0x%04X, suggested_src_address=0x%04X, xport_type=%s): " \
|
||||
"operating on temporary SID: %s",
|
||||
dst_address, suggested_src_address, str(xport_type), str(sid))
|
||||
# FIXME token!
|
||||
assert self.mboard_info['rpc_connection'] in ('remote', 'local')
|
||||
if self.mboard_info['rpc_connection'] == 'remote':
|
||||
return self._xport_mgrs['udp'].request_xport(
|
||||
sid,
|
||||
xport_type,
|
||||
)
|
||||
elif self.mboard_info['rpc_connection'] == 'local':
|
||||
return self._xport_mgrs['liberio'].request_xport(
|
||||
sid,
|
||||
xport_type,
|
||||
)
|
||||
|
||||
def commit_xport(self, xport_info):
|
||||
"""
|
||||
See PeriphManagerBase.commit_xport() for docs.
|
||||
|
||||
Reminder: All connections are incoming, i.e. "send" or "TX" means
|
||||
remote device to local device, and "receive" or "RX" means this local
|
||||
device to remote device. "Remote device" can be, for example, a UHD
|
||||
session.
|
||||
"""
|
||||
## Go, go, go
|
||||
assert self.mboard_info['rpc_connection'] in ('remote', 'local')
|
||||
sid = SID(xport_info['send_sid'])
|
||||
self._available_endpoints.remove(sid.src_ep)
|
||||
self.log.debug("Committing transport for SID %s, xport info: %s",
|
||||
str(sid), str(xport_info))
|
||||
if self.mboard_info['rpc_connection'] == 'remote':
|
||||
return self._xport_mgrs['udp'].commit_xport(sid, xport_info)
|
||||
elif self.mboard_info['rpc_connection'] == 'local':
|
||||
return self._xport_mgrs['liberio'].commit_xport(sid, xport_info)
|
||||
|
||||
###########################################################################
|
||||
# Device info
|
||||
###########################################################################
|
||||
def get_device_info_dyn(self):
|
||||
"""
|
||||
Append the device info with current IP addresses.
|
||||
"""
|
||||
if not self._device_initialized:
|
||||
return {}
|
||||
device_info = self._xport_mgrs['udp'].get_xport_info()
|
||||
device_info.update({
|
||||
'fpga_version': "{}.{}".format(
|
||||
*self.mboard_regs_control.get_compat_number()),
|
||||
'fpga': self.updateable_components.get('fpga', {}).get('type',""),
|
||||
})
|
||||
return device_info
|
||||
|
||||
###########################################################################
|
||||
# Clock/Time API
|
||||
###########################################################################
|
||||
def get_clock_sources(self):
|
||||
" Lists all available clock sources. "
|
||||
self.log.trace("Listing available clock sources...")
|
||||
return ('external', 'internal', 'gpsdo')
|
||||
|
||||
def get_clock_source(self):
|
||||
" Returns the currently selected clock source "
|
||||
return self._clock_source
|
||||
|
||||
def set_clock_source(self, *args):
|
||||
"""
|
||||
Switch reference clock.
|
||||
|
||||
Throws if clock_source is not a valid value.
|
||||
"""
|
||||
clock_source = args[0]
|
||||
assert clock_source in self.get_clock_sources()
|
||||
self.log.debug("Setting clock source to `{}'".format(clock_source))
|
||||
if clock_source == self.get_clock_source():
|
||||
self.log.trace("Nothing to do -- clock source already set.")
|
||||
return
|
||||
self._clock_source = clock_source
|
||||
ref_clk_freq = self.get_ref_clock_freq()
|
||||
self.mboard_regs_control.set_clock_source(clock_source, ref_clk_freq)
|
||||
self.log.debug("Reference clock frequency is: {} MHz".format(
|
||||
ref_clk_freq/1e6
|
||||
))
|
||||
self.dboard.update_ref_clock_freq(ref_clk_freq)
|
||||
|
||||
def set_ref_clock_freq(self, freq):
|
||||
"""
|
||||
Tell our USRP what the frequency of the external reference clock is.
|
||||
|
||||
Will throw if it's not a valid value.
|
||||
"""
|
||||
# Other frequencies have not been tested
|
||||
assert freq in (10e6, 20e6)
|
||||
self.log.debug("We've been told the external reference clock " \
|
||||
"frequency is {} MHz.".format(freq / 1e6))
|
||||
if self._ext_clock_freq == freq:
|
||||
self.log.trace("New external reference clock frequency " \
|
||||
"assignment matches previous assignment. Ignoring " \
|
||||
"update command.")
|
||||
return
|
||||
self._ext_clock_freq = freq
|
||||
if self.get_clock_source() == 'external':
|
||||
for slot, dboard in enumerate(self.dboards):
|
||||
if hasattr(dboard, 'update_ref_clock_freq'):
|
||||
self.log.trace(
|
||||
"Updating reference clock on dboard %d to %f MHz...",
|
||||
slot, freq/1e6
|
||||
)
|
||||
dboard.update_ref_clock_freq(freq)
|
||||
|
||||
|
||||
def get_ref_clock_freq(self):
|
||||
" Returns the currently active reference clock frequency"
|
||||
clock_source = self.get_clock_source()
|
||||
if clock_source == "internal" or clock_source == "gpsdo":
|
||||
return E320_DEFAULT_INT_CLOCK_FREQ
|
||||
elif clock_source == "external":
|
||||
return self._ext_clock_freq
|
||||
|
||||
def get_time_sources(self):
|
||||
" Returns list of valid time sources "
|
||||
return ['internal', 'external', 'gpsdo']
|
||||
|
||||
def get_time_source(self):
|
||||
" Return the currently selected time source "
|
||||
return self._time_source
|
||||
|
||||
def set_time_source(self, time_source):
|
||||
" Set a time source "
|
||||
assert time_source in self.get_time_sources()
|
||||
if time_source == self.get_time_source():
|
||||
self.log.trace("Nothing to do -- time source already set.")
|
||||
return
|
||||
self._time_source = time_source
|
||||
self.mboard_regs_control.set_time_source(time_source, self.get_ref_clock_freq())
|
||||
|
||||
###########################################################################
|
||||
# Hardware peripheral controls
|
||||
###########################################################################
|
||||
|
||||
def set_fp_gpio_master(self, value):
|
||||
"""set driver for front panel GPIO
|
||||
Arguments:
|
||||
value {unsigned} -- value is a single bit bit mask of 12 pins GPIO
|
||||
"""
|
||||
self.mboard_regs_control.set_fp_gpio_master(value)
|
||||
|
||||
def get_fp_gpio_master(self):
|
||||
"""get "who" is driving front panel gpio
|
||||
The return value is a bit mask of 8 pins GPIO.
|
||||
0: means the pin is driven by PL
|
||||
1: means the pin is driven by PS
|
||||
"""
|
||||
return self.mboard_regs_control.get_fp_gpio_master()
|
||||
|
||||
def set_fp_gpio_radio_src(self, value):
|
||||
"""set driver for front panel GPIO
|
||||
Arguments:
|
||||
value {unsigned} -- value is 2-bit bit mask of 8 pins GPIO
|
||||
00: means the pin is driven by radio 0
|
||||
01: means the pin is driven by radio 1
|
||||
"""
|
||||
self.mboard_regs_control.set_fp_gpio_radio_src(value)
|
||||
|
||||
def get_fp_gpio_radio_src(self):
|
||||
"""get which radio is driving front panel gpio
|
||||
The return value is 2-bit bit mask of 8 pins GPIO.
|
||||
00: means the pin is driven by radio 0
|
||||
01: means the pin is driven by radio 1
|
||||
"""
|
||||
return self.mboard_regs_control.get_fp_gpio_radio_src()
|
||||
|
||||
def enable_gps(self, enable):
|
||||
"""
|
||||
Turn power to the GPS (CLK_GPS_PWR_EN) off or on.
|
||||
"""
|
||||
self.mboard_regs_control.enable_gps(enable)
|
||||
|
||||
def enable_fp_gpio(self, voltage):
|
||||
"""
|
||||
Turn power to the front panel GPIO off or on and set voltage
|
||||
to (1.8, 2.5, 3.3V) and setting to 0 turns off GPIO.
|
||||
"""
|
||||
self.log.trace("{} power to front-panel GPIO".format(
|
||||
"Enabling" if voltage == 0 else "Disabling"
|
||||
))
|
||||
self.mboard_regs_control.enable_fp_gpio(voltage)
|
||||
|
||||
def set_fp_gpio_voltage(self, value):
|
||||
"""
|
||||
Set Front Panel GPIO voltage (1.8, 2.5 or 3.3 Volts)
|
||||
"""
|
||||
self.log.trace("Setting front-panel GPIO voltage to {:3.1f} V".format(value))
|
||||
self.mboard_regs_control.set_fp_gpio_voltage(value)
|
||||
|
||||
def get_fp_gpio_voltage(self):
|
||||
"""
|
||||
Get Front Panel GPIO voltage (1.8, 2.5 or 3.3 Volts)
|
||||
"""
|
||||
value = self.mboard_regs_control.get_fp_gpio_voltage()
|
||||
self.log.trace("Current front-panel GPIO voltage {:3.1f} V".format(value))
|
||||
return value
|
||||
|
||||
def set_channel_mode(self, channel_mode):
|
||||
"Set channel mode in FPGA and select which tx channel to use"
|
||||
self.mboard_regs_control.set_channel_mode(channel_mode)
|
||||
|
||||
###########################################################################
|
||||
# Sensors
|
||||
###########################################################################
|
||||
def get_temp_sensor(self):
|
||||
"""
|
||||
Get temperature sensor reading of the E320.
|
||||
"""
|
||||
# TODO: This is Catalina's temperature. Do we want to return a different temp?
|
||||
return self.catalina.get_temperature()
|
||||
|
||||
def get_gps_lock_sensor(self):
|
||||
"""
|
||||
Get lock status of GPS as a sensor dict
|
||||
"""
|
||||
self.log.trace("Reading status GPS lock pin from port expander")
|
||||
raise NotImplementedError("GPS lock not implemented")
|
||||
# FIXME put it in a register
|
||||
# TODO: implement get_gps_lock, splits up functionality
|
||||
#gps_locked = bool(self._gpios.get("GPS-LOCKOK"))
|
||||
#return {
|
||||
# 'name': 'gps_lock',
|
||||
# 'type': 'BOOLEAN',
|
||||
# 'unit': 'locked' if gps_locked else 'unlocked',
|
||||
# 'value': str(gps_locked).lower(),
|
||||
#}
|
||||
|
||||
# TODO: Add other GPS sensors (time, TPV, SKY, etc.)
|
||||
# TODO: Add all physical sensors we can
|
||||
|
||||
###########################################################################
|
||||
# EEPROMs
|
||||
###########################################################################
|
||||
def get_mb_eeprom(self):
|
||||
"""
|
||||
Return a dictionary with EEPROM contents.
|
||||
|
||||
All key/value pairs are string -> string.
|
||||
|
||||
We don't actually return the EEPROM contents, instead, we return the
|
||||
mboard info again. This filters the EEPROM contents to what we think
|
||||
the user wants to know/see.
|
||||
"""
|
||||
return self.mboard_info
|
||||
|
||||
def set_mb_eeprom(self, eeprom_vals):
|
||||
"""
|
||||
See PeriphManagerBase.set_mb_eeprom() for docs.
|
||||
"""
|
||||
self.log.warn("Called set_mb_eeprom(), but not implemented!")
|
||||
raise NotImplementedError
|
||||
|
||||
def get_db_eeprom(self, dboard_idx):
|
||||
"""
|
||||
See PeriphManagerBase.get_db_eeprom() for docs.
|
||||
"""
|
||||
if dboard_idx != E320_DBOARD_SLOT_IDX:
|
||||
self.log.warn("Trying to access invalid dboard index {}. "
|
||||
"Using the only dboard.".format(dboard_idx))
|
||||
db_eeprom_data = copy.copy(self.dboard.device_info)
|
||||
for blob_id, blob in iteritems(self.dboard.get_user_eeprom_data()):
|
||||
if blob_id in db_eeprom_data:
|
||||
self.log.warn("EEPROM user data contains invalid blob ID "
|
||||
"%s", blob_id)
|
||||
else:
|
||||
db_eeprom_data[blob_id] = blob
|
||||
return db_eeprom_data
|
||||
|
||||
def set_db_eeprom(self, dboard_idx, eeprom_data):
|
||||
"""
|
||||
Write new EEPROM contents with eeprom_map.
|
||||
|
||||
Arguments:
|
||||
dboard_idx -- Slot index of dboard (can only be E320_DBOARD_SLOT_IDX)
|
||||
eeprom_data -- Dictionary of EEPROM data to be written. It's up to the
|
||||
specific device implementation on how to handle it.
|
||||
"""
|
||||
if dboard_idx != E320_DBOARD_SLOT_IDX:
|
||||
self.log.warn("Trying to access invalid dboard index {}. "
|
||||
"Using the only dboard.".format(dboard_idx))
|
||||
safe_db_eeprom_user_data = {}
|
||||
for blob_id, blob in iteritems(eeprom_data):
|
||||
if blob_id in self.dboard.device_info:
|
||||
error_msg = "Trying to overwrite read-only EEPROM " \
|
||||
"entry `{}'!".format(blob_id)
|
||||
self.log.error(error_msg)
|
||||
raise RuntimeError(error_msg)
|
||||
if not isinstance(blob, str) and not isinstance(blob, bytes):
|
||||
error_msg = "Blob data for ID `{}' is not a " \
|
||||
"string!".format(blob_id)
|
||||
self.log.error(error_msg)
|
||||
raise RuntimeError(error_msg)
|
||||
assert isinstance(blob, str)
|
||||
safe_db_eeprom_user_data[blob_id] = blob.encode('ascii')
|
||||
self.dboard.set_user_eeprom_data(safe_db_eeprom_user_data)
|
||||
|
||||
###########################################################################
|
||||
# Component updating
|
||||
###########################################################################
|
||||
# Note: Component updating functions defined by ZynqComponents
|
||||
@no_rpc
|
||||
def _update_fpga_type(self):
|
||||
"""Update the fpga type stored in the updateable components"""
|
||||
fpga_type = self.mboard_regs_control.get_fpga_type()
|
||||
self.log.debug("Updating mboard FPGA type info to {}".format(fpga_type))
|
||||
self.updateable_components['fpga']['type'] = fpga_type
|
||||
383
mpm/python/usrp_mpm/periph_manager/e320_periphs.py
Normal file
383
mpm/python/usrp_mpm/periph_manager/e320_periphs.py
Normal file
@ -0,0 +1,383 @@
|
||||
#
|
||||
# Copyright 2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
"""
|
||||
E320 peripherals
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank
|
||||
from usrp_mpm.sys_utils.uio import UIO
|
||||
|
||||
# Map register values to SFP transport types
|
||||
E320_SFP_TYPES = {
|
||||
0: "", # Port not connected
|
||||
1: "1G",
|
||||
2: "10G",
|
||||
3: "A", # Aurora
|
||||
}
|
||||
|
||||
E320_FPGA_TYPES_BY_SFP = {
|
||||
(""): "",
|
||||
("1G"): "1G",
|
||||
("10G"): "XG",
|
||||
("A"): "AA",
|
||||
}
|
||||
|
||||
class FrontpanelGPIO(GPIOBank):
|
||||
"""
|
||||
Abstraction layer for the front panel GPIO
|
||||
"""
|
||||
EMIO_BASE = 54
|
||||
FP_GPIO_OFFSET = 32 # Bit offset within the ps_gpio_* pins
|
||||
|
||||
def __init__(self, ddr):
|
||||
GPIOBank.__init__(
|
||||
self,
|
||||
'zynq_gpio',
|
||||
self.FP_GPIO_OFFSET + self.EMIO_BASE,
|
||||
0xFF, # use_mask
|
||||
ddr
|
||||
)
|
||||
|
||||
class MboardRegsControl(object):
|
||||
"""
|
||||
Control the FPGA Motherboard registers
|
||||
"""
|
||||
# Motherboard registers
|
||||
MB_COMPAT_NUM = 0x0000
|
||||
MB_DATESTAMP = 0x0004
|
||||
MB_GIT_HASH = 0x0008
|
||||
MB_SCRATCH = 0x000C
|
||||
MB_NUM_CE = 0x0010
|
||||
MB_NUM_IO_CE = 0x0014
|
||||
MB_CLOCK_CTRL = 0x0018
|
||||
MB_XADC_RB = 0x001C
|
||||
MB_BUS_CLK_RATE = 0x0020
|
||||
MB_BUS_COUNTER = 0x0024
|
||||
MB_SFP_PORT_INFO = 0x0028
|
||||
MB_GPIO_CTRL = 0x002C
|
||||
MB_GPIO_MASTER = 0x0030
|
||||
MB_GPIO_RADIO_SRC = 0x0034
|
||||
MB_GPS_CTRL = 0x0038
|
||||
MB_GPS_STATUS = 0x003C
|
||||
MB_DBOARD_CTRL = 0x0040
|
||||
MB_DBOARD_STATUS = 0x0044
|
||||
|
||||
# Bitfield locations for the MB_CLOCK_CTRL register.
|
||||
MB_CLOCK_CTRL_PPS_SEL_INT = 0
|
||||
MB_CLOCK_CTRL_PPS_SEL_EXT = 1
|
||||
MB_CLOCK_CTRL_REF_SEL = 2
|
||||
MB_CLOCK_CTRL_REF_CLK_LOCKED = 3
|
||||
|
||||
# Bitfield locations for the MB_GPIO_CTRL register.
|
||||
MB_GPIO_CTRL_BUFFER_OE_N = 0
|
||||
MB_GPIO_CTRL_EN_VAR_SUPPLY = 1
|
||||
MB_GPIO_CTRL_EN_2V5 = 2
|
||||
MB_GPIO_CTRL_EN_3V3 = 3
|
||||
|
||||
# Bitfield locations for the MB_GPS_CTRL register.
|
||||
MB_GPS_CTRL_PWR_EN = 0
|
||||
MB_GPS_CTRL_RST_N = 1
|
||||
MB_GPS_CTRL_INITSURV_N = 2
|
||||
|
||||
# Bitfield locations for the MB_GPS_STATUS register.
|
||||
MB_GPS_STATUS_LOCK = 0
|
||||
MB_GPS_STATUS_ALARM = 1
|
||||
MB_GPS_STATUS_PHASELOCK = 2
|
||||
MB_GPS_STATUS_SURVEY = 3
|
||||
MB_GPS_STATUS_WARMUP = 4
|
||||
|
||||
# Bitfield locations for the MB_DBOARD_CTRL register.
|
||||
MB_DBOARD_CTRL_MIMO = 0
|
||||
MB_DBOARD_CTRL_TX_CHAN_SEL = 1
|
||||
|
||||
def __init__(self, label, log):
|
||||
self.log = log
|
||||
self.regs = UIO(
|
||||
label=label,
|
||||
read_only=False
|
||||
)
|
||||
self.poke32 = self.regs.poke32
|
||||
self.peek32 = self.regs.peek32
|
||||
|
||||
def get_compat_number(self):
|
||||
"""get FPGA compat number
|
||||
|
||||
This function reads back FPGA compat number.
|
||||
The return is a tuple of
|
||||
2 numbers: (major compat number, minor compat number )
|
||||
"""
|
||||
with self.regs.open():
|
||||
compat_number = self.peek32(self.MB_COMPAT_NUM)
|
||||
minor = compat_number & 0xff
|
||||
major = (compat_number>>16) & 0xff
|
||||
return (major, minor)
|
||||
|
||||
def enable_fp_gpio(self, value):
|
||||
""" Enable front panel GPIO buffers and power supply
|
||||
and set voltage 1.8, 2.5 or 3.3 V
|
||||
Setting value to 0 would disable gpio
|
||||
"""
|
||||
if value == 0:
|
||||
enable = False
|
||||
else:
|
||||
enable = True
|
||||
self.set_fp_gpio_voltage(value)
|
||||
mask = 0xFFFFFFFF ^ ((0b1 << self.MB_GPIO_CTRL_BUFFER_OE_N) | \
|
||||
(0b1 << self.MB_GPIO_CTRL_EN_VAR_SUPPLY))
|
||||
with self.regs.open():
|
||||
reg_val = self.peek32(self.MB_GPIO_CTRL) & mask
|
||||
reg_val = reg_val | (not enable << self.MB_GPIO_CTRL_BUFFER_OE_N) | \
|
||||
(enable << self.MB_GPIO_CTRL_EN_VAR_SUPPLY)
|
||||
self.log.trace("Writing MB_GPIO_CTRL to 0x{:08X}".format(reg_val))
|
||||
return self.poke32(self.MB_GPIO_CTRL, reg_val)
|
||||
|
||||
def set_fp_gpio_voltage(self, value):
|
||||
""" Set Front Panel GPIO voltage (in volts)
|
||||
3V3 2V5 | Voltage
|
||||
-----------------
|
||||
0 0 | 1.8 V
|
||||
0 1 | 2.5 V
|
||||
1 0 | 3.3 V
|
||||
Arguments:
|
||||
value : 1.8, 2.5 or 3.3
|
||||
"""
|
||||
assert value in (1.8, 2.5, 3.3)
|
||||
if value == 1.8:
|
||||
voltage_reg = 0
|
||||
elif value == 2.5:
|
||||
voltage_reg = 1
|
||||
elif value == 3.3:
|
||||
voltage_reg = 2
|
||||
mask = 0xFFFFFFFF ^ ((0b1 << self.MB_GPIO_CTRL_EN_3V3) | \
|
||||
(0b1 << self.MB_GPIO_CTRL_EN_2V5))
|
||||
with self.regs.open():
|
||||
reg_val = self.peek32(self.MB_GPIO_CTRL) & mask
|
||||
reg_val = reg_val | (voltage_reg << self.MB_GPIO_CTRL_EN_2V5)
|
||||
self.log.trace("Writing MB_GPIO_CTRL to 0x{:08X}".format(reg_val))
|
||||
return self.poke32(self.MB_GPIO_CTRL, reg_val)
|
||||
|
||||
def get_fp_gpio_voltage(self):
|
||||
"""
|
||||
Get Front Panel GPIO voltage (in volts)
|
||||
"""
|
||||
mask = 0x3 << self.MB_GPIO_CTRL_EN_2V5
|
||||
voltage = [1.8, 2.5, 3.3]
|
||||
with self.regs.open():
|
||||
reg_val = (self.peek32(self.MB_GPIO_CTRL) & mask) >> self.MB_GPIO_CTRL_EN_2V5
|
||||
return voltage[reg_val]
|
||||
|
||||
def set_fp_gpio_master(self, value):
|
||||
"""set driver for front panel GPIO
|
||||
Arguments:
|
||||
value {unsigned} -- value is a single bit bit mask of 8 pins GPIO
|
||||
"""
|
||||
with self.regs.open():
|
||||
return self.poke32(self.MB_GPIO_MASTER, value)
|
||||
|
||||
def get_fp_gpio_master(self):
|
||||
"""get "who" is driving front panel gpio
|
||||
The return value is a bit mask of 8 pins GPIO.
|
||||
0: means the pin is driven by PL
|
||||
1: means the pin is driven by PS
|
||||
"""
|
||||
with self.regs.open():
|
||||
return self.peek32(self.MB_GPIO_MASTER) & 0xfff
|
||||
|
||||
def set_fp_gpio_radio_src(self, value):
|
||||
"""set driver for front panel GPIO
|
||||
Arguments:
|
||||
value {unsigned} -- value is 2-bit bit mask of 8 pins GPIO
|
||||
00: means the pin is driven by radio 0
|
||||
01: means the pin is driven by radio 1
|
||||
"""
|
||||
with self.regs.open():
|
||||
return self.poke32(self.MB_GPIO_RADIO_SRC, value)
|
||||
|
||||
def get_fp_gpio_radio_src(self):
|
||||
"""get which radio is driving front panel gpio
|
||||
The return value is 2-bit bit mask of 8 pins GPIO.
|
||||
00: means the pin is driven by radio 0
|
||||
01: means the pin is driven by radio 1
|
||||
"""
|
||||
with self.regs.open():
|
||||
return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffffff
|
||||
|
||||
def get_build_timestamp(self):
|
||||
"""
|
||||
Returns the build date/time for the FPGA image.
|
||||
The return is datetime string with the ISO 8601 format
|
||||
(YYYY-MM-DD HH:MM:SS.mmmmmm)
|
||||
"""
|
||||
with self.regs.open():
|
||||
datestamp_rb = self.peek32(self.MB_DATESTAMP)
|
||||
if datestamp_rb > 0:
|
||||
dt_str = datetime.datetime(
|
||||
year=((datestamp_rb>>17)&0x3F)+2000,
|
||||
month=(datestamp_rb>>23)&0x0F,
|
||||
day=(datestamp_rb>>27)&0x1F,
|
||||
hour=(datestamp_rb>>12)&0x1F,
|
||||
minute=(datestamp_rb>>6)&0x3F,
|
||||
second=((datestamp_rb>>0)&0x3F))
|
||||
self.log.trace("FPGA build timestamp: {}".format(str(dt_str)))
|
||||
return str(dt_str)
|
||||
else:
|
||||
# Compatibility with FPGAs without datestamp capability
|
||||
return ''
|
||||
|
||||
def get_git_hash(self):
|
||||
"""
|
||||
Returns the GIT hash for the FPGA build.
|
||||
The return is a tuple of
|
||||
2 numbers: (short git hash, bool: is the tree dirty?)
|
||||
"""
|
||||
with self.regs.open():
|
||||
git_hash_rb = self.peek32(self.MB_GIT_HASH)
|
||||
git_hash = git_hash_rb & 0x0FFFFFFF
|
||||
tree_dirty = ((git_hash_rb & 0xF0000000) > 0)
|
||||
dirtiness_qualifier = 'dirty' if tree_dirty else 'clean'
|
||||
self.log.trace("FPGA build GIT Hash: {:07x} ({})".format(
|
||||
git_hash, dirtiness_qualifier))
|
||||
return (git_hash, dirtiness_qualifier)
|
||||
|
||||
def set_time_source(self, time_source, ref_clk_freq):
|
||||
"""
|
||||
Set time source
|
||||
"""
|
||||
pps_sel_val = 0x0
|
||||
if time_source == 'internal' or time_source == 'gpsdo':
|
||||
self.log.trace("Setting time source to internal (GPSDO)"
|
||||
"({:.1f} MHz reference)...".format(ref_clk_freq))
|
||||
pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT
|
||||
elif time_source == 'external':
|
||||
self.log.debug("Setting time source to external...")
|
||||
pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_EXT
|
||||
else:
|
||||
assert False, "Cannot set to invalid time source: {}".format(time_source)
|
||||
with self.regs.open():
|
||||
reg_val = self.peek32(self.MB_CLOCK_CTRL) & 0xFFFFFF90
|
||||
# prevent glitches by writing a cleared value first, then the final value.
|
||||
self.poke32(self.MB_CLOCK_CTRL, reg_val)
|
||||
reg_val = reg_val | (pps_sel_val & 0x6F)
|
||||
self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
|
||||
self.poke32(self.MB_CLOCK_CTRL, reg_val)
|
||||
|
||||
def set_clock_source(self, clock_source, ref_clk_freq):
|
||||
"""
|
||||
Set clock source
|
||||
"""
|
||||
if clock_source == 'internal' or clock_source == 'gpsdo':
|
||||
self.log.trace("Setting clock source to internal (GPSDO)"
|
||||
"({:.1f} MHz reference)...".format(ref_clk_freq))
|
||||
ref_sel_val = 0b0
|
||||
elif clock_source == 'external':
|
||||
self.log.debug("Setting clock source to external..."
|
||||
"({:.1f} MHz reference)...".format(ref_clk_freq))
|
||||
ref_sel_val = 0b1
|
||||
else:
|
||||
assert False, "Cannot set to invalid clock source: {}".format(clock_source)
|
||||
mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_REF_SEL)
|
||||
with self.regs.open():
|
||||
reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask
|
||||
reg_val = reg_val | (ref_sel_val << self.MB_CLOCK_CTRL_REF_SEL)
|
||||
self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
|
||||
self.poke32(self.MB_CLOCK_CTRL, reg_val)
|
||||
|
||||
def get_fpga_type(self):
|
||||
"""
|
||||
Reads the type of the FPGA image currently loaded
|
||||
Returns a string with the type (ie 1G, XG, AU, etc.)
|
||||
"""
|
||||
with self.regs.open():
|
||||
sfp_info_rb = self.peek32(self.MB_SFP_PORT_INFO)
|
||||
# Print the registers values as 32-bit hex values
|
||||
self.log.trace("SFP Info: 0x{0:0{1}X}".format(sfp_info_rb, 8))
|
||||
try:
|
||||
sfp_type = E320_SFP_TYPES.get((sfp_info_rb & 0x0000FF00) >> 8, "")
|
||||
self.log.trace("SFP type: {}".format(sfp_type))
|
||||
return sfp_type
|
||||
except KeyError:
|
||||
self.log.warning("Unrecognized SFP type: {}"
|
||||
.format(sfp_type))
|
||||
return ""
|
||||
|
||||
def get_gps_locked_val(self):
|
||||
"""
|
||||
Get GPS LOCK status
|
||||
"""
|
||||
mask = 0b1 << self.MB_GPS_STATUS_LOCK
|
||||
with self.regs.open():
|
||||
reg_val = self.peek32(self.MB_GPS_STATUS) & mask
|
||||
gps_locked = reg_val & 0x1 #FIXME
|
||||
if gps_locked:
|
||||
self.log.trace("GPS locked!")
|
||||
# Can return this value because the gps_locked value is on the LSB
|
||||
return gps_locked
|
||||
|
||||
def get_gps_status(self):
|
||||
"""
|
||||
Get GPS status
|
||||
"""
|
||||
mask = 0x1F
|
||||
with self.regs.open():
|
||||
gps_status = self.peek32(self.MB_GPS_STATUS) & mask
|
||||
return gps_status
|
||||
|
||||
def enable_gps(self, enable):
|
||||
"""
|
||||
Turn power to the GPS (CLK_GPS_PWR_EN) off or on.
|
||||
Power signal is GPS_3V3.
|
||||
"""
|
||||
self.log.trace("{} power to GPS".format(
|
||||
"Enabling" if enable else "Disabling"
|
||||
))
|
||||
mask = 0xFFFFFFFF ^ (0b1 << self.MB_GPS_CTRL_PWR_EN)
|
||||
with self.regs.open():
|
||||
reg_val = self.peek32(self.MB_GPS_CTRL) & mask
|
||||
reg_val = reg_val | (enable << self.MB_GPS_CTRL_PWR_EN)
|
||||
self.log.trace("Writing MB_GPS_CTRL to 0x{:08X}".format(reg_val))
|
||||
return self.poke32(self.MB_GPS_CTRL, reg_val)
|
||||
|
||||
def get_refclk_lock(self):
|
||||
"""
|
||||
Check the status of the reference clock (adf4002) in FPGA.
|
||||
"""
|
||||
mask = 0b1 << self.MB_CLOCK_CTRL_REF_CLK_LOCKED
|
||||
with self.regs.open():
|
||||
reg_val = self.peek32(self.MB_CLOCK_CTRL)
|
||||
locked = (reg_val & mask) > 0
|
||||
if not locked:
|
||||
self.log.warning("Reference Clock reporting unlocked. "
|
||||
"MB_CLOCK_CTRL reg: 0x{:08X}".format(reg_val))
|
||||
else:
|
||||
self.log.trace("Reference Clock locked!")
|
||||
return locked
|
||||
|
||||
def set_channel_mode(self, channel_mode):
|
||||
"""
|
||||
Set channel mode in FPGA and select which tx channel to use
|
||||
channel mode = "MIMO" for mimo
|
||||
channel mode = "SISO_TX1", "SISO_TX0" for siso tx1, tx0 respectively.
|
||||
"""
|
||||
with self.regs.open():
|
||||
reg_val = self.peek32(self.MB_DBOARD_CTRL)
|
||||
if channel_mode == "MIMO":
|
||||
reg_val = (0b1 << self.MB_DBOARD_CTRL_MIMO)
|
||||
self.log.trace("Setting channel mode in AD9361 interface: {}".format("2R2T" if channel_mode == 2 else "1R1T"))
|
||||
else:
|
||||
# Warn if user tries to set either tx0/tx1 in mimo mode
|
||||
# as both will be set automatically
|
||||
if channel_mode == "SISO_TX1":
|
||||
# in SISO mode, Channel 1
|
||||
reg_val = (0b1 << self.MB_DBOARD_CTRL_TX_CHAN_SEL) | (0b0 << self.MB_DBOARD_CTRL_MIMO)
|
||||
self.log.trace("Setting TX channel in AD9361 interface to: TX1")
|
||||
elif channel_mode == "SISO_TX0":
|
||||
# in SISO mode, Channel 0
|
||||
reg_val = (0b0 << self.MB_DBOARD_CTRL_TX_CHAN_SEL) | (0b0 << self.MB_DBOARD_CTRL_MIMO)
|
||||
self.log.trace("Setting TX channel in AD9361 interface to: TX0")
|
||||
self.log.trace("Writing MB_DBOARD_CTRL to 0x{:08X}".format(reg_val))
|
||||
self.poke32(self.MB_DBOARD_CTRL, reg_val)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2017 Ettus Research, a National Instruments Company
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0
|
||||
#
|
||||
@ -13,21 +13,28 @@ INSTALL(PROGRAMS
|
||||
SET(eeprom_tool_sources)
|
||||
SET(eeprom_tool_libs)
|
||||
|
||||
IF("${MPM_DEVICE}" STREQUAL "n3xx")
|
||||
IF(ENABLE_LIBMPM)
|
||||
MESSAGE(STATUS "Adding MPM EEPROM tools...")
|
||||
SET(eeprom_tool_libs eeprom.c)
|
||||
SET(eeprom_tool_sources
|
||||
eeprom-blank.c
|
||||
eeprom-dump.c
|
||||
eeprom-id.c
|
||||
eeprom-init.c
|
||||
eeprom-set-flags.c
|
||||
)
|
||||
ENDIF(ENABLE_LIBMPM)
|
||||
IF(ENABLE_MYKONOS)
|
||||
MESSAGE(STATUS "Adding N3XX-specific EEPROM tools...")
|
||||
SET(eeprom_tool_libs eeprom.c)
|
||||
SET(eeprom_tool_sources
|
||||
db-dump.c
|
||||
db-id.c
|
||||
db-init.c
|
||||
eeprom-blank.c
|
||||
eeprom-dump.c
|
||||
eeprom-id.c
|
||||
eeprom-init.c
|
||||
eeprom-set-flags.c
|
||||
fan-limits.c
|
||||
)
|
||||
ENDIF("${MPM_DEVICE}" STREQUAL "n3xx")
|
||||
ENDIF(ENABLE_MYKONOS)
|
||||
|
||||
|
||||
#for each source: build an executable and install
|
||||
FOREACH(eeprom_tool_source ${eeprom_tool_sources})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user