diff options
author | Brent Stapleton <brent.stapleton@ettus.com> | 2018-04-17 19:33:23 -0700 |
---|---|---|
committer | Brent Stapleton <bstapleton@g.hmc.edu> | 2018-07-18 15:37:27 -0700 |
commit | 300a5e3f6e5e845b4b8d093222e1c51ca4640f79 (patch) | |
tree | 62f8bb7bc7d847b8f32f1fe5b4c9c06ef608080e /mpm/python/usrp_mpm/dboard_manager | |
parent | a1d6530ce50ca9590739ffa40464747d3db968eb (diff) | |
download | uhd-300a5e3f6e5e845b4b8d093222e1c51ca4640f79.tar.gz uhd-300a5e3f6e5e845b4b8d093222e1c51ca4640f79.tar.bz2 uhd-300a5e3f6e5e845b4b8d093222e1c51ca4640f79.zip |
mpm: initial commit of E320 code
Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com>
Diffstat (limited to 'mpm/python/usrp_mpm/dboard_manager')
-rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt | 5 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/__init__.py | 4 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/neon.py | 281 |
3 files changed, 286 insertions, 4 deletions
diff --git a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt index 6ad2f91f0..64217be07 100644 --- a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt +++ b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt @@ -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 diff --git a/mpm/python/usrp_mpm/dboard_manager/__init__.py b/mpm/python/usrp_mpm/dboard_manager/__init__.py index 3f7b6e9f0..8f6e5da96 100644 --- a/mpm/python/usrp_mpm/dboard_manager/__init__.py +++ b/mpm/python/usrp_mpm/dboard_manager/__init__.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 - diff --git a/mpm/python/usrp_mpm/dboard_manager/neon.py b/mpm/python/usrp_mpm/dboard_manager/neon.py new file mode 100644 index 000000000..e3797c7d3 --- /dev/null +++ b/mpm/python/usrp_mpm/dboard_manager/neon.py @@ -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 + + |